杰瑞科技汇

Java日期Calendar如何正确使用与处理?

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 的主要缺点

  1. 月份和星期从 0/1 开始get(Calendar.MONTH) + 1 这种写法非常容易出错,也降低了代码的可读性。
  2. API 设计笨拙:方法名不够直观,addroll 的区别。
  3. 可变性Calendar 对象是可变的,在多线程环境下共享同一个实例是不安全的。
  4. 性能问题Calendar 的实例化相对较慢。

java.time 包 (新 API - 强烈推荐)

由于 CalendarDate 的种种问题,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);

Calendarjava.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.Calendarjava.util.Date
  • 与旧 API 交互:如果你需要将 java.time 的对象转换为旧的 Date 对象,或者反之,可以使用 java.util.Datejava.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 的所有痛点,还提供了更强大、更优雅的解决方案。

分享:
扫描分享到社交APP
上一篇
下一篇