Calendar 是 Java 早期处理日期和时间的一个核心类,它位于 java.util 包中,虽然现在 Java 8 引入了更现代、更易用的 java.time 包,但 Calendar 仍然在许多遗留代码中被广泛使用,因此理解它非常重要。

Calendar 类的核心思想
Calendar 是一个抽象类,它不表示一个具体的时间点,而是代表一个“日历系统”,它提供了很多方法来操作日期和时间的各个部分(如年、月、日、时、分、秒等)。
获取 Calendar 实例
由于 Calendar 是抽象类,我们不能直接 new 它,必须使用其静态工厂方法 getInstance() 来获取一个实例。
import java.util.Calendar;
public class CalendarExample {
public static void main(String[] args) {
// 获取一个表示当前日期和时间的 Calendar 实例
Calendar calendar = Calendar.getInstance();
System.out.println("当前 Calendar 对象: " + calendar);
// 输出示例: 当前 Calendar 对象: java.util.GregorianCalendar[time=1678886400000,areFieldsSet=true,areAllFieldsSet=true,lenient=true,zone=sun.util.calendar.ZoneInfo[id="Asia/Shanghai",offset=28800000,dstSavings=0,useDaylight=false,transitions=235,lastRule=null],firstDayOfWeek=1,minimalDaysInFirstWeek=1,ERA=1,YEAR=2025,MONTH=2,WEEK_OF_YEAR=11,WEEK_OF_MONTH=2,DAY_OF_MONTH=14,DAY_OF_YEAR=74,DAY_OF_WEEK=3,DAY_OF_WEEK_IN_MONTH=2,AM_PM=0,HOUR=10,HOUR_OF_DAY=10,MINUTE=40,SECOND=0,MILLISECOND=0,ZONE_OFFSET=28800000,DST_OFFSET=0]
}
}
从输出可以看到,Calendar 对象内部包含了非常丰富的信息,包括时间戳、各个时间字段的值、时区等。
Calendar 的重要字段
Calendar 使用一组常量来表示日期和时间的各个部分,这些常量是 Calendar 类的静态成员。

| 常量 | 含义 | 备注 |
|---|---|---|
YEAR |
年 | |
MONTH |
月 | 重要:从 0 开始计数 (0=一月, 1=二月, ..., 11=十二月) |
DAY_OF_MONTH |
月中的第几天 | (1-31) |
DAY_OF_YEAR |
年中的第几天 | (1-366) |
HOUR |
小时 (12小时制) | (0-11) |
HOUR_OF_DAY |
小时 (24小时制) | (0-23) |
MINUTE |
分钟 | (0-59) |
SECOND |
秒 | (0-59) |
MILLISECOND |
毫秒 | (0-999) |
AM_PM |
上午/下午 | Calendar.AM (0) 或 Calendar.PM (1) |
DAY_OF_WEEK |
星期几 | 重要:从 1 开始计数 (1=星期日, 2=星期一, ..., 7=星期六) |
WEEK_OF_YEAR |
年中的第几周 | |
WEEK_OF_MONTH |
月中的第几周 |
常用操作
获取日期时间字段
使用 get(int field) 方法来获取指定字段的值。
Calendar calendar = Calendar.getInstance();
// 获取年
int year = calendar.get(Calendar.YEAR);
System.out.println("年份: " + year); // 输出: 2025
// 获取月 (注意:需要 +1)
int month = calendar.get(Calendar.MONTH) + 1;
System月份: " + month); // 输出: 3 (如果当前是三月)
// 获取日
int day = calendar.get(Calendar.DAY_OF_MONTH);
System.out.println("日: " + day); // 输出: 14
// 获取小时 (24小时制)
int hour = calendar.get(Calendar.HOUR_OF_DAY);
System.out.println("小时: " + hour);
// 获取星期几 (注意:1是星期日)
int dayOfWeek = calendar.get(Calendar.DAY_OF_WEEK);
System.out.println("星期几 (1=星期日): " + dayOfWeek);
设置日期时间
使用 set(int field, int value) 方法来设置某个字段的值。
Calendar calendar = Calendar.getInstance();
// 设置年为 2025
calendar.set(Calendar.YEAR, 2025);
// 设置月为 9 (代表十月)
calendar.set(Calendar.MONTH, 9);
// 设置日为 1
calendar.set(Calendar.DAY_OF_MONTH, 1);
// 也可以一次性设置年月日
// calendar.set(2025, Calendar.OCTOBER, 1);
System.out.println("设置后的日期: " + calendar.getTime());
// 输出: 设置后的日期: Wed Oct 01 10:40:00 CST 2025
时间计算 (核心功能)
Calendar 最强大的功能之一就是进行时间的加减操作。
add(int field, int amount): 在指定字段上增加或减少一个值,同时会向上或向下进位。- 在
MONTH字段上加 1,如果当前是 12 月,年份会自动加 1,月份变为 1 月。
- 在
roll(int field, int amount): 在指定字段上增加或减少一个值,但不会影响更大的时间单位。- 在
MONTH字段上加 1,如果当前是 12 月,年份不会改变,月份会变成 1 月(类似于循环)。
- 在
示例:add() 方法

Calendar calendar = Calendar.getInstance();
System.out.println("当前时间: " + calendar.getTime());
// 增加 3 个月
calendar.add(Calendar.MONTH, 3);
System.out.println("增加3个月后: " + calendar.getTime());
// 减少 5 天
calendar.add(Calendar.DAY_OF_MONTH, -5);
System.out.println("减少5天后: " + calendar.getTime());
// 增加 1 年
calendar.add(Calendar.YEAR, 1);
System.out.println("增加1年后: " + calendar.getTime());
示例:roll() 方法
Calendar calendar = Calendar.getInstance();
// 假设当前是 2025年10月31日
calendar.set(2025, Calendar.OCTOBER, 31);
System.out.println("初始时间: " + calendar.getTime()); // Tue Oct 31 10:40:00 CST 2025
// roll 增加 3 个月 (年份不会变)
calendar.roll(Calendar.MONTH, 3);
System.out.println("roll增加3个月后: " + calendar.getTime()); // Tue Jan 31 10:40:00 CST 2025
// 月份从10月 -> 1月,但年份仍然是2025
获取 Date 对象
Calendar 和 java.util.Date 可以互相转换。
calendar.getTime(): 将Calendar对象转换为Date对象。calendar.setTime(Date date): 用一个Date对象来设置Calendar。
Calendar calendar = Calendar.getInstance();
Date date = calendar.getTime();
System.out.println("从 Calendar 获取的 Date: " + date);
// 输出: 从 Calendar 获取的 Date: Tue Mar 14 10:40:00 CST 2025
Calendar 的主要缺点
正是因为 Calendar 存在一些明显的设计缺陷,Java 8 才推出了全新的 java.time API。
- 月份从 0 开始:这是最常见的“陷阱”,开发者很容易忘记
MONTH字段需要+1才能得到实际的月份。 - 星期几从 1 开始 (星期日):与人们的日常习惯(周一为第一天)不符,容易混淆。
- 可变性:
Calendar对象是可变的,一旦创建,你就可以直接修改它的状态,这在并发环境下是不安全的,也使得代码难以维护。 - API 设计笨拙:很多操作(如加减时间)需要理解
add和roll的细微差别,API 不够直观。 - 线程不安全:
Calendar实例不能在多线程环境中安全共享。
与 java.time API 的对比 (现代推荐)
如果你使用的是 Java 8 或更高版本,强烈推荐使用 java.time 包中的类,它们更直观、更安全、功能更强大。
| 功能 | java.util.Calendar |
java.time (推荐) |
|---|---|---|
| 表示日期 | Calendar |
LocalDate |
| 表示时间 | Calendar |
LocalTime |
| 表示日期时间 | Calendar |
LocalDateTime |
| 获取当前时间 | Calendar.getInstance() |
LocalDateTime.now() |
| 获取年/月/日 | get(Calendar.YEAR) |
getYear() / getMonthValue() / getDayOfMonth() |
| 设置日期 | set(year, month, day) |
of(year, month, day) 或 withYear() |
| 时间计算 | add(field, amount) |
plusDays(1), minusMonths(2) 等,非常直观 |
| 不可变性 | 可变 | 不可变 (线程安全) |
| 月份 | 0-11 | 1-12 (符合直觉) |
| 星期 | 1-7 (1=周日) | 1-7 (1=周一, 7=周日) |
java.time 示例:
import java.time.LocalDateTime;
import java.time.LocalDate;
public class ModernTimeExample {
public static void main(String[] args) {
// 获取当前日期时间
LocalDateTime now = LocalDateTime.now();
System.out.println("当前日期时间: " + now);
// 获取年月日,API非常清晰
int year = now.getYear();
int month = now.getMonthValue(); // 直接是 1-12
int day = now.getDayOfMonth();
System.out.printf("年: %d, 月: %d, 日: %d%n", year, month, day);
// 创建特定日期
LocalDate date = LocalDate.of(2025, 10, 1);
System.out.println("特定日期: " + date);
// 时间计算,API非常直观
LocalDateTime future = now.plusDays(5).minusMonths(1);
System.out.println("5天后减去1个月: " + future);
}
}
| 特性 | java.util.Calendar |
java.time (Java 8+) |
|---|---|---|
| 状态 | 可变 | 不可变 |
| 线程安全 | 不安全 | 线程安全 |
| API 直观性 | 较差,有陷阱 | 非常好,符合直觉 |
| 月份 | 0-11 | 1-12 |
| 星期 | 1-7 (1=周日) | 1-7 (1=周一) |
| 推荐场景 | 维护旧代码 | 所有新项目 |
- 学习
Calendar:为了理解和维护 Java 8 之前的旧代码。 - 使用
java.time:进行所有新的日期时间开发,它是 Java 未来的标准,也是更优的选择。
