Java8(四)--时间API
引言
在 Java 8 之前,日期时间处理是每个开发者心中的痛——java.util.Date 的设计缺陷、Calendar 的笨重 API、线程安全问题以及反人类的月份从 0 开始计数。
2014 年 Java 8 带来了全新的日期时间 API(JSR 310),基于 Joda-Time 库设计,彻底解决了历史遗留问题。
一、为什么需要新的日期 API?
1、旧 API 的致命缺陷:
设计混乱:Date 同时包含日期和时间,但方法命名误导(如 getYear() 返回 1900 后的年数)
可变性隐患:SimpleDateFormat 非线程安全,Calendar 对象可修改
时区处理困难:默认使用系统时区,转换逻辑复杂
反人类设计:月份从 0 开始(1 月=0),星期从周日开始
2、新 API 的核心优势:
不可变对象:所有类都是线程安全的
清晰领域模型:明确区分日期(Date)、时间(Time)、日期时间(DateTime)
链式操作:支持流畅的 API 调用
内置时区支持:ZoneId 和 ZonedDateTime 简化时区处理
二、核心类一览:
1、基础类:
| 类名 | 描述 | 示例值 |
|---|---|---|
LocalDate |
日期(年月日) | 2023-08-25 |
LocalTime |
时间(时分秒纳秒) | 14:30:45.123 |
LocalDateTime |
日期+时间(无时区) | 2023-08-25T14:30:45.123 |
ZonedDateTime |
带时区的完整日期时间 | 2023-08-25T14:30+08:00[Asia/Shanghai] |
Instant |
时间戳(Unix 时间,精确到纳秒) | 1692945045.123456789 |
2、辅助类:
| 类名 | 用途 |
|---|---|
Duration |
时间间隔(秒、纳秒级) |
Period |
日期间隔(年、月、日) |
DateTimeFormatter |
日期格式化与解析 |
ZoneId |
时区标识(如 "Asia/Shanghai") |
三、基础操作实战:
1、创建对象:
// 获取当前日期时间
LocalDate today = LocalDate.now();
LocalTime currentTime = LocalTime.now();
LocalDateTime now = LocalDateTime.now();
// 指定值创建
LocalDate birthday = LocalDate.of(1995, Month.MAY, 23);
LocalTime meetingTime = LocalTime.of(14, 30); // 14:30
LocalDateTime projectDeadline = LocalDateTime.parse("2023-12-31T23:59:59");
// 时区敏感时间
ZonedDateTime shanghaiTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
ZonedDateTime newYorkTime = ZonedDateTime.now(ZoneId.of("America/New_York"));
2、日期计算:
// 加减操作
LocalDate nextWeek = today.plusWeeks(1);
LocalDateTime threeHoursLater = now.plusHours(3);
// 时间间隔计算
Duration duration = Duration.between(startTime, endTime);
long minutes = duration.toMinutes(); // 间隔分钟数
Period period = Period.between(startDate, endDate);
int years = period.getYears(); // 间隔年数
3、比较与判断:
boolean isAfter = today.isAfter(LocalDate.of(2020, 1, 1));
boolean isLeapYear = today.isLeapYear(); // 是否闰年
DayOfWeek dayOfWeek = today.getDayOfWeek(); // 获取星期几(MONDAY~SUNDAY)
四、时区处理详解:
1、时区转换:
// 上海时间转纽约时间
ZonedDateTime shanghaiTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
ZonedDateTime newYorkTime = shanghaiTime.withZoneSameInstant(ZoneId.of("America/New_York"));
// 输出结果示例:
// 上海:2023-08-25T16:00+08:00[Asia/Shanghai]
// 纽约:2023-08-25T04:00-04:00[America/New_York]
2、处理夏令时:
新 API 自动处理夏令时变化:
// 测试纽约 2023 年夏令时切换
ZonedDateTime beforeDst = ZonedDateTime.of(
LocalDateTime.of(2023, 3, 12, 1, 30),
ZoneId.of("America/New_York")
);
ZonedDateTime afterDst = beforeDst.plusHours(1);
// 输出:
// beforeDst: 2023-03-12T01:30-05:00[America/New_York]
// afterDst: 2023-03-12T03:30-04:00[America/New_York] (跳过了不存在的 2:30)
五、格式化与解析:五、格式化与解析:
1、预定义格式:
// ISO 格式
String isoDate = LocalDate.now().format(DateTimeFormatter.ISO_DATE); // 2023-08-25
// 自定义格式
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss");
String formatted = LocalDateTime.now().format(formatter); // 2023/08/25 14:30:45
// 解析字符串
LocalDateTime parsed = LocalDateTime.parse("2023-08-25T14:30:45", DateTimeFormatter.ISO_LOCAL_DATE_TIME);
2、本地化格式:
DateTimeFormatter germanFormatter = DateTimeFormatter
.ofLocalizedDate(FormatStyle.FULL)
.withLocale(Locale.GERMAN);
String germanDate = LocalDate.now().format(germanFormatter);
// 输出:Freitag, 25. August 2023
六、与传统 API 的互操作:
1、转换旧版 Date:
// Date -> Instant
Date oldDate = new Date();
Instant instant = oldDate.toInstant();
// Instant -> Date
Date newDate = Date.from(instant);
// Calendar -> ZonedDateTime
Calendar calendar = Calendar.getInstance();
ZonedDateTime zdt = ZonedDateTime.ofInstant(calendar.toInstant(), calendar.getTimeZone().toZoneId());
2、迁移建议:
立即停止使用:Date 和 Calendar
逐步替换:使用适配器方法转换遗留代码
数据库交互:JDBC 4.2+ 支持直接存储 LocalDate 类型
七、最佳实践与陷阱规避:
1、实践建议:
明确类型选择:
只需要日期 → LocalDate
需要精确时间 → LocalTime
需要时间戳 → Instant
需要时区 → ZonedDateTime
始终传递时区:
// 错误做法(隐式使用系统时区)
LocalDateTime.now();
// 正确做法(显式指定时区)
LocalDateTime.now(ZoneId.of("Asia/Shanghai"));
2、常见陷阱:
时区遗忘:跨时区系统必须明确指定时区
错误的时间精度:LocalTime 默认精度到纳秒,需注意比较逻辑
混淆 Period 和 Duration:日期间隔 vs 精确时间间隔
八、实战案例:
案例 1:计算信用卡有效期
LocalDate issueDate = LocalDate.of(2023, 8, 1);
Period validityPeriod = Period.ofYears(3);
LocalDate expiryDate = issueDate.plus(validityPeriod).withDayOfMonth(1).minusDays(1);
// 输出:2026-07-31
案例 2:跨时区会议提醒
ZonedDateTime meetingTokyo = ZonedDateTime.of(
LocalDateTime.of(2023, 9, 1, 15, 0),
ZoneId.of("Asia/Tokyo")
);
ZonedDateTime meetingLondon = meetingTokyo.withZoneSameInstant(ZoneId.of("Europe/London"));
// 东京 15:00 → 伦敦 07:00(夏令时差 8 小时)
总结
Java 8 日期时间 API 通过以下改进成为现代开发的必备工具:
清晰直观:每个类职责单一(日期、时间、时区分离)
线程安全:所有实例不可变
强大功能:内置处理夏令时、闰年、时区转换等复杂逻辑
无缝衔接:完美支持国际化与数据库交互
迁移到新 API 不仅能减少 bug,更能提升代码可读性和可维护性。建议所有新项目直接采用新 API,旧项目逐步进行迁移改造。

浙公网安备 33010602011771号