Java中java.util和java.time新旧时间日期API的日期计算实战
作者:tianyu234
Java中日期计算工具类主要分为新旧两大阵营,本文全面对比新旧API,详解Period、Duration、TemporalAdjusters等现代工具,帮你轻松搞定日期加减、间隔计算和日期调整,彻底告别Calendar的坑
在 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) |
到此这篇关于Java中Period、Duration、ChronoUnit、TemporalAdjusters日期时间API的实战指南的文章就介绍到这了,更多相关Java日期时间API内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
