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() 更强大,它允许跨日历系统比较,比如可以拿 LocalDateJapaneseDate 比较。而 equals() 要求类型完全一致,否则直接返回 false

使用 compareTo() 进行数值化比较:

assertThat(firstDate.compareTo(secondDate), is(1));  // 大于返回正数
assertThat(secondDate.compareTo(firstDate), is(-1)); // 小于返回负数

这是实现排序或范围判断的基础。

3. 包含时间成分的日期比较

当涉及到具体时间(时分秒)时,我们使用 LocalDateTimeZonedDateTime

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

这个方法用于比较 LocalDateTimeLocalDate 是否是同一天。

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.DateCalendar,它们的设计问题很多:

  • ❌ 线程不安全
  • ❌ 可变对象
  • ❌ 语义模糊(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(如 DateLocalDateTime),务必先做类型转换,再比较。推荐参考官方文档中的 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


原始标题:Comparing Dates in Java | Baeldung