Calendar
Calendar 是什么?
Calendar 是 JDK 1.1 引入的抽象类,用来替代 Date 的日期计算能力。它比 Date 稍微好一点,但也好不到哪去——月份依然从 0 开始、非线程安全、设计混乱。
为什么先讲它?因为老项目里可能还在用,了解它才能理解新 API 有多好。
基本使用
创建实例
java
// 使用静态工厂方法
Calendar cal = Calendar.getInstance();
// 返回的是 GregorianCalendar(阳历)
// 手动指定
Calendar cal2 = Calendar.getInstance(TimeZone.getTimeZone("America/New_York"));常用字段
Calendar 用常量表示各个字段:
java
Calendar cal = Calendar.getInstance();
// 年
cal.get(Calendar.YEAR); // 2026
// 月(坑:从 0 开始)
cal.get(Calendar.MONTH); // 2(三月),不是二月
cal.get(Calendar.MARCH); // 用常量更清晰,但值一样是 2
// 日
cal.get(Calendar.DAY_OF_MONTH); // 22
cal.get(Calendar.DAY_OF_WEEK); // 1(周日)
cal.get(Calendar.DAY_OF_YEAR); // 今天是第几天
// 时分秒
cal.get(Calendar.HOUR_OF_DAY); // 24小时制
cal.get(Calendar.HOUR); // 12小时制
cal.get(Calendar.MINUTE);
cal.get(Calendar.SECOND);设置值
java
Calendar cal = Calendar.getInstance();
// 单独设置某个字段
cal.set(Calendar.YEAR, 2026);
cal.set(Calendar.MONTH, Calendar.MARCH); // 用常量,不容易搞错
cal.set(Calendar.DAY_OF_MONTH, 22);
// 一次性设置
cal.set(2026, Calendar.MARCH, 22);
// 带时分秒
cal.set(2026, Calendar.MARCH, 22, 14, 30, 0);计算日期
java
Calendar cal = Calendar.getInstance();
// 加减日期
cal.add(Calendar.DAY_OF_MONTH, 7); // 7 天后
cal.add(Calendar.MONTH, -1); // 1 个月前
// 获取月末
Calendar endOfMonth = Calendar.getInstance();
endOfMonth.set(Calendar.DAY_OF_MONTH,
endOfMonth.getActualMaximum(Calendar.DAY_OF_MONTH));
// 获取月初
Calendar startOfMonth = Calendar.getInstance();
startOfMonth.set(Calendar.DAY_OF_MONTH, 1);常用场景
月初和月末
java
// 月初
Calendar start = Calendar.getInstance();
start.set(Calendar.DAY_OF_MONTH, 1);
start.set(Calendar.HOUR_OF_DAY, 0);
start.set(Calendar.MINUTE, 0);
start.set(Calendar.SECOND, 0);
start.set(Calendar.MILLISECOND, 0);
// 月末
Calendar end = Calendar.getInstance();
end.set(Calendar.DAY_OF_MONTH,
end.getActualMaximum(Calendar.DAY_OF_MONTH));
end.set(Calendar.HOUR_OF_DAY, 23);
end.set(Calendar.MINUTE, 59);
end.set(Calendar.SECOND, 59);
end.set(Calendar.MILLISECOND, 999);
// 转 Date
Date startDate = start.getTime();
Date endDate = end.getTime();日期间隔
java
public static long daysBetween(Date start, Date end) {
Calendar c1 = Calendar.getInstance();
c1.setTime(start);
c1.set(Calendar.HOUR_OF_DAY, 0);
c1.set(Calendar.MINUTE, 0);
c1.set(Calendar.SECOND, 0);
c1.set(Calendar.MILLISECOND, 0);
Calendar c2 = Calendar.getInstance();
c2.setTime(end);
c2.set(Calendar.HOUR_OF_DAY, 0);
c2.set(Calendar.MINUTE, 0);
c2.set(Calendar.SECOND, 0);
c2.set(Calendar.MILLISECOND, 0);
return (c2.getTimeInMillis() - c1.getTimeInMillis()) / (1000 * 60 * 60 * 24);
}判断闰年
java
public static boolean isLeapYear(int year) {
Calendar cal = Calendar.getInstance();
cal.set(year, Calendar.DECEMBER, 31);
return cal.getActualMaximum(Calendar.DAY_OF_YEAR) == 366;
}线程安全问题
Calendar 是非线程安全的:
java
public class UnsafeCalendar implements Runnable {
private static Calendar shared = Calendar.getInstance(); // 坑!
@Override
public void run() {
shared.set(Calendar.DAY_OF_MONTH, 22);
// 并发时会被其他线程覆盖
System.out.println(shared.getTime());
}
}解决方案一:每次创建新实例
java
public Date getNextWeek() {
Calendar cal = Calendar.getInstance(); // 每个线程用独立的实例
cal.add(Calendar.DAY_OF_MONTH, 7);
return cal.getTime();
}解决方案二:ThreadLocal
java
private static final ThreadLocal<Calendar> calendarThreadLocal =
ThreadLocal.withInitial(() -> Calendar.getInstance());
public static Calendar getThreadLocalCalendar() {
return calendarThreadLocal.get();
}与新 API 的对比
| 操作 | Calendar | JDK 8+ LocalDate |
|---|---|---|
| 创建 | Calendar.getInstance() | LocalDate.now() |
| 设置月 | set(Calendar.MONTH, 2) | withMonth(3) |
| 加减 | add(Calendar.DAY, 7) | plusDays(7) |
| 月末 | getActualMaximum() | lengthOfMonth() |
| 线程安全 | 否 | 是 |
| 月份起点 | 0(一月) | 3(三月) |
Calendar 月份从 0 开始是最大的坑:
java
Calendar cal = Calendar.getInstance();
// 这是三月,不是二月!
cal.set(Calendar.MONTH, 2); // 3 月
// 要设置二月应该这样:
cal.set(Calendar.MONTH, Calendar.FEBRUARY); // 用常量
// 或者
cal.set(Calendar.MONTH, 1); // 2 月,但谁记得住?总结
Calendar 的问题:
- 月份从 0 开始:最大的坑,容易出错
- 非线程安全:共享实例会出问题
- API 混乱:Date 和 Calendar 职责不清
- 使用繁琐:计算月末都要绕一圈
建议:新代码直接用 JDK 8+ 的 LocalDate / LocalDateTime,Calendar 只用于维护旧系统。
