选 duration 还是 period 取决于问题类型:问“多少秒、小时”等物理时间用 duration;问“几岁、剩几年几个月”等日历概念用 period,二者语义不同,混用会导致错误。

选 Duration 还是 Period,关键看你要回答的问题类型:问“过了多少秒、分钟、小时”,用 Duration;问“几岁了”“合同还剩几年几个月”,用 Period。两者语义不同,混用会导致结果错得离谱,比如把 2 月 28 日加一个月算成 3 月 28 日(实际应是 3 月 28 日),但用 Duration 算就只会机械加 2592000 秒,完全忽略日历规则。
Duration:只认纳秒,不认日历
Duration 表示纯线性时间长度,内部由秒 + 纳秒构成,和时区、闰年、夏令时全无关系。它适合测量耗时、倒计时、超时控制这类“物理时间流逝”场景。
- 只能用于含完整时间信息的对象:Instant、LocalDateTime、LocalTime、ZonedDateTime;传入 LocalDate 会直接抛
UnsupportedTemporalTypeException - 若原始数据是 LocalDate,需先转为当天零点的 LocalDateTime:
date.atStartOfDay() -
toDays()、toHours()等方法返回整数截断值(如 47.9 小时 →toHours() == 47);要小数精度,得用toNanos()自行换算 - 跨夏令时边界(如 3 月10日 2:00 跳到 3:00)时,Duration 仍按固定秒数计算,不会自动补/减 1 小时
Period:按日历滚动,不是简单加减
Period 表达的是“在日历上往前或往后翻多少年、月、日”,背后依赖真实月份天数、闰年等规则。它不适用于带时间的类型——哪怕你传 LocalDateTime,Period 也会默默丢弃时分秒,只比对日期部分。
- 只接受 LocalDate、YearMonth 等纯日期类;对 LocalDateTime 调用
between()是合法的,但结果仅反映日期差,时间部分被静默忽略 - 想算两个带时间的时刻之间“自然月数”,必须先显式剥离时间:
dt1.toLocalDate()和dt2.toLocalDate() -
getMonths()返回的是“剩余月份数”,不是总月数;完整解读需组合getYears()、getMonths()、getDays() -
plus(Period.ofDays(30))不等于plusMonths(1);前者是日历上加 30 天,后者是“滚到下个月同日”,遇到 1 月 31 日加 1 个月会变成 2 月 28 日(非闰年)
别踩这些典型坑
很多问题表面像时间差,实则本质不同。选错类型,轻则结果难读,重则业务逻辑出错。
- 算“用户注册到今天一共多少天”?→ 用
ChronoUnit.DAYS.between(startDate, endDate)或Period.between().getDays(),别用 Duration 的toDays()(可能因时区或 DST 偏差) - 算“接口响应耗时 1234 毫秒”?→ 用 Duration,别用 Period(它没有毫秒概念)
- 算“生日还有几天”?→ 若指日历上的自然天数(比如 12 月 31 日到 1 月 1 日是 1 天),用 Period;若指精确到秒的倒计时(比如 23 小时 59 分 59 秒),用 Duration
- 跨时区比较(如用户手机时间 vs 服务器日志时间)?→ 必须先统一转成 Instant 或 ZonedDateTime,再用 Duration.between()
一句话总结怎么选
问“多少秒/毫秒/小时”,答案落在时间轴上,用 Duration;问“几岁/第几个月/合同还剩几年几月”,答案落在日历本上,用 Period。不复杂,但容易忽略语义差别。










