欢迎光临
我们一直在努力

Java 日期时间 API :Period、Duration、ChronoUnit、TemporalAdjusters 日期计算实战

在 Java 中,日期计算的工具类按“新旧阵营”主要分为以下几类:

类别所属阵营类名核心用途
老 API(遗留) java.util Calendar 万能日期计算(加减、比较、获取字段),但设计臃肿、线程不安全、易用性差
新 API(推荐) java.time Period 日期(年/月/日) 级别的间隔计算
Duration 时间(时/分/秒/纳秒) 级别的间隔计算
TemporalAdjusters 日期调整器(如:获取某个月的最后一天、下个周一)
ChronoUnit 任意时间单位之间的间隔计算(DAYS、MONTHS、HOURS 等)
LocalDateTime/LocalDate 自带方法 plusDays()、minusMonths()、isAfter()、until()

🚀 黄金选型原则:

  • 新项目:全部使用 java.time(Period、Duration、TemporalAdjusters、ChronoUnit)
  • 遗留项目:能迁就迁,绝不要在新代码里用 Calendar

一、老 API:Calendar(为什么它该被淘汰?)

Calendar 是 Java 1.1 时代为了解决 Date 的缺陷而引入的,但它本身又带来了新的问题。

1. 基础使用

import java.util.Calendar;

// 获取当前时间
Calendar cal = Calendar.getInstance();
System.out.println(cal.getTime()); // Tue Aug 18 14:30:25 CST 2026

// 获取各字段
int year = cal.get(Calendar.YEAR); // 2026
int month = cal.get(Calendar.MONTH) + 1; // 8(月份从 0 开始,必须 +1)
int day = cal.get(Calendar.DAY_OF_MONTH); // 18
int hour = cal.get(Calendar.HOUR_OF_DAY); // 14(24 小时制)
int minute = cal.get(Calendar.MINUTE); // 30

// 日期运算(可变)
cal.add(Calendar.DAY_OF_MONTH, 10); // +10 天
cal.add(Calendar.MONTH, 2); // -2 月
cal.set(Calendar.YEAR, 2025); // 直接设置年份

2. Calendar 的设计缺陷

问题说明
月份从 0 开始 1 月 = 0,12 月 = 11,容易出错
可变性 add()、set() 直接修改原对象,容易造成隐蔽 Bug
线程不安全 多个线程共享一个 Calendar 实例会数据错乱
API 臃肿 get(Calendar.YEAR) 这种写法又长又丑,IDE 提示不友好
时区混乱 默认 JVM 时区,跨时区计算容易踩坑

😱 结论:Calendar 已被官方标记为“legacy”,新项目绝对不要用。

二、新 API:Period(日期级别的间隔)

Period 用于计算年、月、日级别的间隔。不涉及时间(时/分/秒)。

1. 基础使用

import java.time.LocalDate;
import java.time.Period;

LocalDate start = LocalDate.of(2024, 1, 1);
LocalDate end = LocalDate.of(2026, 8, 18);

// 计算间隔
Period period = Period.between(start, end);
System.out.println(period.getYears()); // 2
System.out.println(period.getMonths()); // 7
System.out.println(period.getDays()); // 17

// 获取总月数(不常用)
long totalMonths = period.toTotalMonths(); // 31(2年7个月 = 31个月)

// 创建 Period(用于加减)
Period oneYear = Period.ofYears(1);
Period twoMonths = Period.ofMonths(2);
Period tenDays = Period.ofDays(10);
Period oneYearTwoMonths = Period.of(1, 2, 10); // 1年2个月10天

// 日期加减
LocalDate future = start.plus(oneYear); // 2025-01-01
LocalDate past = end.minus(tenDays); // 2026-08-08

2. 注意事项

// ⚠️ Period.between() 不是简单的“总天数”相减
// 它按“年→月→日”分别计算,受月份天数影响

LocalDate start = LocalDate.of(2026, 1, 31);
LocalDate end = LocalDate.of(2026, 2, 28);
Period p = Period.between(start, end);
System.out.println(p.getMonths()); // 0
System.out.println(p.getDays()); // 28(1月31日 → 2月28日,不是按 28 天算)

📌 使用场景:计算年龄(年/月/日)、合同剩余期限、会员有效期。

三、新 API:Duration(时间级别的间隔)

Duration 用于计算时、分、秒、纳秒级别的间隔。不涉及日期(年/月/日)。

1. 基础使用

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.LocalTime;

LocalDateTime start = LocalDateTime.of(2026, 8, 18, 10, 0, 0);
LocalDateTime end = LocalDateTime.of(2026, 8, 18, 14, 30, 25);

// 计算间隔
Duration duration = Duration.between(start, end);
System.out.println(duration.toDays()); // 0
System.out.println(duration.toHours()); // 4(小时数)
System.out.println(duration.toMinutes()); // 270(分钟数)
System.out.println(duration.getSeconds()); // 15025(总秒数)
System.out.println(duration.toMillis()); // 15025000(总毫秒数)
System.out.println(duration.toNanos()); // 15025000000000(总纳秒数)

// 创建 Duration
Duration fiveHours = Duration.ofHours(5);
Duration thirtyMinutes = Duration.ofMinutes(30);
Duration tenSeconds = Duration.ofSeconds(10);
Duration fiveHoursThirtyMin = Duration.of(5, ChronoUnit.HOURS)
.plus(Duration.ofMinutes(30));

// 时间加减
LocalTime time = LocalTime.of(10, 0);
LocalTime later = time.plus(fiveHours); // 15:00:00
LocalDateTime dt = LocalDateTime.now().minus(thirtyMinutes); // 当前时间 – 30 分钟

2. 重要注意事项

// ⚠️ Duration 不能处理 年/月/日(会抛异常)
// ❌ 错误
Duration.between(LocalDate.of(2026,1,1), LocalDate.of(2026,8,18));
// 编译报错:无法应用于 LocalDate

// ✅ 正确:使用 Period 处理日期
Period.between(LocalDate.of(2026,1,1), LocalDate.of(2026,8,18));

// ✅ 如果必须用 Duration,先转 LocalDateTime
LocalDateTime start = LocalDate.of(2026,1,1).atStartOfDay();
LocalDateTime end = LocalDate.of(2026,8,18).atStartOfDay();
Duration d = Duration.between(start, end);
System.out.println(d.toDays()); // 229(总天数)

📌 使用场景:计算任务执行耗时(方法耗时)、倒计时、两个时间点之间的精确时间差。

四、新 API:ChronoUnit(任意时间单位)

ChronoUnit 是一个枚举,提供了标准化的时间单位,可以用来在任意单位之间计算间隔。

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;

LocalDateTime start = LocalDateTime.of(2026, 8, 18, 10, 0);
LocalDateTime end = LocalDateTime.of(2026, 8, 20, 14, 30);

// 按不同单位计算差值
long days = ChronoUnit.DAYS.between(start, end); // 2
long hours = ChronoUnit.HOURS.between(start, end); // 52
long minutes = ChronoUnit.MINUTES.between(start, end); // 3150
long seconds = ChronoUnit.SECONDS.between(start, end); // 189000
long millis = ChronoUnit.MILLIS.between(start, end); // 189000000

// 可用的单位
ChronoUnit.YEARS // 年
ChronoUnit.MONTHS // 月
ChronoUnit.WEEKS // 周
ChronoUnit.DAYS // 天
ChronoUnit.HOURS // 小时
ChronoUnit.MINUTES // 分钟
ChronoUnit.SECONDS // 秒
ChronoUnit.MILLIS // 毫秒
ChronoUnit.MICROS // 微秒
ChronoUnit.NANOS // 纳秒

// 日期加减(用指定单位)
LocalDateTime future = start.plus(3, ChronoUnit.DAYS); // 加 3 天
LocalDateTime past = end.minus(5, ChronoUnit.HOURS); // 减 5 小时

💡 ChronoUnit vs Period/Duration:

  • Period/Duration:适合预先定义好间隔对象,然后多次复用(如 Duration.ofHours(2))
  • ChronoUnit:适合一次性计算,或不确定用哪种单位时更灵活

五、新 API:TemporalAdjusters(日期调整器)

这是 java.time 中最强大的日期计算工具之一,专门用于将日期调整到某个特定的目标。

import java.time.LocalDate;
import java.time.DayOfWeek;
import java.time.temporal.TemporalAdjusters;

LocalDate today = LocalDate.of(2026, 8, 18); // 2026-08-18(周二)

// 获取本月最后一天
LocalDate lastDayOfMonth = today.with(TemporalAdjusters.lastDayOfMonth());
System.out.println(lastDayOfMonth); // 2026-08-31

// 获取本月第一天
LocalDate firstDayOfMonth = today.with(TemporalAdjusters.firstDayOfMonth());
System.out.println(firstDayOfMonth); // 2026-08-01

// 获取下个周一
LocalDate nextMonday = today.with(TemporalAdjusters.next(DayOfWeek.MONDAY));
System.out.println(nextMonday); // 2026-08-24

// 获取上个周五
LocalDate previousFriday = today.with(TemporalAdjusters.previous(DayOfWeek.FRIDAY));
System.out.println(previousFriday); // 2026-08-14

// 获取下个月的第一个周日
LocalDate firstSunday = today.with(TemporalAdjusters.firstInMonth(DayOfWeek.SUNDAY));
System.out.println(firstSunday); // 2026-08-02

// 获取当月的第 3 个周四
LocalDate thirdThursday = today.with(TemporalAdjusters.dayOfWeekInMonth(3, DayOfWeek.THURSDAY));
System.out.println(thirdThursday); // 2026-08-20

// 获取明天的开始时间(00:00:00)
LocalDateTime tomorrowStart = today.plusDays(1).atStartOfDay();
System.out.println(tomorrowStart); // 2026-08-19T00:00

常用 TemporalAdjusters 方法速查

方法说明
firstDayOfMonth() 当月第一天
lastDayOfMonth() 当月最后一天
firstDayOfYear() 当年第一天
lastDayOfYear() 当年最后一天
next(DayOfWeek) 下一个指定星期几
previous(DayOfWeek) 上一个指定星期几
firstInMonth(DayOfWeek) 当月第一个指定星期几
lastInMonth(DayOfWeek) 当月最后一个指定星期几
dayOfWeekInMonth(int, DayOfWeek) 当月第 N 个指定星期几

六、新 API:LocalDate/LocalDateTime 自带方法(最常用)

其实日常开发中,最常用的日期计算方法就是这些类自己的方法,上面那些工具类是辅助:

LocalDate today = LocalDate.now();

// ✅ 加减日期(最常用)
LocalDate tomorrow = today.plusDays(1);
LocalDate yesterday = today.minusDays(1);
LocalDate nextMonth = today.plusMonths(1);
LocalDate lastYear = today.minusYears(1);

// ✅ 比较(最常用)
boolean isAfter = today.isAfter(LocalDate.of(2026, 1, 1));
boolean isBefore = today.isBefore(LocalDate.of(2027, 1, 1));
boolean isEqual = today.isEqual(LocalDate.of(2026, 8, 18));

// ✅ 获取时间差(until 方法)
long daysUntil = today.until(LocalDate.of(2027, 1, 1), ChronoUnit.DAYS);
Period period = today.until(LocalDate.of(2027, 1, 1));

七、新旧 API 对比总结

功能老 API(遗留)新 API(推荐)
日期间隔 Calendar(不推荐) Period(年月日)
时间间隔 Calendar(不推荐) Duration(时分秒)
任意单位间隔 TimeUnit(有限支持) ChronoUnit(全面支持)
日期调整(下周一、月底) Calendar(手动计算,繁琐) TemporalAdjusters(一行搞定)
日期加减 Calendar.add()(可变) plusXxx() / minusXxx()(不可变)
线程安全 ❌ 不安全 ✅ 全部不可变、线程安全

八、生产环境完整示例

import java.time.*;
import java.time.format.DateTimeFormatter;
import java.time.temporal.TemporalAdjusters;

public class DateCalcUtils {
private static final DateTimeFormatter FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");

// ===== 计算两个日期之间的差值(年月日)=====
public static Period betweenDates(LocalDate start, LocalDate end) {
return Period.between(start, end);
}

// ===== 计算两个时间之间的差值(时分秒)=====
public static Duration betweenTimes(LocalDateTime start, LocalDateTime end) {
return Duration.between(start, end);
}

// ===== 判断是否在同一天 =====
public static boolean isSameDay(LocalDateTime d1, LocalDateTime d2) {
return d1.toLocalDate().isEqual(d2.toLocalDate());
}

// ===== 获取当月的第一天 00:00:00 =====
public static LocalDateTime getFirstDayOfMonth() {
return LocalDate.now().with(TemporalAdjusters.firstDayOfMonth()).atStartOfDay();
}

// ===== 获取当月的最后一天 23:59:59 =====
public static LocalDateTime getLastDayOfMonth() {
return LocalDate.now().with(TemporalAdjusters.lastDayOfMonth())
.atTime(LocalTime.MAX); // 23:59:59.999999999
}

// ===== 计算年龄(周岁)=====
public static int getAge(LocalDate birthDate) {
return Period.between(birthDate, LocalDate.now()).getYears();
}

// ===== 获取两个时间点之间的毫秒数 =====
public static long getMillisBetween(LocalDateTime start, LocalDateTime end) {
return Duration.between(start, end).toMillis();
}

// ===== 格式化为字符串 =====
public static String format(LocalDateTime dt) {
return dt.format(FORMATTER);
}
}

九、思考题

// 问题1:下面代码分别输出什么?
LocalDate start = LocalDate.of(2024, 1, 31);
LocalDate end = LocalDate.of(2024, 2, 28);
Period p = Period.between(start, end);
System.out.println(p.getMonths()); // A
System.out.println(p.getDays()); // B

// 问题2:计算“当前时间 + 3天2小时30分”,用哪些类?
LocalDateTime now = LocalDateTime.now();
// 请写出代码

// 问题3:下面代码输出什么?
LocalDate today = LocalDate.of(2026, 8, 18);
LocalDate lastDay = today.with(TemporalAdjusters.lastDayOfMonth());
System.out.println(lastDay.getDayOfWeek()); // C

答案(选中下方空白区域查看):

  • A 输出 0,B 输出 28(因为 1月31日 → 2月28日,跨越1个月,但不满一个月,按天数算 28 天)。
  • LocalDateTime future = now.plusDays(3).plusHours(2).plusMinutes(30); 或 LocalDateTime future = now.plus(3, ChronoUnit.DAYS).plus(Duration.ofHours(2).plusMinutes(30));
  • 输出 MONDAY(2026-08-31 是周一)
  • 总结(终极速查表)

    场景推荐类一句话示例
    日期加减(年/月/日) LocalDate.plusXxx() today.plusMonths(2)
    时间加减(时/分/秒) LocalDateTime.plusXxx() now.plusHours(3)
    日期间隔(年/月/日) Period Period.between(start, end)
    时间间隔(时/分/秒) Duration Duration.between(start, end)
    任意单位间隔 ChronoUnit ChronoUnit.DAYS.between(start, end)
    调整到下周一/月底 TemporalAdjusters date.with(TemporalAdjusters.next(MONDAY))
    计算年龄 Period Period.between(birth, now).getYears()
    计算方法耗时 Duration Duration.between(startTime, endTime).toMillis()
    计算两个日期相差的天数 ChronoUnit.DAYS ChronoUnit.DAYS.between(start, end)

    💬 互动话题:你用过 Calendar 踩过哪些坑(比如月份从 0 开始、add 的可变性等)?有没有迁移到 java.time 后“真香”的经历?欢迎评论区分享!🚀

    发布日期:2026-08-21

    赞(0)
    未经允许不得转载:171主机测评 » Java 日期时间 API :Period、Duration、ChronoUnit、TemporalAdjusters 日期计算实战
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址