在Java 8引入的java.time包中,Period和Duration都用来表示一段时间间隔,但它们的设计目标和计算规则截然不同。Period面向“日历日期”概念,而Duration面向“物理时间”概念,选错类型会让业务计算结果出现隐蔽偏差。

一、Period:基于日期的日历间隔
Period用于表示以年、月、日为单位的时间间隔,它直接对应人类日历上的跨度。例如“间隔3年2个月15天”这种描述,用Period表达最自然。Period的底层存储就是三个整数字段:years、months、days,并不涉及小时分钟秒。
Period最常用的创建方式是通过静态工厂方法between,传入两个LocalDate对象。因为只处理日期,所以它完全不考虑当天的具体时刻,也不会因为闰年或大小月而产生“实际小时数”的变化,而是严格按日历推演。例如从1月31日加一个月,结果是2月28日(或29日),这符合生活常识而非物理时长。
import java.time.LocalDate;
import java.time.Period;
public class PeriodDemo {
public static void main(String[] args) {
LocalDate start = LocalDate.of(2023, 1, 31);
LocalDate end = LocalDate.of(2023, 3, 2);
// 计算两个日期之间的间隔
Period period = Period.between(start, end);
System.out.println(period.getYears()); // 0
System.out.println(period.getMonths()); // 1
System.out.println(period.getDays()); // 2
// 也可手动构造
Period custom = Period.of(1, 6, 10);
System.out.println(custom); // P1Y6M10D
}
}
从上面的代码可以看到,Period.between会拆分出年、月、日三个独立部分,而不是给出一个总的“天数”。如果业务需要按自然月对账、计算员工司龄、判断会员到期日,Period是最合适的工具。它的优点是与日历一致、可读性强;缺点是没法直接转成“总共多少小时”,也不适合精确计时。
二、Duration:基于时间的物理时长
Duration表示以秒和纳秒为单位的精确时间长度,它衡量的是机器意义上的时间流逝。无论是LocalTime、LocalDateTime还是Instant,只要包含时刻信息,都可以用Duration来计算间隔。Duration内部以“秒+纳秒”存储,能精确表达23小时59分这类跨天但不跨日历的概念。
与Period不同,Duration不关心月有多少天、是否有闰秒之类的日历规则,它只做线性数学。比如计算接口耗时、任务超时时间、两个时间戳之差,都必须用Duration。如果你错误地用Period去减带时刻的对象,编译器会直接报错,因为Period只接受LocalDate。
import java.time.LocalDateTime;
import java.time.Duration;
public class DurationDemo {
public static void main(String[] args) {
LocalDateTime t1 = LocalDateTime.of(2023, 1, 1, 8, 0, 0);
LocalDateTime t2 = LocalDateTime.of(2023, 1, 3, 10, 30, 0);
Duration duration = Duration.between(t1, t2);
System.out.println(duration.toDays()); // 2
System.out.println(duration.toHours()); // 50
System.out.println(duration.toMinutes()); // 3030
// 手动构造两小时三十分钟
Duration d = Duration.ofHours(2).plusMinutes(30);
System.out.println(d); // PT2H30M
}
}
Duration提供了toDays、toHours、toMillis等方法,可以方便地换算成不同粒度。它在调度系统、性能统计、缓存过期等场景中不可或缺。需要注意的是,Duration的toDays是“整除24小时”的结果,并不会考虑日历日界,因此和Period的getDays语义完全不一样。
三、核心差异对比与选型建议
很多初学者会把二者混用,导致“账单一算少了一天”或“超时判断偏长”的问题。下面用一张表总结关键区别:
| 对比维度 | Period | Duration |
|---|---|---|
| 时间单位 | 年、月、日 | 秒、纳秒 |
| 适用类型 | LocalDate | LocalTime、LocalDateTime、Instant |
| 日历感知 | 是,按历法推演 | 否,纯线性时长 |
| 典型场景 | 年龄、账期、自然月 | 耗时、超时、间隔秒数 |
选型时先问自己一个问题:我要的是“日历上过了几个月”还是“实际走了多少秒”?如果是前者,闭眼选Period;如果是后者,必须用Duration。在微服务里做token过期校验,用Duration.between(now, expireTime).isNegative()判断最稳妥;而计算用户注册满30天送券,则应用Period.between(regDate, today).getDays() >= 30结合月份处理。
另外要避免一个常见误区:试图把Period直接转成Duration。因为“一个月”在不同月份对应28到31天不等,Java没有提供这种转换,这正是二者本质不同的体现。如果真需要估算,只能按业务规则自行定义每月天数再乘Duration,但那已不属于API本身能力。
四、组合使用与注意事项
在复杂业务中,二者可以各司其职再组合。例如先以Period算出账单周期起止日期,再用Duration计算每天服务运行精确时长。书写代码时,推荐显式调用getYears、getMonths、toHours等语义明确的方法,不要依赖toString输出做解析,以免格式变化引发故障。
还有一点容易忽略:Period.normalized方法会把天数超过当月上限的部分进位到月,而Duration.truncatedTo可截断到指定精度。合理使用这些辅助方法,能让间隔计算更贴合真实需求,减少边界bug。
import java.time.Period;
public class NormalizeDemo {
public static void main(String[] args) {
Period p = Period.of(0, 1, 40); // 1个月零40天
System.out.println(p); // P1M40D
System.out.println(p.normalized()); // P2M9D或P2M10D视月份而定
}
}
总之,Period和Duration是Java时间库里两套平行的坐标系。理清日历间隔与物理间隔的边界,才能在日期计算、任务调度、时长统计中写出既准确又易维护的代码。