Period在Java中仅适用于LocalDate,按年、月、日逐级计算日历差,不支持时区和跨日历,结果需手动取绝对值,且无法处理月末等边界情况。

Period 在 Java 中不能直接计算任意两个日期的「年月天」差
Java 的 Period 类不是万能的日期差计算器。它只对 LocalDate 有效,且内部按「日历单位」逐级减法(先算年、再算月、再算日),不考虑时区、夏令时或跨日历系统(比如从 2023-02-28 到 2024-02-29)。如果你传入 LocalDateTime 或 ZonedDateTime,会编译报错或抛 DateTimeException。
常见错误现象:Period.between(ld1, ld2) 返回负值(比如 -1 年 11 个月 30 天)——这不是 bug,是它严格按“被减数减减数”顺序计算的结果,但业务上往往需要绝对差值。
- 必须用
LocalDate:哪怕你有完整时间戳,也得先用.toLocalDate()截断,否则编译不通过 - 结果不可逆:
period.addTo(date)不一定等于原日期,因为 2 月没有 30 日等边界情况会导致回滚 - 不处理时分秒:哪怕你传入
LocalDateTime.now().toLocalDate(),也完全丢失精度,别指望它能算出“还剩 14 小时”
正确用法:先对齐类型,再调用 between(),最后手动取绝对值
假设你要算 2022-03-15 到 2024-07-22 的年月天差,就得老老实实走三步:
LocalDate start = LocalDate.of(2022, 3, 15); LocalDate end = LocalDate.of(2024, 7, 22); Period period = Period.between(start, end); // 得到 P2Y4M7D int years = Math.abs(period.getYears()); int months = Math.abs(period.getMonths()); int days = Math.abs(period.getDays());
注意:Period 自身不提供「总天数」,要算总天数得换用 ChronoUnit.DAYS.between(start, end),两者语义不同:Period 是日历感知的(考虑月份天数差异),ChronoUnit.DAYS 是纯线性计数。
- 如果 start 晚于 end,
Period.between()返回负Period,各字段都为负,所以必须用Math.abs()—— 它不会自动帮你转成正向表达 - 不要用
period.toString()解析字符串来取值,格式不稳定(如 P1Y 可能不带 M/D) - 避免链式调用
Period.between(a, b).normalized():它只归一化月数(把 13 个月转成 1 年 1 个月),但不会调整年/日字段去补偿,意义有限
替代方案:Joda-Time 的 Years.monthsDaysFrom() 已废弃,别踩坑
老项目里可能见过 Joda-Time 的 Years.monthsDaysFrom(),但它在 Java 8+ 迁移后已彻底废弃,且行为和 Period 不一致(比如对 2023-01-31 到 2023-03-03 会返回 1 个月 +2 天,而 Period 返回 1 个月 +3 天)。现在强行引入 Joda-Time 只会增加维护成本和兼容性风险。
- 别为了“看起来更准”而降级依赖,
Period是官方推荐标准,问题在于你是否理解它的设计前提 - 如果业务真要求“精确到小时分钟”,必须放弃
Period,改用Duration(针对Instant)或自己写逻辑拆解ChronoUnit各单位差值 - Android 开发者注意:低于 API 26 的设备不支持
java.time,需用ThreeTenABP,但Period行为与标准版一致
容易被忽略的边界:2 月 29 日、跨闰年、月末对齐
最常翻车的是这类计算:Period.between(LocalDate.of(2023, 1, 31), LocalDate.of(2024, 1, 31)) → 正常返回 P1Y;但换成 LocalDate.of(2023, 1, 31) 到 LocalDate.of(2024, 2, 29),结果是 P1Y1M?错,是 P1Y0M29D —— 因为 2024-02-29 存在,而 2023-02-29 不存在,Period 不会“智能对齐到月末”,它就按字面日期减。
- 2023-01-31 到 2024-02-28:结果是 P1Y0M28D(不是 P1Y1M-1D)
- 2023-01-31 到 2024-03-01:结果是 P1Y1M1D(不是 P1Y1M0D)
- 所有“月末日”参与计算时,务必手动验证,别依赖直觉
这种边界行为没法绕开,只能接受——Period 的设计目标就是日历单位差,不是“人类口语中觉得合理”的差。

















