Java传统日期时间API实战:Date、Calendar与DateFormat的演进与避坑指南
2026/7/16 22:57:43 网站建设 项目流程

1. Java传统日期时间API概述

如果你在2014年之前写过Java程序,肯定对java.util.Date这个类不陌生。那时候我们要获取当前时间,通常会这样写:

Date now = new Date(); System.out.println(now); // 输出:Tue Jun 25 14:30:45 CST 2024

这个输出看起来还算直观,但当你真正开始处理日期计算、格式化等操作时,就会发现这套API设计得非常反人类。我在早期项目中踩过不少坑,比如:

  1. 月份从0开始计数(1月是0,12月是11)
  2. 年份从1900年开始计算
  3. Date对象既包含日期又包含时间,但时间处理非常麻烦
  4. 线程安全问题让人头疼

后来JDK 1.1引入了CalendarDateFormat试图解决这些问题,但它们又带来了新的复杂性。直到Java 8才推出了全新的java.time包,官方也正式将Date/Calendar标记为"不推荐使用"。

2. Date类:简单但问题重重

2.1 Date的基本用法

Date类最常用的两个构造方法:

// 无参构造:获取当前时间 Date now = new Date(); // 带long参数构造:根据毫秒数创建日期 Date specificTime = new Date(0L); // 1970-01-01 08:00:00 (东八区)

这里有个坑:Date的toString()输出格式是固定的英文格式,而且时区显示为CST(China Standard Time),这在需要本地化显示时会很麻烦。

2.2 常见陷阱

陷阱1:年份计算

Date date = new Date(124, 5, 25); // 2024年6月25日

注意这里的年份参数124表示2024(1900+124),月份参数5表示6月(0-based)。这种反直觉的设计很容易出错。

陷阱2:可变性

Date date = new Date(); long time = date.getTime(); date.setTime(time + 1000); // 修改了原始对象

Date对象是可变的,这在多线程环境下非常危险。我在一个Web项目中就遇到过因为共享Date对象导致的线程安全问题。

3. DateFormat:格式化与解析

3.1 SimpleDateFormat使用

由于Date的toString()格式固定,我们通常用SimpleDateFormat来格式化:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); String formatted = sdf.format(new Date()); // 输出:2024-06-25 14:30:45

3.2 线程安全问题

重要警告:SimpleDateFormat不是线程安全的!下面的代码在多线程环境下会出问题:

// 错误的共享实例写法 private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd"); // 正确做法是每个线程创建独立实例,或者使用ThreadLocal private static final ThreadLocal<SimpleDateFormat> threadLocalSdf = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

我曾经在Spring MVC项目中使用共享的SimpleDateFormat实例,结果在高并发时出现了日期解析错乱的问题。

3.3 解析严格性

默认情况下,SimpleDateFormat的解析是宽松的:

SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); Date date = sdf.parse("2024-02-30"); // 会自动转换为2024-03-01

如果希望严格校验日期有效性,需要设置:

sdf.setLenient(false); // 这时解析"2024-02-30"会抛出ParseException

4. Calendar类:更强大的日期操作

4.1 Calendar基础

Calendar解决了Date的一些设计缺陷,提供了更丰富的日期字段操作:

Calendar cal = Calendar.getInstance(); int year = cal.get(Calendar.YEAR); int month = cal.get(Calendar.MONTH) + 1; // 仍然要+1 int day = cal.get(Calendar.DAY_OF_MONTH);

4.2 日期计算

Calendar的add方法可以方便地进行日期加减:

Calendar cal = Calendar.getInstance(); cal.add(Calendar.DAY_OF_MONTH, 7); // 加7天 cal.add(Calendar.MONTH, -1); // 减1个月

4.3 时区处理

Calendar比Date更好的时区支持:

Calendar cal = Calendar.getInstance(); cal.setTimeZone(TimeZone.getTimeZone("America/New_York"));

5. 新旧API对比与迁移建议

虽然Java 8已经推出多年,但很多遗留系统仍然在使用这些传统API。以下是迁移建议:

  1. Date → Instant

    // 旧代码 Date date = new Date(); // 新代码 Instant instant = Instant.now();
  2. Calendar → ZonedDateTime

    // 旧代码 Calendar cal = Calendar.getInstance(); // 新代码 ZonedDateTime zdt = ZonedDateTime.now();
  3. SimpleDateFormat → DateTimeFormatter

    // 旧代码 SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); // 新代码 DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd");

如果你正在维护老系统,理解这些传统API的设计缺陷和正确用法仍然非常重要。特别是在面试中,面试官经常会考察你对这些历史问题的理解程度。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询