1. 概要

このチュートリアルでは、制限付きおよび制限なしの方法でランダムな日付と時刻を生成する方法を確認します。

従来のjava.util.Date APIと、Java8の新しい日時ライブラリを使用してこれらの値を生成する方法を見ていきます。

2. ランダムな日付と時刻

日付と時刻はエポックタイムと比較して32ビット整数にすぎないため、次の単純なアルゴリズムに従ってランダムな時間値を生成できます。

  1. ランダムな32ビット数intを生成します
  2. 生成されたランダム値を適切な日時コンストラクターまたはビルダーに渡します

2.1. バウンドインスタント

java.time.Instantは、Java8で追加された新しい日付と時刻の1つです。これらはタイムライン上の瞬間的なポイントを表します。

他の2つの間でランダムなインスタントを生成するために、次のことができます。

  1. 指定されたInstantsのエポック秒の間にランダムな数値を生成します
  2. そのランダムな数値をofEpochSecond()メソッドに渡すことにより、ランダムなInstantを作成します
public static Instant between(Instant startInclusive, Instant endExclusive) {
    long startSeconds = startInclusive.getEpochSecond();
    long endSeconds = endExclusive.getEpochSecond();
    long random = ThreadLocalRandom
      .current()
      .nextLong(startSeconds, endSeconds);

    return Instant.ofEpochSecond(random);
}

マルチスレッド環境でより高いスループットを実現するために、ThreadLocalRandomを使用して乱数を生成しています。

生成されたInstantは常に最初のInstant以上であり、は2番目の Instant:未満であることを確認できます。

Instant hundredYearsAgo = Instant.now().minus(Duration.ofDays(100 * 365));
Instant tenDaysAgo = Instant.now().minus(Duration.ofDays(10));
Instant random = RandomDateTimes.between(hundredYearsAgo, tenDaysAgo);
assertThat(random).isBetween(hundredYearsAgo, tenDaysAgo);

もちろん、ランダム性のテストは本質的に非決定論的であり、実際のアプリケーションでは一般的に推奨されないことを忘れないでください。

同様に、別のインスタントの前後にランダムなインスタントを生成することもできます。

public static Instant after(Instant startInclusive) {
    return between(startInclusive, Instant.MAX);
}

public static Instant before(Instant upperExclusive) {
    return between(Instant.MIN, upperExclusive);
}

2.2. 制限付き日付

java.util.Dateコンストラクターの1つは、エポック後のミリ秒数を取ります。したがって、同じアルゴリズムを使用して、他の2つの間にランダムな日付を生成できます。

public static Date between(Date startInclusive, Date endExclusive) {
    long startMillis = startInclusive.getTime();
    long endMillis = endExclusive.getTime();
    long randomMillisSinceEpoch = ThreadLocalRandom
      .current()
      .nextLong(startMillis, endMillis);

    return new Date(randomMillisSinceEpoch);
}

同様に、この動作を検証できるはずです。

long aDay = TimeUnit.DAYS.toMillis(1);
long now = new Date().getTime();
Date hundredYearsAgo = new Date(now - aDay * 365 * 100);
Date tenDaysAgo = new Date(now - aDay * 10);
Date random = LegacyRandomDateTimes.between(hundredYearsAgo, tenDaysAgo);
assertThat(random).isBetween(hundredYearsAgo, tenDaysAgo);

2.3. 無制限インスタント

完全にランダムなInstantを生成するには、ランダムな整数を生成し、それを ofEpochSecond()メソッドに渡すだけです。

public static Instant timestamp() {
    return Instant.ofEpochSecond(ThreadLocalRandom.current().nextInt());
}

エポックタイムがより妥当なランダムタイムを生成するため、32ビット秒を使用します。したがって、ここでは nextInt()メソッドを使用しています

また、この値は、Javaが処理できるInstantの最小値と最大値の間にある必要があります。

Instant random = RandomDateTimes.timestamp();
assertThat(random).isBetween(Instant.MIN, Instant.MAX);

2.4. 無制限の日付

有界の例と同様に、ランダムな値を Dateのコンストラクターに渡して、ランダムな Date:を生成できます。

public static Date timestamp() {
    return new Date(ThreadLocalRandom.current().nextInt() * 1000L);
}

コンストラクターの時間単位はミリ秒であるため、32ビットエポック秒に1000を掛けてミリ秒に変換しています。

確かに、この値はまだ最小と最大の可能な日付値の間にあります:

Date MIN_DATE = new Date(Long.MIN_VALUE);
Date MAX_DATE = new Date(Long.MAX_VALUE);
Date random = LegacyRandomDateTimes.timestamp();
assertThat(random).isBetween(MIN_DATE, MAX_DATE);

3. ランダムな日付

これまで、日付と時刻の両方のコンポーネントを含むランダムなテンポラルを生成していました。 同様に、エポック日の概念を使用して、日付コンポーネントのみでランダムなテンポラルを生成できます。

エポック日は、1970年1月1日からの日数と同じです。 したがって、ランダムな日付を生成するには、ランダムな数値を生成し、その数値をエポック日として使用する必要があります。

3.1. 跳ねる

日付コンポーネントのみを含む一時的な抽象化が必要なので、java.time.LocalDateは適切な候補のようです。

public static LocalDate between(LocalDate startInclusive, LocalDate endExclusive) {
    long startEpochDay = startInclusive.toEpochDay();
    long endEpochDay = endExclusive.toEpochDay();
    long randomDay = ThreadLocalRandom
      .current()
      .nextLong(startEpochDay, endEpochDay);

    return LocalDate.ofEpochDay(randomDay);
}

ここでは、 toEpochDay()メソッドを使用して、各LocalDateを対応するエポック日に変換しています。 同様に、このアプローチが正しいことを確認できます。

LocalDate start = LocalDate.of(1989, Month.OCTOBER, 14);
LocalDate end = LocalDate.now();
LocalDate random = RandomDates.between(start, end);
assertThat(random).isAfterOrEqualTo(start, end);

3.2. 無制限

範囲に関係なくランダムな日付を生成するために、ランダムなエポック日を単純に生成できます。

public static LocalDate date() {
    int hundredYears = 100 * 365;
    return LocalDate.ofEpochDay(ThreadLocalRandom
      .current().nextInt(-hundredYears, hundredYears));
}

私たちのランダム日付ジェネレータは、エポックの前後100年からランダムな日を選択します。 繰り返しますが、この背後にある理論的根拠は、妥当な日付値を生成することです。

LocalDate randomDay = RandomDates.date();
assertThat(randomDay).isBetween(LocalDate.MIN, LocalDate.MAX);

4. ランダム時間

日付で行ったのと同様に、時間コンポーネントだけでランダムなテンポラルを生成できます。 そのために、 1日の秒の概念を使用できます。つまり、ランダムな時間は、1日の始まりからの秒を表すランダムな数値に等しくなります。

4.1. 跳ねる

java.time.LocalTime クラスは、時間コンポーネントのみをカプセル化する一時的な抽象化です。

public static LocalTime between(LocalTime startTime, LocalTime endTime) {
    int startSeconds = startTime.toSecondOfDay();
    int endSeconds = endTime.toSecondOfDay();
    int randomTime = ThreadLocalRandom
      .current()
      .nextInt(startSeconds, endSeconds);

    return LocalTime.ofSecondOfDay(randomTime);
}

他の2つの間にランダムな時間を生成するために、次のことができます。

  1. 指定された時刻の日の秒の間にランダムな数値を生成します
  2. そのランダムな数字を使用してランダムな時間を作成します

このランダムな時間生成アルゴリズムの動作を簡単に確認できます。

LocalTime morning = LocalTime.of(8, 30);
LocalTime randomTime = RandomTimes.between(LocalTime.MIDNIGHT, morning);
assertThat(randomTime)
  .isBetween(LocalTime.MIDNIGHT, morning)
  .isBetween(LocalTime.MIN, LocalTime.MAX);

4.2. 無制限

無制限の時間値でさえ、00:00:00から23:59:59の範囲にある必要があるため、委任によってこのロジックを簡単に実装できます。

public static LocalTime time() {
    return between(LocalTime.MIN, LocalTime.MAX);
}

5. 結論

このチュートリアルでは、ランダムな日付と時刻の定義をランダムな数値に減らしました。 次に、この削減が、タイムスタンプ、日付、または時刻のように動作するランダムな時間値を生成するのにどのように役立つかを確認しました。

いつものように、サンプルコードはGitHubで入手できます。