明天同一时刻,与再过24小时:Period 和 Duration 不能互换
“一天以后到期”听起来明确,落到代码却至少有两种含义:明天当地同一钟点,或者从现在再经过86400秒。在夏令时切换附近,它们会得到不同的Instant。
一项按自然日排期的提醒,和一项有效期24小时的令牌,不该因为都叫oneDay就共用同一段加法。差别不在API风格,而在计时依据。
以Europe/Berlin的2025年切换日期为例,下面从当地中午开始分别加Period的一天和Duration的一天。JDK 17内置时区规则负责解释具体偏移,结果同时打印当地钟点与真实经过小时数。
import java.time.*;
public class CalendarDay {
static void show(String label, LocalDate date) {
ZoneId zone = ZoneId.of("Europe/Berlin");
ZonedDateTime start = date.atTime(12, 0).atZone(zone);
ZonedDateTime calendar = start.plus(Period.ofDays(1));
ZonedDateTime fixed = start.plus(Duration.ofDays(1));
System.out.println(label + "_period=" + calendar.toLocalTime() + ",hours="
+ Duration.between(start.toInstant(), calendar.toInstant()).toHours());
System.out.println(label + "_duration=" + fixed.toLocalTime() + ",hours="
+ Duration.between(start.toInstant(), fixed.toInstant()).toHours());
}
public static void main(String[] args) {
show("spring", LocalDate.of(2025, 3, 29));
show("autumn", LocalDate.of(2025, 10, 25));
}
}
保存为 CalendarDay.java,执行 java CalendarDay.java。在本次Corretto 17.0.15环境核对到:
spring_period=12:00,hours=23
spring_duration=13:00,hours=24
autumn_period=12:00,hours=25
autumn_duration=11:00,hours=24
春天少一小时,秋天多一小时
三月样本跨过夏令时开始。Period把日期推进一天,并尽量保留当地中午,真实只经过23小时;Duration则严格增加24小时,落到下午一点。
十月样本相反。当地中午到下一天中午经过25小时,固定24小时则落到上午十一点。两个结果都符合各自定义,不能看哪一个“更接近一天”就判断库出错。
这里选择中午,是为了让实验把日长差异单独显露出来,避免开始时刻本身落在缺口或重叠钟点上。若排期要求每天凌晨某个可能不存在的时刻,还需要额外决定调整或拒绝策略,不能从本例直接推断所有当地钟点都可用。
哪种业务选哪条时间轴
令牌从签发后有效24小时、网络租约持续30秒,通常适合按Instant和Duration计算截止点。展示时再转为用户时区,不让当地钟表调整改变已约定的持续长度。
门店每个营业日中午处理、账单在次日当地同一时刻提醒,则更接近日历与ZoneId语义。排期需要保存当地日期时间和适用时区,必要时还应记录处理夏令时缺口与重叠的选择。只保存一个没有时区的LocalDateTime,无法唯一确定执行时刻。
“日终到期”又是第三个常见约定。它不等于创建时间加一个Period;需要按照业务时区求相应日期边界,并明确是当日结束还是次日开始。以半开区间表示截止点,通常比手拼23:59:59.999更容易保持精度一致。
转成毫秒以后,不能再假装知道原日历规则
如果把Period的一天提前换成固定毫秒再保存,日历语义已经丢失。反过来,从某两天实际相差的毫秒推断“所有天都是这个长度”,也会把一次切换日的例外扩展成错误规则。
数据库字段和接口文档应直接说明:值代表一个Instant、一个Duration,还是某时区的本地排期。统一用long传输不等于统一了含义,字段名叫expireTime也不足以说明单位与时间轴。
时区规则还会随政策和JDK时区数据更新。涉及长期未来排期时,需要决定规则更新后是否按新的当地政策重新解释,还是保留已计算的Instant。这个决定应出现在排期模型里,而不是由某次服务器升级无声改变。
用两次切换测试,别只跑一个普通日期
测试应覆盖没有切换的普通日、春季23小时日和秋季25小时日。断言同时核对当地钟点与Instant之间的差,而不只检查日期加了一。
本例的日期是明确历史规则样本,不宣称所有国家都有夏令时,也不要求没有夏令时的业务为了形式引入复杂度。真正需要的是将需求中的“一天”翻译为可判断的定义;一旦定义选定,Period和Duration就分别是很直接的表达工具。