在处理日期和时间相关的业务时,计算两个时间点之间相差多少天、多少小时是绕不开的需求。Java 8引入的新时间API提供了两个专门用于表示差值的类:Period和Duration。这两个类名字看着相似,实际用途却差别很大。如果用错了,轻则计算结果不准确,重则直接抛出异常。本文将从原理、用法和常见坑三个层面,详细讲解如何用它们正确计算日期时间差。

一、Period与Duration的设计区别
要正确使用这两个类,首先要理解它们各自的设计定位。Period是基于日期的单位来表示时间量的,内部只存储三个字段:年(years)、月(months)、日(days),它完全不关心时分秒。而Duration是基于时间的单位,内部以秒和纳秒的形式存储时间量,更适合精确到时分秒甚至毫秒级别的计算。
这个设计差异决定了它们能配合的类也不同。Period只能和LocalDate这种纯日期类打交道,如果你试图对一个LocalDateTime调用until方法并传入ChronoUnit.YEARS以外的日期单位以外的参数时需要注意类型匹配。而Duration主要用于LocalTime、LocalDateTime和Instant之间的计算。简单记:算日期差用Period,算时间差用Duration。
还有一点容易被忽略:Period是"人类视角"的差值,比如"1年2个月3天",这个说法对人类很自然,但两个相差"1年2个月3天"的日期,实际跨过的天数可能不同(因为每个月天数不一样)。而Duration是"机器视角"的差值,就是纯粹的时间长度,一天固定按24小时算。理解了这一点,后面遇到的很多奇怪现象就都能解释了。
二、使用Period计算日期差
计算年龄是Period最典型的应用场景。假设需要计算某人从出生日期到今天的年龄,直接用Period.between就能得到准确的年月日差值:
import java.time.LocalDate;
import java.time.Period;
public class PeriodDemo {
public static void main(String[] args) {
LocalDate birthday = LocalDate.of(1995, 6, 15);
LocalDate today = LocalDate.now();
// 计算两个日期之间的差值
Period period = Period.between(birthday, today);
System.out.println("相差:" + period.getYears() + "年"
+ period.getMonths() + "个月"
+ period.getDays() + "天");
// 只关心总天数时,用ChronoUnit更方便
long totalDays = java.time.temporal.ChronoUnit.DAYS.between(birthday, today);
System.out.println("总共相差天数:" + totalDays);
}
}需要注意Period.between的参数顺序:第一个参数是开始时间,第二个参数是结束时间。如果开始时间在结束时间之后,得到的结果会是负数,这在校验"结束日期不能早于开始日期"的场景里反而可以利用,比如判断结果是否为负来给出提示。
另一个常见误区是试图用Period去计算LocalDateTime的差值。比如下面这段代码会直接抛出DateTimeException:
import java.time.LocalDateTime;
import java.time.Period;
public class WrongUsage {
public static void main(String[] args) {
LocalDateTime start = LocalDateTime.of(2024, 1, 1, 8, 0);
LocalDateTime end = LocalDateTime.of(2024, 3, 15, 18, 30);
// 编译能过,但运行时抛异常
// Period.between(start.toLocalDate(), end.toLocalDate()) 才是正确做法
Period p = Period.between(start.toLocalDate(), end.toLocalDate());
System.out.println(p.getMonths() + "个月" + p.getDays() + "天");
}
}因为Period无法表达时分秒部分,所以遇到LocalDateTime时,要么先转成LocalDate丢弃时分秒,要么改用Duration处理。两种取舍要看业务是否在意那几个小时几分钟。
三、使用Duration计算时间差
当需要计算任务执行耗时、会议持续时长这类精确到秒甚至毫秒的场景,就该Duration出场了。Duration提供了between静态方法,配合toHours、toMinutes、toMillis等方法可以灵活输出各种单位的时长:
import java.time.Duration;
import java.time.LocalDateTime;
public class DurationDemo {
public static void main(String[] args) {
LocalDateTime startTime = LocalDateTime.of(2024, 5, 1, 9, 0, 0);
LocalDateTime endTime = LocalDateTime.of(2024, 5, 1, 17, 30, 45);
Duration duration = Duration.between(startTime, endTime);
System.out.println("总小时数:" + duration.toHours());
System.out.println("总分钟数:" + duration.toMinutes());
System.out.println("总秒数:" + duration.getSeconds());
System.out.println("总毫秒数:" + duration.toMillis());
// 如果想输出 "8小时30分45秒" 这种形式,需要自己换算
long hours = duration.toHours();
long minutes = duration.toMinutes() % 60;
long seconds = duration.getSeconds() % 60;
System.out.println(hours + "小时" + minutes + "分" + seconds + "秒");
}
}Duration特别适合和Instant配合做性能统计。Instant代表一个精确的时间戳瞬间,用它统计代码执行时间比老的System.currentTimeMillis更语义化,而且天然支持纳秒精度:
import java.time.Duration;
import java.time.Instant;
public class TimingDemo {
public static void main(String[] args) {
Instant begin = Instant.now();
// 模拟一段业务逻辑
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
Instant finish = Instant.now();
System.out.println("耗时毫秒:" + Duration.between(begin, finish).toMillis());
}
}有一个细节要留意:Duration没有toDays以外的日期单位概念,它理解的一天永远是24小时。如果时间段跨越了夏令时切换,真实的日历天数和Duration.toDays的结果可能会差出一个小时对应的天数偏差。当然在国内没有夏令时,这个问题基本碰不到,但如果是国际化项目就必须心里有数。
四、常见坑与最佳实践
第一个坑是月份天数不固定带来的困惑。比如从1月31日到2月28日,用Period.between计算得到的是0个月28天,而不是1个月。因为从1月31日往后推一个月在2月没有31日这一天,API只能按实际规则归算。很多人预期"差不多一个月"应该算1个月,这就产生了认知偏差。遇到这类需求,更稳妥的做法是明确用ChronoUnit.DAYS.between拿总天数,或者和产品确认计费口径。
第二个坑是Duration和Period不能混用做加减。给LocalDate加一个Duration会抛UnsupportedTemporalTypeException,因为纯日期没有时分秒概念;反过来给LocalTime加Period也不行。写代码时记住类型匹配规则:LocalDate配Period,LocalDateTime、LocalTime、Instant配Duration。
最后给几条实践建议:第一,封装统一的工具方法,把差值计算收敛到一处,避免散落各处的Period和Duration误用;第二,输出给用户看的时长优先转换成"X小时Y分Z秒"这种人性化格式,机器存储则保留秒或毫秒;第三,涉及数据库存储时,Duration可以转成秒数存整型字段,Period建议拆成年月日三列,方便后续查询统计。掌握这些要点,日期时间差的计算就不再是难题了。