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()
值比较。步骤如下:
- 用
ZonedDateTime
计算昨天零点的毫秒时间戳 - 验证目标日期是否在
[昨天零点, 今天零点)
区间内
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+ 推荐方案:利用 LocalDate
的 minusDays()
方法。LocalDate
仅包含年月日信息,天然适合日期比较操作。实现步骤:
- 通过
LocalDate.now()
获取当前日期 - 调用
minusDays(1)
得到昨天日期 - 直接比较两个
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在旧项目中仍常见。核心步骤:
- 用
DateTime.now()
获取当前时间 - 调用
minusDays(1)
减去一天 - 使用
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
- 遗留系统:根据现有代码库选择
Calendar
或Date
毫秒方案 - 已集成Joda-Time的项目:继续使用,但长期建议迁移
本文示例代码已上传至 GitHub