1. 日期与时间的加减
在编程中,处理日期和时间并不仅仅是“把日期漂亮地输出到屏幕”。我们经常需要:
- 知道距离重要事件还有多少天;
- 计算用户年龄;
- 确定两个事件之间过去了多少时间;
- 检查截止日期是否已到;
- 在日期上加上一定数量的天、周、月或小时。
例如,在我们的应用中想提醒用户距离任务的截止日期还有 3 天;或者在生日已过(日期已在过去)时向他表示祝贺。
为此,java.time 提供了专门的类和方法,使这些计算简单且安全。
加法与减法的基本方法
所有日期与时间类(LocalDate、LocalTime、LocalDateTime、ZonedDateTime、Instant)都有以下方法:
- plusXxx() — 相加(天、月、年、小时、分钟等)
- minusXxx() — 相减(同上)
import java.time.LocalDate;
LocalDate today = LocalDate.now(); // 今天的日期
LocalDate tomorrow = today.plusDays(1); // 明天
LocalDate nextMonth = today.plusMonths(1); // 一个月后
LocalDate lastWeek = today.minusWeeks(1); // 一周前
System.out.println("今天: " + today);
System.out.println("明天: " + tomorrow);
System.out.println("一个月后: " + nextMonth);
System.out.println("一周前: " + lastWeek);
所有这些方法都会返回一个新对象,而不会修改原对象。不可变性——一切的基础!
import java.time.LocalTime;
LocalTime now = LocalTime.now();
LocalTime inTwoHours = now.plusHours(2);
LocalTime tenMinutesAgo = now.minusMinutes(10);
System.out.println("现在: " + now);
System.out.println("2 小时后: " + inTwoHours);
System.out.println("10 分钟前: " + tenMinutesAgo);
组合方法
LocalDate vacation = today.plusMonths(2).plusDays(10);
System.out.println("休假: " + vacation);
2. 获取日期差值:Period 与 Duration
当需要知道两个日期或两个时刻之间经过了多久时,两个主角登场——Period 与 Duration。让我们更近地了解它们。
Period 与 Duration 有何不同?
- Period — 用于按日期(年、月、日)处理。例如,“3 年 2 个月 5 天”。
- Duration — 用于按时间(小时、分钟、秒、纳秒)处理。例如,“5 小时 30 分钟”。
| 类 | 用途 | 用法示例 |
|---|---|---|
|
日期之间的差值 | 两日期之间的年/月/日数量 |
|
时刻之间的差值 | 两事件之间的小时/分钟/秒 |
示例:计算用户年龄
import java.time.LocalDate;
import java.time.Period;
LocalDate birthday = LocalDate.of(2000, 1, 15); // 生日
LocalDate today = LocalDate.now();
Period age = Period.between(birthday, today);
System.out.println("年龄: " + age.getYears() + " 年, " +
age.getMonths() + " 个月, " +
age.getDays() + " 天");
类比:Period 就像“人类可读”的日期差(“我 24 年 5 个月 12 天大”)。
示例:距离截止日期还剩多久
import java.time.LocalDate;
LocalDate deadline = LocalDate.of(2025, 7, 1);
LocalDate today = LocalDate.now();
if (today.isBefore(deadline)) {
Period left = Period.between(today, deadline);
System.out.println("距离截止日期还剩: " +
left.getMonths() + " 个月和 " +
left.getDays() + " 天");
} else {
System.out.println("截止日期已过!");
}
Duration:时间差
import java.time.LocalDateTime;
import java.time.Duration;
LocalDateTime start = LocalDateTime.of(2025, 6, 1, 10, 0, 0);
LocalDateTime end = LocalDateTime.of(2025, 6, 1, 15, 30, 0);
Duration duration = Duration.between(start, end);
System.out.println("持续时间: " + duration.toHours() + " 小时 " +
(duration.toMinutes() % 60) + " 分钟");
Duration 以秒、分钟、小时、天来度量(其中“天”始终按 24 小时计算,不考虑日历特性)。
对比:什么时候用 Period,什么时候用 Duration?
- 如果只有日期(没有时间)——使用 Period。
- 如果包含时间(例如 LocalDateTime、Instant)——使用 Duration。
3. 日期与时间比较:isBefore、isAfter、equals
有时只需要判断:日期是否已经到来?事件在过去还是未来?为此,所有日期与时间类都提供了这些方法:
- isBefore(otherDate)
- isAfter(otherDate)
- isEqual(otherDate)(或直接 equals)
import java.time.LocalDate;
LocalDate today = LocalDate.now();
LocalDate deadline = LocalDate.of(2025, 7, 1);
if (today.isBefore(deadline)) {
System.out.println("还有时间!");
} else if (today.isEqual(deadline)) {
System.out.println("今天是截止日期!");
} else {
System.out.println("截止日期已经过去了 :(");
}
import java.time.LocalTime;
LocalTime now = LocalTime.now();
LocalTime lunch = LocalTime.of(13, 0);
if (now.isAfter(lunch)) {
System.out.println("已经吃过午饭了!");
} else {
System.out.println("离午饭还有时间。");
}
重要:只能比较相同类型的对象(LocalDate 对 LocalDate,LocalDateTime 对 LocalDateTime,等等)。
4. 实践:计算差值与检查期限
示例:距离任务截止日期还剩多少天
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
LocalDate today = LocalDate.now();
LocalDate deadline = LocalDate.of(2025, 7, 1);
long daysLeft = ChronoUnit.DAYS.between(today, deadline);
if (daysLeft > 0) {
System.out.println("距离截止日期还有 " + daysLeft + " 天");
} else if (daysLeft == 0) {
System.out.println("今天是截止日期!");
} else {
System.out.println("截止日期已过 " + Math.abs(daysLeft) + " 天");
}
ChronoUnit.DAYS.between() —— 获取两日期之间精确天数的便捷方式(不考虑月份和年份)。
示例:两个事件之间相隔多少秒
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
LocalDateTime start = LocalDateTime.of(2025, 6, 1, 10, 0, 0);
LocalDateTime end = LocalDateTime.of(2025, 6, 1, 15, 30, 0);
long seconds = ChronoUnit.SECONDS.between(start, end);
System.out.println("两次事件之间经过了 " + seconds + " 秒");
示例:计算任务的“年龄”
import java.time.LocalDate;
import java.time.Period;
LocalDate created = LocalDate.of(2025, 5, 20);
LocalDate today = LocalDate.now();
Period period = Period.between(created, today);
System.out.println("任务创建于 " +
period.getDays() + " 天, " +
period.getMonths() + " 个月和 " +
period.getYears() + " 年前。");
示例:两个 Instant 之间的持续时间
import java.time.Instant;
import java.time.Duration;
Instant start = Instant.now();
// ... 做点什么 ...
Instant end = Instant.now();
Duration duration = Duration.between(start, end);
System.out.println("执行耗时 " + duration.toMillis() + " 毫秒");
5. 使用 Period 与 Duration 的注意事项
Period 的特点
- Period 按日历计算差值:2 月 28 日与 3 月 1 日之间是 1 天,即使跨月。
- 可以分别获取年、月、日:getYears()、getMonths()、getDays()。
- 如果需要两日期之间的总天数——使用 ChronoUnit.DAYS.between()。
Duration 的特点
- Duration 处理精确时间(小时、分钟、秒)。
- 可与 LocalTime、LocalDateTime、Instant 一起使用。
- 不能用于仅日期的 LocalDate —— 否则会抛出异常。
示例:Period 与 Duration 的差异
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Duration;
import java.time.Period;
LocalDate date1 = LocalDate.of(2025, 2, 28);
LocalDate date2 = LocalDate.of(2025, 3, 1);
Period period = Period.between(date1, date2); // 2 天(闰年)
long days = java.time.temporal.ChronoUnit.DAYS.between(date1, date2); // 2 天
System.out.println("Period: " + period.getDays() + " 天");
System.out.println("ChronoUnit: " + days + " 天");
LocalDateTime dt1 = LocalDateTime.of(2025, 6, 1, 23, 0);
LocalDateTime dt2 = LocalDateTime.of(2025, 6, 2, 1, 0);
Duration duration = Duration.between(dt1, dt2); // 2 小时
System.out.println("Duration: " + duration.toHours() + " 小时");
6. 用 Duration 与 Period 以友好格式输出差值
当为用户展示日期或时间差时,“123456 秒”并不直观。通常我们更希望看到类似“2 天 3 小时 15 分钟”。
示例:输出日期与时间的差值
import java.time.LocalDateTime;
import java.time.Duration;
LocalDateTime start = LocalDateTime.of(2025, 6, 1, 14, 0);
LocalDateTime end = LocalDateTime.of(2025, 6, 3, 16, 30);
Duration duration = Duration.between(start, end);
long days = duration.toDays();
long hours = duration.minusDays(days).toHours();
long minutes = duration.minusDays(days).minusHours(hours).toMinutes();
System.out.println("差异: " + days + " 天 " +
hours + " 小时 " + minutes + " 分钟");
7. 使用 ZonedDateTime 与 Instant 的注意点
- 不同时区的两个 ZonedDateTime 之间的差值按绝对时间计算,而不是各自时区的“本地时间”。
- 夏令时/冬令时的切换会影响两个日期之间的小时差。
示例:ZonedDateTime 之间的差值
import java.time.ZonedDateTime;
import java.time.ZoneId;
import java.time.Duration;
ZonedDateTime MinskTime = ZonedDateTime.of(2025, 6, 1, 12, 0, 0, 0, ZoneId.of("Europe/Minsk"));
ZonedDateTime nyTime = ZonedDateTime.of(2025, 6, 1, 5, 0, 0, 0, ZoneId.of("America/New_York"));
Duration diff = Duration.between(nyTime, MinskTime);
System.out.println("差值: " + diff.toHours() + " 小时");
8. 进行日期与时间计算时的常见错误
错误 №1:混淆 Period 和 Duration。
如果尝试把 Duration 用于 LocalDate,会得到异常。处理日期请用 Period,处理时间请用 Duration。
错误 №2:忽略时区。
在不同时区比较日期与时间,可能得到意料之外的结果。最好统一到同一时区,或使用 Instant。
错误 №3:以为 plus/minus 方法会修改对象本身。
所有 java.time 对象都是不可变的!别忘了把结果赋给新变量(例如 today = today.plusDays(1))。
错误 №4:对 Period 使用 getDays(),并误以为它代表总天数。
实际上它只是扣除年和月后的“剩余天数”。若要获取总天数,请使用 ChronoUnit.DAYS.between()。
错误 №5:比较不同类型的对象。
方法 isBefore、isAfter、isEqual 只适用于比较相同类型的对象(例如 LocalDate 对 LocalDate)。
GO TO FULL VERSION