1. 简介
本文将重点讲解如何使用 Java 8 的日期时间 API(Date/Time API)进行日期比较。我们会深入探讨判断两个日期是否相等、以及如何对日期进行大小比较的多种方式。
核心目标是帮助你在实际开发中避免踩坑,尤其是在处理时区、时间精度和跨类型比较时,给出简单粗暴又可靠的解决方案。
2. 日期比较基础
Java 8 中表示日期最基本的方式是 LocalDate
。我们先创建两个 LocalDate
实例,分别代表 2019 年 8 月 10 日和 2019 年 7 月 1 日:
LocalDate firstDate = LocalDate.of(2019, 8, 10);
LocalDate secondDate = LocalDate.of(2019, 7, 1);
✅ 推荐使用以下方法进行比较:
isAfter()
:判断当前日期是否在目标日期之后isBefore()
:判断当前日期是否在目标日期之前isEqual()
:判断两个日期是否表示本地时间线上的同一时刻equals()
和compareTo()
:标准的 Java 对象比较方式
示例代码如下:
assertThat(firstDate.isAfter(secondDate), is(true));
上面断言会通过,因为 8 月 10 日确实在 7 月 1 日之后。
assertThat(firstDate.isBefore(secondDate), is(false));
显然,firstDate
不可能在 secondDate
之前。
assertThat(firstDate.isEqual(firstDate), is(true));
assertThat(firstDate.isEqual(secondDate), is(false));
isEqual()
是语义最清晰的日期相等判断方法。
2.1 使用 Comparable 接口比较
equals()
方法在类型一致时,行为与 isEqual()
相同:
assertThat(firstDate.equals(secondDate), is(false));
⚠️ 但注意:isEqual()
更强大,它允许跨日历系统比较,比如可以拿 LocalDate
和 JapaneseDate
比较。而 equals()
要求类型完全一致,否则直接返回 false
。
使用 compareTo()
进行数值化比较:
assertThat(firstDate.compareTo(secondDate), is(1)); // 大于返回正数
assertThat(secondDate.compareTo(firstDate), is(-1)); // 小于返回负数
这是实现排序或范围判断的基础。
3. 包含时间成分的日期比较
当涉及到具体时间(时分秒)时,我们使用 LocalDateTime
或 ZonedDateTime
。
LocalDateTime 比较
与 LocalDate
类似,LocalDateTime
也支持 isAfter()
、isBefore()
、isEqual()
等方法:
LocalDateTime time1 = LocalDateTime.of(2019, 8, 10, 12, 30);
LocalDateTime time2 = LocalDateTime.of(2019, 8, 10, 13, 00);
assertThat(time1.isBefore(time2), is(true));
ZonedDateTime 比较:时区陷阱 ⚠️
这才是最容易踩坑的地方。来看一个经典例子:
ZonedDateTime timeInNewYork =
ZonedDateTime.of(2019, 8, 10, 8, 0, 0, 0, ZoneId.of("America/New_York"));
ZonedDateTime timeInBerlin =
ZonedDateTime.of(2019, 8, 10, 14, 0, 0, 0, ZoneId.of("Europe/Berlin"));
这两个时间点实际上是 同一时刻(UTC 时间相同),验证如下:
assertThat(timeInNewYork.isAfter(timeInBerlin), is(false));
assertThat(timeInNewYork.isBefore(timeInBerlin), is(false));
assertThat(timeInNewYork.isEqual(timeInBerlin), is(true)); // ✅ 同一时刻
但注意!equals()
和 compareTo()
的行为不同:
assertThat(timeInNewYork.equals(timeInBerlin), is(false));
assertThat(timeInNewYork.compareTo(timeInBerlin), is(-1));
❌ 原因:equals()
比较的是对象的完整字段(包括 ZoneId
),而 isEqual()
比较的是 时间轴上的瞬时点(instant)。所以虽然时间相同,但由于时区不同,equals()
返回 false
。
📌 结论:**判断两个带时区的时间是否“相等”,一定要用 isEqual()
,而不是 equals()
**。
4. 更复杂的比较场景
有时候我们不需要精确到毫秒,而是想比较“是否同一天”、“是否同一小时”等。这时可以用 truncatedTo()
方法。
工具方法封装
public static boolean isSameDay(LocalDateTime timestamp, LocalDate localDateToCompare) {
return timestamp.toLocalDate().isEqual(localDateToCompare);
}
这个方法用于比较 LocalDateTime
和 LocalDate
是否是同一天。
public static boolean isSameDay(LocalDateTime timestamp, LocalDateTime timestampToCompare) {
return timestamp.truncatedTo(ChronoUnit.DAYS)
.isEqual(timestampToCompare.truncatedTo(ChronoUnit.DAYS));
}
📌 truncatedTo(ChronoUnit.DAYS)
会把时间部分截断为 00:00:00,只保留日期。
同理,我们可以实现按小时对齐的比较:
public static boolean isSameHour(LocalDateTime timestamp, LocalDateTime timestampToCompare) {
return timestamp.truncatedTo(ChronoUnit.HOURS)
.isEqual(timestampToCompare.truncatedTo(ChronoUnit.HOURS));
}
这个技巧同样适用于 ZonedDateTime
:
public static boolean isSameHour(ZonedDateTime zonedTimestamp, ZonedDateTime zonedTimestampToCompare) {
return zonedTimestamp.truncatedTo(ChronoUnit.HOURS)
.isEqual(zonedTimestampToCompare.truncatedTo(ChronoUnit.HOURS));
}
验证示例:
ZonedDateTime zonedTimestamp =
ZonedDateTime.of(2019, 8, 10, 8, 30, 0, 0, ZoneId.of("America/New_York"));
ZonedDateTime zonedTimestampToCompare =
ZonedDateTime.of(2019, 8, 10, 14, 0, 0, 0, ZoneId.of("Europe/Berlin"));
assertThat(DateTimeComparisonUtils.isSameHour(zonedTimestamp, zonedTimestampToCompare), is(true));
✅ 尽管本地时间分别是 8:30 和 14:00,但由于它们对应的 UTC 时间在同一小时内,所以判断为“同一小时”。
5. 老版本 Date API 的比较
在 Java 8 之前,我们只能使用 java.util.Date
和 Calendar
,它们的设计问题很多:
- ❌ 线程不安全
- ❌ 可变对象
- ❌ 语义模糊(
Date
其实是瞬时点,不是“日期”)
尽管如此,它们也提供了基本的比较方法:
Date firstDate = toDate(LocalDateTime.of(2019, 8, 10, 0, 00, 00));
Date secondDate = toDate(LocalDateTime.of(2019, 8, 15, 0, 00, 00));
assertThat(firstDate.after(secondDate), is(false));
assertThat(firstDate.before(secondDate), is(true));
assertThat(firstDate.compareTo(secondDate), is(-1));
assertThat(firstDate.equals(secondDate), is(false));
这些方法基于毫秒级的瞬时点进行比较。
推荐工具类:Apache Commons Lang
对于复杂比较,强烈建议使用 DateUtils
:
public static boolean isSameDay(Date date, Date dateToCompare) {
return DateUtils.isSameDay(date, dateToCompare);
}
public static boolean isSameHour(Date date, Date dateToCompare) {
return DateUtils.truncatedEquals(date, dateToCompare, Calendar.HOUR);
}
📌 注意:truncatedEquals
会先截断再比较,避免了时间部分的干扰。
⚠️ 跨 API 比较建议:如果需要混合使用新旧 API(如 Date
和 LocalDateTime
),务必先做类型转换,再比较。推荐参考官方文档中的 Date 与 LocalDate 互转指南。
6. 总结
比较需求 | 推荐方法 |
---|---|
是否之后/之前 | isAfter() , isBefore() |
是否同一时刻 | isEqual() ✅ |
对象是否完全相同 | equals() ❌(慎用) |
排序/数值比较 | compareTo() |
是否同一天/小时 | truncatedTo() + isEqual() |
老 API 复杂比较 | DateUtils 工具类 |
✅ 核心原则:
- 优先使用 Java 8 的
java.time
包 - 判断“时间点”是否相同用
isEqual()
- 避免直接使用
equals()
比较时间对象 - 涉及时区时,务必理解
ZonedDateTime
的行为差异
所有示例代码已上传至 GitHub:https://github.com/baeldung/tutorials/tree/master/core-java-modules/core-java-8-datetime