duration适用于精确秒级时间差计算,period专用于年月日层级的日期差;前者基于纳秒线性差值,后者依赖日历规则;选择依据是问题本质:“多少秒”用duration,“几年几月几天”用period。

Duration 适合算两个时间点之间的精确秒级差值
Duration 是 Java 8+ java.time 包中用于表示“基于时间的量”的类,它只关心纳秒、秒、毫秒等时间单位,不涉及日历系统。这意味着它适合计算两个 Instant、LocalDateTime 或 ZonedDateTime 之间相差多少秒、分钟、小时——但要注意:如果用在带时区或夏令时切换的时间上,结果仍是纯线性差值,不会自动处理 DST 跳变。
常见错误现象:Duration.between(start, end) 对 LocalDate 报 UnsupportedTemporalTypeException,因为 LocalDate 没有时间信息,无法转成纳秒精度的时序量。
- 用
Duration前确认两个对象都实现了Temporal且含完整时间(如Instant、LocalDateTime) - 若输入是
LocalDate,先转成当天零点的LocalDateTime再计算,否则会抛异常 - 注意
Duration的toDays()等方法返回的是整数截断值(如 36.9 小时 →toHours() == 36),需要小数精度时用toNanos()自行换算
示例:
LocalDateTime start = LocalDateTime.of(2024, 1, 1, 10, 0); LocalDateTime end = LocalDateTime.of(2024, 1, 1, 12, 30); Duration d = Duration.between(start, end); // 得到 PT2H30M System.out.println(d.toMinutes()); // 输出 150
Period 专用于计算年月日层级的日期差(不含时间)
Period 表示“基于日历的量”,它只处理年、月、日三个字段,背后依赖 ChronoUnit 和当前日历规则(比如二月天数、闰年)。它不能用于 Instant 或纯时间类型,只能作用于 LocalDate、YearMonth 等日期类对象。
常见错误现象:对 LocalDateTime 直接调用 Period.between() 会静默忽略时间部分,只比对日期部分(如 2024-01-01T23:59 和 2024-01-02T00:01 算出来是 P1D,而非你预期的 “几乎一天但差两分钟”)。
- 想算“从某日到某日过了几个自然月”,用
Period.between(LocalDate, LocalDate) - 如果原始数据含时间(如
LocalDateTime),必须先用.toLocalDate()显式剥离时间,避免隐式截断带来歧义 -
Period的getMonths()返回的是“剩余月份数”,不是总月数;完整差值需组合getYears()、getMonths()、getDays()
示例:
LocalDate start = LocalDate.of(2023, 1, 15); LocalDate end = LocalDate.of(2024, 3, 20); Period p = Period.between(start, end); // P1Y2M5D System.out.println(p.getYears()); // 1 System.out.println(p.getMonths()); // 2 System.out.println(p.getDays()); // 5
别混用 —— Instant / LocalDateTime 与 LocalDate 的语义鸿沟很关键
很多人卡在“为什么 Duration.between(a, b) 和 Period.between(a, b) 结果完全不同”,根本原因不是 API 设计问题,而是输入类型错配导致语义丢失。
- 把
LocalDateTime当作“日期”传给Period,它会丢掉时间但不报错,结果看似合理实则掩盖了精度损失 - 把
LocalDate强转成Instant(比如用atStartOfDay().atZone(...).toInstant())再喂给Duration,看似可行,但时区选择会影响结果(例如Asia/Shanghai和UTC下同一天的Instant值不同) - 跨夏令时边界计算
Duration仍准确(因它是纯线性差),但Period在这种场景下完全不适用——它根本不处理时间
判断依据很简单:你要回答的问题是“过了多少秒?”还是“过了几年几个月几天?”——前者选 Duration,后者选 Period,中间没有模糊地带。
实际业务中容易被忽略的边界情况
真实系统里最常出问题的不是语法,而是对“日期差”本身的定义模糊。比如“用户注册满 30 天可领取奖励”,这里的“30 天”到底是:
-
Duration.ofDays(30)(严格 720 小时后触发)? - 还是
Period.ofDays(30)(注册日 + 30 个自然日,即LocalDate.plusDays(30))?
前者受服务器时钟漂移、NTP 同步影响;后者受日历规则影响(比如 1 月 31 日加一个月是 2 月 28/29 日)。更隐蔽的是:有些需求其实是“自然月”,该用 Period.ofMonths(1),而不是简单加 30 天。
另一个坑是 Period 的 normalized() 方法:它会把 P13M 转成 P1Y1M,但默认不调用;如果你依赖 getMonths() 做逻辑判断(比如“是否满 12 个月”),忘了归一化就会漏判。










