在Java中生成随机日期

2023/06/09

1. 概述

在本教程中,我们将了解如何以有界和无界的方式生成随机日期和时间。

我们将研究如何使用遗留的java.util.Date API 以及Java 8中的新日期时间库生成这些值。

2.随机日期和时间

与纪元时间相比,日期和时间不过是 32 位整数,因此我们可以通过以下简单算法生成随机时间值:

  1. 生成一个随机的 32 位数字,一个int
  2. 将生成的随机值传递给适当的日期和时间构造函数或构建器

2.1. 有界瞬间

java.time.I nstant是Java 8 中新增的日期和时间之一。它们表示时间线上的瞬时点。

为了 在另外两个 Instant 之间生成一个随机Instant ,我们可以:

  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 并且 小于第二个 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);

当然,请记住,测试随机性本质上是不确定的,通常不建议在实际应用中使用。

同样,也可以在另一个 Instant 之后或之前生成一个随机Instant :

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 构造函数之一 采用纪元后的毫秒数。因此,我们可以使用相同的算法在其他两个日期之间生成一个随机 日期 :

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 值之间:

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.随机时间

与我们对日期所做的类似,我们可以生成仅包含时间成分的随机时间。为了做到这一点,我们可以使用一天中的第二个概念。也就是说,一个随机时间等于一个随机数,表示自一天开始以来的秒数。

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);
}

为了在另外两个时间之间生成一个随机时间,我们可以:

  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上获得。

Show Disqus Comments

Post Directory

扫码关注公众号:Taketoday
发送 290992
即可立即永久解锁本站全部文章