java.util.Calendar 类 (旧 API)
Calendar 是 Java 1.1 引入的一个抽象类,用于替换有诸多问题的 Date 类,它提供了更丰富的日期和时间操作功能,比如获取/设置年、月、日、时、分、秒,以及进行时间的加减等。
为什么需要 Calendar?
java.util.Date 类存在很多问题:
- 它包含日期和时间,但没有时区信息,容易导致混淆。
- 它的方法(如
getYear(),getMonth())已被废弃,因为它从 1900 年开始计算年份,从 0 开始计算月份,非常不直观。 - 它不是线程安全的。
Calendar 类旨在解决这些问题,提供更强大、更灵活的日期操作。
如何获取 Calendar 实例?
Calendar 是一个抽象类,不能直接 new,必须通过其静态工厂方法 getInstance() 来获取一个实例。
import java.util.Calendar;
// 获取一个 Calendar 实例,默认使用当前时间和默认时区
Calendar calendar = Calendar.getInstance();
// 也可以指定时区
// Calendar calendar = Calendar.getInstance(TimeZone.getTimeZone("America/New_York"));
核心方法
a. 获取日期/时间字段
Calendar 使用一组常量来表示不同的日期字段,这些常量定义在 Calendar 类中。
| 常量 | 含义 | 注意事项 |
|---|---|---|
YEAR |
年 | 4 位数,如 2025 |
MONTH |
月 | 从 0 开始 (0=一月, 1=二月, ..., 11=十二月) |
DAY_OF_MONTH |
月中的天 | 1-31 |
HOUR_OF_DAY |
24小时制的小时 | 0-23 |
HOUR |
12小时制的小时 | 0-11 |
MINUTE |
分钟 | 0-59 |
SECOND |
秒 | 0-59 |
DAY_OF_WEEK |
一周中的天 | 周日是 1,周一是 2,...,周六是 7 |
AM_PM |
上午/下午 | Calendar.AM (0) 或 Calendar.PM (1) |
Calendar calendar = Calendar.getInstance();
int year = calendar.get(Calendar.YEAR);
// 注意:月份需要 +1 才是实际的月份
int month = calendar.get(Calendar.MONTH) + 1;
int day = calendar.get(Calendar.DAY_OF_MONTH);
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int minute = calendar.get(Calendar.MINUTE);
System.out.println("当前时间: " + year + "年" + month + "月" + day + "日 " + hour + ":" + minute);
b. 设置日期/时间
使用 set() 方法可以修改 Calendar 对象表示的时间。
Calendar calendar = Calendar.getInstance();
// 设置时间为 2025年10月26日 15:30:00
// 注意:月份是 10,因为是从 0 开始的,所以传入 10 代表 11月
calendar.set(2025, 10, 26, 15, 30, 0);
System.out.println("设置后的时间: " + calendar.getTime());
c. 时间计算
Calendar 提供了 add() 和 roll() 方法来修改时间。
add(field, amount): 在指定字段上增加或减少一个值。它会向上进位或向下借位。将月份加 1,如果当前是 12 月,年份会自动加 1。
roll(field, amount): 在指定字段上增加或减少一个值。它不会向上进位或向下借位。将月份加 1,如果当前是 12 月,会变成 1 月,但年份不会变。
Calendar calendar = Calendar.getInstance();
calendar.set(2025, Calendar.NOVEMBER, 31); // 2025年11月31日(这个日期不存在,Calendar会自动调整到12月1日)
System.out.println("初始日期: " + calendar.getTime()); // 2025-12-01 10:00:00 CST
// 使用 add: 月份加 1
calendar.add(Calendar.MONTH, 1);
System.out.println("add 后的日期: " + calendar.getTime()); // 2025-01-01 10:00:00 CST (年份变了)
// 重新设置日期
calendar.set(2025, Calendar.NOVEMBER, 30);
System.out.println("重新设置日期: " + calendar.getTime()); // 2025-11-30 10:00:00 CST
// 使用 roll: 月份加 1
calendar.roll(Calendar.MONTH, 1);
System.out.println("roll 后的日期: " + calendar.getTime()); // 2025-12-30 10:00:00 CST (年份没变)
Calendar 的主要缺点
- 月份和星期从 0/1 开始:
get(Calendar.MONTH) + 1这种写法非常容易出错,也降低了代码的可读性。 - API 设计笨拙:方法名不够直观,
add和roll的区别。 - 可变性:
Calendar对象是可变的,在多线程环境下共享同一个实例是不安全的。 - 性能问题:
Calendar的实例化相对较慢。
java.time 包 (新 API - 强烈推荐)
由于 Calendar 和 Date 的种种问题,Java 8 引入了全新的 java.time 包,这个 API 设计更优秀、更安全、更易用,是现在和未来 Java 开发中进行日期时间操作的首选。
java.time 包包含以下几个核心类:
| 类 | 描述 |
|---|---|
LocalDate |
表示一个日期(年、月、日),不包含时间。 |
LocalTime |
表示一个时间(时、分、秒、纳秒),不包含日期。 |
LocalDateTime |
表示一个日期和时间,但没有时区信息。 |
ZonedDateTime |
表示一个带有时区的日期和时间。 |
Instant |
表示一个时间戳,与 Unix 时间戳(从 1970-01-01 00:00:00 UTC 开始)类似。 |
DateTimeFormatter |
用于格式化和解析日期时间对象。 |
LocalDate, LocalTime, LocalDateTime
这三个是不可变的、线程安全的类。
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.format.DateTimeFormatter;
// 获取当前日期
LocalDate today = LocalDate.now();
System.of.println("今天的日期: " + today); // 2025-10-26
// 获取当前时间
LocalTime now = LocalTime.now();
System.out.println("现在的时间: " + now); // 10:15:30.123
// 获取当前日期和时间
LocalDateTime nowDateTime = LocalDateTime.now();
System.out.println("当前的日期和时间: " + nowDateTime); // 2025-10-26T10:15:30.123
// 创建特定的日期
LocalDate birthday = LocalDate.of(1990, 5, 15);
System.out.println("生日: " + birthday); // 1990-05-15
// 创建特定的时间
LocalTime specificTime = LocalTime.of(14, 30, 0);
System.out.println("特定时间: " + specificTime); // 14:30
// 日期时间的加减 (不可变性,返回新对象)
LocalDateTime tomorrow = nowDateTime.plusDays(1);
LocalDateTime lastMonth = nowDateTime.minusMonths(1);
System.out.println("明天的这个时候: " + tomorrow);
System.out.println("上个月的这个时候: " + lastMonth);
// 获取年月日 (API 直观,无需 +1)
int year = nowDateTime.getYear();
int month = nowDateTime.getMonthValue(); // 使用 getValue() 直接得到 1-12
int day = nowDateTime.getDayOfMonth();
System.out.println("年: " + year + ", 月: " + month + ", 日: " + day);
ZonedDateTime (处理时区)
import java.time.ZoneId;
import java.time.ZonedDateTime;
// 获取带时区的当前时间
ZonedDateTime nowInShanghai = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
ZonedDateTime nowInNewYork = ZonedDateTime.now(ZoneId.of("America/New_York"));
System.out.println("上海时间: " + nowInShanghai);
System.out.println("纽约时间: " + nowInNewYork);
// 时区转换
ZonedDateTime tokyoTime = nowInShanghai.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
System.out.println("东京时间: " + tokyoTime);
DateTimeFormatter (格式化)
import java.time.format.DateTimeFormatter;
// 定义格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss");
// 格式化
String formattedDateTime = nowDateTime.format(formatter);
System.out.println("格式化后的日期时间: " + formattedDateTime); // 2025年10月26日 10:15:30
// 解析
String dateStr = "2025-02-29";
LocalDate parsedDate = LocalDate.parse(dateStr);
System.out.println("解析后的日期: " + parsedDate);
Calendar 与 java.time 的对比总结
| 特性 | java.util.Calendar |
java.time (Java 8+) |
|---|---|---|
| API 设计 | 笨拙,方法名不直观 | 清晰、一致、方法名直观 |
| 月份/星期 | 月份从 0 开始,星期从 1 开始 | 月份从 1 开始 (getMonthValue()),星期枚举 (DayOfWeek.MONDAY) |
| 可变性 | 可变,线程不安全 | 不可变,线程安全 |
| 时区处理 | 复杂,容易出错 | 简单、明确 (ZonedDateTime) |
| 核心类 | Calendar |
LocalDate, LocalTime, LocalDateTime, ZonedDateTime |
| 格式化 | SimpleDateFormat (线程不安全) |
DateTimeFormatter (线程安全) |
| 推荐度 | 不推荐,仅用于维护旧代码 | 强烈推荐,所有新项目的首选 |
何时使用哪个?
- 新项目开发 (Java 8+):绝对应该使用
java.time,它的设计更符合现代编程理念,能让你写出更健壮、更易读的代码。 - 维护旧项目 (Java 7 或更早):你只能使用
java.util.Calendar和java.util.Date。 - 与旧 API 交互:如果你需要将
java.time的对象转换为旧的Date对象,或者反之,可以使用java.util.Date和java.time之间的转换方法。
// java.time -> java.util.Date Date date = Date.from(Instant.now()); // java.util.Date -> java.time Instant instant = date.toInstant(); ZonedDateTime zonedDateTime = instant.atZone(ZoneId.systemDefault());
Calendar 是 Java 日期时间历史上的一个重要里程碑,但它已经过时,对于任何新的 Java 开发工作,请毫不犹豫地拥抱 java.time 包,它不仅解决了旧 API 的所有痛点,还提供了更强大、更优雅的解决方案。
