在Java开发中,处理日期和时间是常见的需求。Java的日期时间API历经演变,尤其是JDK 8的发布带来了全新的设计。本文将系统梳理JDK 8之前的核心API及其局限性,并详细讲解JDK 8引入的现代日期时间API的使用方法。
JDK 8之前的日期时间API
在Java 8之前,开发者主要依赖java.util包下的几个类进行日期时间操作,它们在设计上存在一些固有缺陷。
1. System.currentTimeMillis()
这是一个最基础的方法,用于获取当前时间与1970年1月1日0时0分0秒(UTC)之间的毫秒数差值,即时间戳。
- 返回值:
long类型的时间戳。
- 常用场景:用于计算时间间隔或生成唯一的时间戳ID。
2. java.util.Date 与 java.sql.Date
这是两个容易混淆的类,分别位于java.util和java.sql包中。
-
java.util.Date
用于表示特定的瞬间,精确到毫秒。但其大部分构造方法和方法已过时(deprecated)。
- 创建对象:
Date date = new Date(); // 基于当前系统时间
- 常用方法:
toString(): 输出可读的日期时间字符串(如 Tue Dec 02 08:03:24 CST 2025)。
getTime(): 返回对应的毫秒时间戳。
@Test
public void testUtilDate() {
Date date1 = new Date(); // 创建一个基于当前系统时间的Date对象
System.out.println(date1.toString()); // 输出: Tue Dec 02 08:03:24 CST 2025
long milliTimes = date1.getTime(); // 获取对应的时间戳
System.out.println(milliTimes); // 输出: 1764598448012
}
-
java.sql.Date
它是java.util.Date的子类,主要用于与数据库中的DATE类型字段交互,只包含年月日信息。
- 创建对象:
java.sql.Date date = new java.sql.Date(1764598448012L); // 基于指定时间戳
- 常用方法:
toString() 方法仅输出年月日(如 2025-12-01)。
@Test
public void testSqlDate() {
java.sql.Date date1 = new java.sql.Date(1764598448012L); // 基于时间戳创建
System.out.println(date1.toString()); // 输出: 2025-12-01
System.out.println(date1.getTime()); // 输出: 1764598448012
}

此类用于Date对象与格式化的字符串之间相互转换,即格式化与解析。
- 格式化 (Date → String):使用
format(Date date) 方法。
- 解析 (String → Date):使用
parse(String source) 方法,字符串必须符合指定的格式。
@Test
public void testSimpleDateFormat() throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat();
Date date1 = new Date();
// 格式化:日期 ----> 字符串
String strDate = sdf.format(date1);
System.out.println(strDate); // 输出默认格式,如: 2025/12/2 上午8:10
// 解析:字符串 ----> 日期
Date date2 = sdf.parse("2025/12/2 上午8:10");
System.out.println(date2); // 输出: Tue Dec 02 08:10:00 CST 2025
}
可以通过构造器 SimpleDateFormat(String pattern) 指定格式。
@Test
public void testSimpleDateFormatPattern() {
// 使用自定义格式
SimpleDateFormat sdf = new SimpleDateFormat("EEE, d MMM yyyy HH:mm:ss Z");
Date date1 = new Date();
String formattedStr = sdf.format(date1);
System.out.println(formattedStr); // 输出: 周二,2 12月 2025 08:20:46 +0800
// 更常见的格式
sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
formattedStr = sdf.format(date1);
System.out.println(formattedStr); // 输出: 2025-12-02 08:23:08
}

4. Calendar 类(日历类)
Calendar 是一个抽象类,提供了比Date更丰富的日期字段(年、月、日、时、分、秒等)操作。
- 实例化:通过静态方法
Calendar.getInstance() 获取子类实例(通常是GregorianCalendar)。
- 常用方法:
get(int field): 获取指定字段的值(如 Calendar.DAY_OF_MONTH)。
set(int field, int value): 设置字段值。
add(int field, int amount): 为字段添加(或减去)值。
getTime(): 将Calendar转换为Date。
setTime(Date date): 用Date对象设置Calendar的时间。
@Test
public void testCalendar() {
Calendar calendar = Calendar.getInstance();
System.out.println(calendar.getClass()); // 输出: class java.util.GregorianCalendar
// get(int field)
System.out.println(calendar.get(Calendar.DAY_OF_MONTH)); // 输出当月第几天,如: 2
System.out.println(calendar.get(Calendar.DAY_OF_YEAR)); // 输出当年第几天,如: 336
// set(int field, int value)
calendar.set(Calendar.DAY_OF_MONTH, 23);
System.out.println(calendar.get(Calendar.DAY_OF_MONTH)); // 输出: 23
// add(int field, int amount)
calendar.add(Calendar.DAY_OF_MONTH, 4);
System.out.println(calendar.get(Calendar.DAY_OF_MONTH)); // 输出: 27
// getTime(): Calendar ----> Date
Date date = calendar.getTime();
System.out.println(date); // 输出: Sat Dec 27 08:53:52 CST 2025
// setTime(Date date): 使用指定Date重置Calendar
Date date1 = new Date();
calendar.setTime(date1);
System.out.println(calendar.get(Calendar.DAY_OF_MONTH)); // 输出当前日期,如: 2
}

JDK 8 全新的日期时间 API
鉴于旧API存在的诸多问题(如可变性、偏移性、非线程安全、设计混乱等),Java 8 引入了全新的 java.time 包。这是现代Java应用处理日期时间的首选。
1. 旧API过时的主要原因
- 可变性:如
Date和Calendar是可变的,这在多线程环境下不安全。
- 偏移性:
Date的年份从1900开始,月份从0开始,不符合直觉。
- 设计缺陷:
SimpleDateFormat只能格式化Date,且本身非线程安全。
- 功能不足:无法很好地处理时区、闰秒等复杂场景。
2. LocalDate、LocalTime、LocalDateTime
这三个类是java.time包的核心,分别表示日期、时间、日期+时间。它们都是不可变且线程安全的。
- 实例化:
now(): 获取当前日期/时间。
of(...): 根据指定参数创建对象。
@Test
public void testLocalDateTime() {
// now():获取当前实例
LocalDate localDate = LocalDate.now();
LocalTime localTime = LocalTime.now();
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDate); // 输出: 2025-12-02
System.out.println(localTime); // 输出: 10:26:11.893155300
System.out.println(localDateTime); // 输出: 2025-12-02T10:26:11.893155300
// of():指定参数创建
LocalDate localDate1 = LocalDate.of(2025, 12, 2);
LocalTime localTime1 = LocalTime.of(10, 26, 11);
LocalDateTime localDateTime1 = LocalDateTime.of(2025, 12, 2, 10, 29, 12);
System.out.println(localDate1); // 输出: 2025-12-02
System.out.println(localTime1); // 输出: 10:26:11
System.out.println(localDateTime1); // 输出: 2025-12-02T10:29:12
}
- 常用方法 (体现不可变性):
getXxx(): 获取字段值。
withXxx(): 返回一个修改了指定字段的新对象,原对象不变。
plusXxx()/minusXxx(): 返回一个增加了或减少了时间的新对象。
@Test
public void testLocalDateTimeMethods() {
LocalDateTime localDateTime = LocalDateTime.now();
System.out.println(localDateTime.getDayOfMonth()); // 输出当前日,如: 2
// withXxx():设置,返回新对象
LocalDateTime localDateTime1 = localDateTime.withDayOfMonth(12);
System.out.println(localDateTime); // 原对象未变
System.out.println(localDateTime1); // 新对象日期已改为12号
System.out.println(localDateTime.getDayOfMonth()); // 输出: 2
System.out.println(localDateTime1.getDayOfMonth());// 输出: 12
// plusXxx():增加,返回新对象
LocalDateTime localDateTime2 = localDateTime1.plusDays(5);
System.out.println(localDateTime1.getDayOfMonth()); // 输出: 12
System.out.println(localDateTime2.getDayOfMonth()); // 输出: 17
}

3. Instant 类
Instant 用于表示时间线上的一个瞬时点(时间戳),类似于 java.util.Date,但精度可达纳秒。
- 实例化:
Instant.now(): 获取当前时刻(默认是格林威治时间)。
Instant.ofEpochMilli(long epochMilli): 根据毫秒时间戳创建。
- 常用方法:
toEpochMilli(): 获取对应的毫秒时间戳。
atOffset(ZoneOffset offset): 结合偏移量得到OffsetDateTime,用于处理时区。
@Test
public void testInstant() {
// now(): 格林威治时间
Instant instant = Instant.now();
System.out.println(instant); // 输出: 2025-12-02T02:50:50.636435400Z
// 调整为东八区时间
OffsetDateTime offsetDateTime = instant.atOffset(ZoneOffset.ofHours(8));
System.out.println(offsetDateTime); // 输出: 2025-12-02T10:53:24.552167200+08:00
// ofEpochMilli(): 通过时间戳创建
Instant instant1 = Instant.ofEpochMilli(32312341232L);
System.out.println(instant1); // 输出: 1971-01-09T23:39:01.232Z
// toEpochMilli(): 获取时间戳
long milliTime = instant.toEpochMilli();
System.out.println(milliTime); // 输出: 1764644598934
}

这是JDK 8中用于替代SimpleDateFormat的格式化类,它是线程安全的。
- 实例化:常用
DateTimeFormatter.ofPattern(String pattern) 自定义格式。
- 常用方法:
format(TemporalAccessor temporal): 格式化(日期时间 -> 字符串)。
parse(CharSequence text): 解析(字符串 -> 日期时间对象)。
@Test
public void testDateTimeFormatter() {
// 自定义格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 格式化:LocalDateTime ----> 字符串
LocalDateTime localDateTime = LocalDateTime.now();
String strDateTime = formatter.format(localDateTime);
System.out.println(strDateTime); // 输出: 2025-12-02 11:19:45
// 解析:字符串 ----> LocalDateTime
TemporalAccessor parsed = formatter.parse("2025-12-02 15:19:45");
LocalDateTime localDateTime1 = LocalDateTime.from(parsed);
System.out.println(localDateTime1); // 输出: 2025-12-02T15:19:45
}

总结
从java.util.Date和Calendar到java.time包,Java日期时间API完成了一次重要的现代化革新。新API设计清晰(如LocalDate、LocalTime、LocalDateTime分工明确)、线程安全、且符合ISO标准。对于新项目,强烈建议直接使用Java 8及以上的日期时间API,它能更优雅、更安全地处理各类日期时间场景。