一、 “黑历史”:Date
与 Calendar
的深坑
在拥抱新世界之前,我们必须了解旧世界的“坑”在哪里。
1. java.util.Date
的“原罪”
- 名不副实:
Date
类名暗示它代表一个“日期”,但它实际上是一个精确到毫秒的时间戳,代表自1970-01-01T00:00:00Z
(UTC)以来的毫秒数。它包含了时间信息,却叫Date
。 - 可变性 (Mutability):
Date
对象是可变的!它的setYear()
,setMonth()
,setDate()
等方法会直接修改对象本身。
Date date1 = new Date();
Date date2 = date1; // 引用同一个对象
date2.setYear(123); // 意外修改了date1!
System.out.println(date1); // 输出被修改后的值
- 后果: 在多线程环境下极易引发数据错乱,不是线程安全的。
- 糟糕的设计: 月份从0开始(0=1月,11=12月),日期从1开始,这种不一致性是无数Bug的根源。
- 时区处理混乱:
Date
内部存储的是UTC时间戳,但其toString()
方法会根据JVM的默认时区进行格式化,容易造成误解。
2. java.util.Calendar
的“补丁”与新坑
Calendar
被设计用来弥补Date
的不足,但它本身问题更多。
- 复杂且笨重: API极其繁琐。获取年份需要
calendar.get(Calendar.YEAR)
。 - 依然是可变的: 和
Date
一样,set
和add
方法会修改自身。 - 创建成本高:
Calendar.getInstance()
是一个重量级操作,不应在循环中频繁调用。 - 线程不安全: 多个线程共享一个
Calendar
实例是灾难性的。
结论: 在新项目中,应彻底避免使用 Date
和 Calendar
进行业务逻辑处理。 它们仅应在与旧API(如JDBC)交互时作为“适配器”使用。
二、 现代曙光:java.time
包 (JSR-310)
Java 8引入的java.time
包是日期时间处理的银弹。它基于不可变对象设计,API清晰,功能强大。
核心类概览
类 | 用途 | 是否有时区 | 示例 |
| 时间线上的一个瞬时点,精确到纳秒。类似旧的 | ❌ (UTC) |
|
| 本地日期时间,没有时区概念。常用于数据库中的 | ❌ |
|
| 带时区的日期时间。处理跨时区业务的首选。 | ✅ |
|
| 带偏移量的日期时间。偏移量是固定的(如+08:00),不包含时区规则(如夏令时)。 | ✅ |
|
| 仅日期,无时间无时区。 | ❌ |
|
| 仅时间,无日期无时区。 | ❌ |
|
| 时区ID,如 | ||
| 时间量,基于时间的(秒、纳秒)。用于 |
| |
| 时间量,基于日期的(年、月、日)。用于 |
|
核心实践:java.time
API 实战
- 创建与获取当前时间
// 获取当前时间
LocalDateTime now = LocalDateTime.now(); // 系统默认时区
LocalDateTime nowInUtc = LocalDateTime.now(ZoneOffset.UTC); // UTC
ZonedDateTime nowWithZone = ZonedDateTime.now(); // 包含时区信息
// 从字符串解析
LocalDateTime dt = LocalDateTime.parse("2023-10-27T10:15:30");
LocalDate date = LocalDate.parse("2023-10-27");
// 构造特定时间
LocalDateTime specific = LocalDateTime.of(2023, 10, 27, 10, 15, 30);
- 格式化与解析 (DateTimeFormatter)
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String formatted = now.format(formatter); // LocalDateTime -> String
LocalDateTime parsed = LocalDateTime.parse("2023-10-27 10:15:30", formatter); // String -> LocalDateTime
常用预定义格式器: DateTimeFormatter.ISO_LOCAL_DATE
, DateTimeFormatter.BASIC_ISO_DATE
。