在 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 对比总结
| 日期间隔 | 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
答案(选中下方空白区域查看):
总结(终极速查表)
| 日期加减(年/月/日) | 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


