1. 引言

在Java应用中处理日期时间数据时,经常需要比较日期以满足任务调度、提醒或报告等需求。一个常见场景是判断给定日期是否为昨天。本文将探讨多种实现方案,帮助开发者准确识别日期对象是否代表昨天。

2. 使用 java.util.Calendar

传统方案是利用 java.util.Calendar 操作日期时间。通过 Calendar.getInstance() 获取实例,用 calendar.setTime(new Date()) 设置当前日期,再调用 calendar.add(Calendar.DATE, -1) 减去一天得到昨天日期。代码示例:

Date date = new Date(System.currentTimeMillis() - (1000 * 60 * 60 * 24));
Calendar expectedCalendar = Calendar.getInstance();
expectedCalendar.setTime(date);

Calendar actualCalendar = Calendar.getInstance();
actualCalendar.add(Calendar.DATE, -1);

boolean isEqualToYesterday = expectedCalendar.get(Calendar.YEAR) == actualCalendar.get(Calendar.YEAR) &&
  expectedCalendar.get(Calendar.MONTH) == actualCalendar.get(Calendar.MONTH) &&
  expectedCalendar.get(Calendar.DAY_OF_MONTH) == actualCalendar.get(Calendar.DAY_OF_MONTH);

assertTrue(isEqualToYesterday);

⚠️ 直接比较可能失败:Date 对象包含毫秒级时间信息,若时间分量不同会导致误判。解决方案是只比较年、月、日分量(如代码所示),忽略时间部分。

这些类虽被标记为遗留API,但在旧版Java环境中仍广泛使用。

3. 使用 java.util.Date 的毫秒值

此方案基于 Date 内部存储的毫秒数(自1970年1月1日UTC起)。核心思路是计算昨天零点的毫秒值,与目标日期的 getTime() 值比较。步骤如下:

  1. ZonedDateTime 计算昨天零点的毫秒时间戳
  2. 验证目标日期是否在 [昨天零点, 今天零点) 区间内
Date expectedDate = new Date(System.currentTimeMillis() - (1000 * 60 * 60 * 24));
ZonedDateTime yesterdayMidnight = ZonedDateTime.now().minusDays(1).truncatedTo(ChronoUnit.DAYS);
long yesterdayMidnightMillis = yesterdayMidnight.toInstant().toEpochMilli();

boolean isEqualToYesterday = expectedDate.getTime() >= yesterdayMidnightMillis && expectedDate.getTime() < yesterdayMidnightMillis + 86_400_000;

assertTrue(isEqualToYesterday);

✅ 优势:简单高效,适合直接操作遗留 Date 对象的场景。

4. 使用 java.time.LocalDate

Java 8+ 推荐方案:利用 LocalDateminusDays() 方法LocalDate 仅包含年月日信息,天然适合日期比较操作。实现步骤:

  1. 通过 LocalDate.now() 获取当前日期
  2. 调用 minusDays(1) 得到昨天日期
  3. 直接比较两个 LocalDate 对象
Date date = new Date(System.currentTimeMillis() - (1000 * 60 * 60 * 24));
LocalDate expectedLocalDate = LocalDate.of(date.getYear() + 1900, date.getMonth() + 1, date.getDate());

LocalDate actualLocalDate = LocalDate.now().minusDays(1);
boolean isEqualToYesterday = expectedLocalDate.equals(actualLocalDate);
assertTrue(isEqualToYesterday);

⚠️ 注意:Date.getYear() 返回1900年以来的年数,getMonth() 从0开始计数,需手动调整。

✅ 此方案代码简洁,线程安全,是Java 8+项目的首选方案。

5. 使用 Joda-Time

Joda-Time库提供 DateTime 类处理日期。虽然Java 8已内置日期API,但Joda-Time在旧项目中仍常见。核心步骤:

  1. DateTime.now() 获取当前时间
  2. 调用 minusDays(1) 减去一天
  3. 使用 withTimeAtStartOfDay() 重置时间为零点
Date date = new Date(System.currentTimeMillis() - (1000 * 60 * 60 * 24));
DateTime expectedDateTime = new DateTime(date).withTimeAtStartOfDay();

DateTime actualDateTime = DateTime.now().minusDays(1).withTimeAtStartOfDay();

boolean isEqualToYesterday = expectedDateTime.equals(actualDateTime);

assertTrue(isEqualToYesterday);

✅ 关键点:withTimeAtStartOfDay() 方法将时间分量重置为午夜,避免毫秒级差异影响比较。

⚠️ 随着Java 8日期API普及,Joda-Time的维护已放缓,新项目建议优先使用标准API。

6. 总结

各方案对比:

方案 适用场景 优势 劣势
java.util.Calendar 遗留系统维护 兼容旧版Java API繁琐,易出错
Date 毫秒值 快速比较 性能高 需手动处理时区/夏令时
java.time.LocalDate Java 8+新项目 API简洁,线程安全 不兼容Java 7及以下
Joda-Time 已使用Joda-Time的项目 功能丰富 依赖第三方库,发展停滞

推荐选择

  • Java 8+ 项目:优先使用 LocalDate
  • 遗留系统:根据现有代码库选择 CalendarDate 毫秒方案
  • 已集成Joda-Time的项目:继续使用,但长期建议迁移

本文示例代码已上传至 GitHub


原始标题:Checking if a Date Object Equals Yesterday