1. 概述

本文将介绍如何在 Java 中生成随机的日期和时间,涵盖有边界(指定范围)和无边界(全量范围)两种场景。

我们会分别使用传统的 java.util.Date API 和 Java 8 引入的新时间库 java.time 来实现。虽然老 API 仍在维护,但✅ **推荐新项目一律使用 java.time**,更清晰、线程安全且不易踩坑。

2. 随机日期时间(含时分秒)

核心思路很简单:日期时间本质就是距离 Unix 纪元(1970-01-01)的秒数或毫秒数。因此,生成随机时间 = 生成一个随机的时间戳 + 转换为时间对象。

我们使用 ThreadLocalRandom 而非 Math.random(),因为它在多线程环境下性能更好,且 API 更简洁。

2.1. 有边界 Instant

Instantjava.time 包中表示时间线上某一瞬时的类,基于秒+纳秒,非常适合做时间计算。

要生成两个 Instant 之间的随机时间,步骤如下:

  1. 获取起始和结束时间的 epoch 秒数
  2. 在该范围内生成一个随机 long 值
  3. Instant.ofEpochSecond() 构造结果
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);
}

验证示例:

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

⚠️ 注意:endExclusive不包含的,符合 Java 开闭区间惯例。

另外,也可轻松扩展出“之后”或“之前”的随机时间:

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

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

2.2. 有边界 Date(遗留 API)

java.util.Date 的构造函数接收的是自纪元以来的毫秒数。因此逻辑类似:

  1. 获取起始和结束时间的毫秒值
  2. 在范围内生成随机 long
  3. 构造 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);

提醒Date 类已过时,可变且线程不安全,仅用于兼容老系统。

2.3. 无边界 Instant

若不需要范围限制,直接生成一个随机秒数即可:

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

这里使用 nextInt()(32位)而非 nextLong(),是为了避免生成过于极端的时间(如几万年后),保持“合理”性。

验证其仍在有效范围内:

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

乘以 1000L 是为了单位转换。

验证:

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. 随机日期(仅年月日)

如果只需要日期部分(不含时间),可以用 epoch day 的概念 —— 即从 1970-01-01 起经过的天数。

3.1. 有边界 LocalDate

LocalDate 正是用来表示“日期”的不可变类。

实现方式:

  1. 将起始和结束日期转为 epoch day
  2. 在范围内生成随机 long
  3. LocalDate.ofEpochDay() 构造
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);
}

验证:

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

⚠️ 注意:isBetween() 在某些断言库中可能对 LocalDate 不支持开闭区间,建议拆开写更清晰。

3.2. 无边界 LocalDate

无边界时,我们仍希望时间“合理”,比如 ±100 年内:

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

验证:

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

4. 随机时间(仅时分秒)

如果只关心时间部分(如 “14:30:22”),可以用 “一天中的秒数”(0 ~ 86399)来表示。

4.1. 有边界 LocalTime

LocalTime 表示一天中的时间点。

实现逻辑:

  1. 将起始和结束时间转为“当天秒数”
  2. 生成范围内的随机 int
  3. LocalTime.ofSecondOfDay() 构造
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);
}

验证:

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. 无边界 LocalTime

无边界时间其实也有隐含范围:00:00:00 到 23:59:59,即 LocalTime.MINLocalTime.MAX

因此可直接复用有边界方法:

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

简单粗暴,✅ 推荐。

5. 总结

  • 核心思想:随机时间 = 随机时间戳 + 构造器转换
  • ✅ **优先使用 java.time**:InstantLocalDateLocalTime,API 清晰且线程安全
  • ✅ **多线程用 ThreadLocalRandom**:性能更好,避免 Random 的竞争问题
  • 合理控制范围:避免生成过于极端的时间值,影响测试或业务逻辑
  • ❌ **避免使用 java.util.Date**:除非维护老系统

所有示例代码已上传至 GitHub:

https://github.com/baeldung/core-java-modules/tree/master/core-java-8-datetime-2


原始标题:Generating Random Dates in Java