源本科技 | 码上会

Java 日期时间:传统 API

2026/01/30
47
0

学习目标

  • 掌握 java.util.Datejava.util.Calendar 的基本用法

  • 熟练使用 SimpleDateFormat 进行日期格式化与解析

  • 理解传统日期时间 API 在格式化方面的缺陷与线程安全问题

正文内容

DateCalendar

早期的日期表示

在 Java 8 之前,开发者主要依赖 java.util.Datejava.util.Calendar 处理日期和时间。

  • Date 表示一个特定的瞬间(精确到毫秒),但其大部分方法已被弃用。

  • Calendar 是一个抽象类,提供了对年、月、日、时、分、秒等字段的操作能力。

// 使用 Date 获取当前时间
Date now = new Date();
System.out.println("当前时间(Date): " + now);

// 使用 Calendar 获取结构化日期
Calendar cal = Calendar.getInstance();
int year = cal.get(Calendar.YEAR);
int month = cal.get(Calendar.MONTH) + 1; // 注意:月份从 0 开始!
int day = cal.get(Calendar.DAY_OF_MONTH);
System.out.println("今天是:" + year + " 年 " + month + " 月 " + day + " 日");

重要提示:Calendar.MONTH 的值范围是 0(January)到 11(December),这是初学者常犯的错误来源。

日期格式化

SimpleDateFormat 的使用

由于 Date.toString() 输出格式固定且不可控,Java 提供了 java.text.SimpleDateFormat 来实现自定义格式化。

格式化:将 Date 转为字符串

import java.text.SimpleDateFormat;
import java.util.Date;

public class DateFormatExample {
    public static void main(String[] args) {
        Date now = new Date();
        SimpleDateFormat formatter = new SimpleDateFormat("yyyy 年 MM 月 dd 日 HH:mm:ss");
        String formatted = formatter.format(now);
        System.out.println("格式化后的时间:" + formatted);
    }
}

常用模式符号

符号

含义

示例

yyyy

四位年份

2026

MM

两位月份

01, 12

dd

两位日期

05, 30

HH

24 小时制小时

09, 23

mm

分钟

07, 59

ss

03, 45

E

星期几

周五

z

时区

CST, GMT+8

解析:将字符串转为 Date

try {
    String input = "2026 年 01 月 30 日 15:30:00";
    SimpleDateFormat parser = new SimpleDateFormat("yyyy 年 MM 月 dd 日 HH:mm:ss");
    Date parsedDate = parser.parse(input);
    System.out.println("解析结果:" + parsedDate);
} catch (ParseException e) {
    System.err.println("日期格式不匹配!");
}

注意:解析时若字符串与模式不一致,会抛出 ParseException,必须处理异常。

SimpleDateFormat 缺陷

SimpleDateFormat 不是线程安全的。在多线程环境中共享同一个实例可能导致解析错误或格式混乱。

// 危险示例:多个线程共用一个 formatter
SimpleDateFormat unsafe = new SimpleDateFormat("yyyy-MM-dd");

// 安全做法 1:每次创建新实例(性能较差)
// 安全做法 2:使用 ThreadLocal
private static final ThreadLocal<SimpleDateFormat> FORMATTER =
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

此外,SimpleDateFormat 对无效日期默认“宽容”处理(如 2026-02-30 会被自动调整为 2026-03-02),需调用 setLenient(false) 关闭此行为:

SimpleDateFormat strict = new SimpleDateFormat("yyyy-MM-dd");
strict.setLenient(false); // 严格模式

传统 API 的整体局限性

  • 可变性DateCalendar 可被修改,易引发并发 bug。

  • API 设计混乱:年份从 1900 开始计算(Date.getYear() 返回 126 表示 2026 年),月份从 0 开始。

  • 格式化工具不安全SimpleDateFormat 非线程安全,且错误处理机制薄弱。

  • 缺乏时区与国际化深度支持

重点总结

  • SimpleDateFormat 是 Java 8 前唯一的标准格式化工具,但存在线程安全和容错性问题。

  • 使用时务必注意月份从 0 开始、年份偏移等陷阱。

  • 在多线程场景中,应避免共享 SimpleDateFormat 实例,推荐使用 ThreadLocal 或迁移到 Java 8 新 API。

  • 传统日期时间体系已过时,新项目应优先采用 java.time 包。

思考题

  1. 为什么 SimpleDateFormat 不是线程安全的?其内部哪些状态可能导致并发问题?

  2. 如果你在一个高并发 Web 应用中必须使用 SimpleDateFormat,有哪些可行的优化方案?

  3. 尝试用 SimpleDateFormat 解析 "2026-02-30",观察结果。如何确保输入日期必须合法?