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
Instant
是 java.time
包中表示时间线上某一瞬时的类,基于秒+纳秒,非常适合做时间计算。
要生成两个 Instant
之间的随机时间,步骤如下:
- 获取起始和结束时间的 epoch 秒数
- 在该范围内生成一个随机 long 值
- 用
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
的构造函数接收的是自纪元以来的毫秒数。因此逻辑类似:
- 获取起始和结束时间的毫秒值
- 在范围内生成随机 long
- 构造
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
正是用来表示“日期”的不可变类。
实现方式:
- 将起始和结束日期转为 epoch day
- 在范围内生成随机 long
- 用
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
表示一天中的时间点。
实现逻辑:
- 将起始和结束时间转为“当天秒数”
- 生成范围内的随机 int
- 用
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.MIN
到 LocalTime.MAX
。
因此可直接复用有边界方法:
public static LocalTime time() {
return between(LocalTime.MIN, LocalTime.MAX);
}
简单粗暴,✅ 推荐。
5. 总结
- ✅ 核心思想:随机时间 = 随机时间戳 + 构造器转换
- ✅ **优先使用
java.time
**:Instant
、LocalDate
、LocalTime
,API 清晰且线程安全 - ✅ **多线程用
ThreadLocalRandom
**:性能更好,避免Random
的竞争问题 - ✅ 合理控制范围:避免生成过于极端的时间值,影响测试或业务逻辑
- ❌ **避免使用
java.util.Date
**:除非维护老系统
所有示例代码已上传至 GitHub:
https://github.com/baeldung/core-java-modules/tree/master/core-java-8-datetime-2