Java 时间类(中):JDK 8 全新时间 API 详细教程
(2025–2026 生产视角 · 重点实用写法 + 常见误区 + 面试高频点)
自 JDK 8 引入 java.time 包(JSR-310)之后,基本上所有新项目都不应该再使用 java.util.Date、Calendar、SimpleDateFormat 这三个“古老且危险”的类。
本篇把 java.time 包中最核心、最常用的类和用法全部梳理一遍,附上最常见的生产场景写法。
一、JDK 8 时间 API 核心类一览表(背下来)
| 类别 | 核心类 | 是否可变 | 时区相关 | 主要用途 | 面试常考 |
|---|---|---|---|---|---|
| 日期(不含时间) | LocalDate | 不可变 | 否 | 生日、订单日期、账单日 | ★★★★★ |
| 时间(不含日期) | LocalTime | 不可变 | 否 | 上班打卡、下班时间、会议开始时间 | ★★★★☆ |
| 日期+时间 | LocalDateTime | 不可变 | 否 | 大多数业务场景(日志、创建时间、更新时间) | ★★★★★ |
| 带时区日期时间 | ZonedDateTime / OffsetDateTime | 不可变 | 是 | 跨境业务、全球用户、定时任务跨夏令时 | ★★★★☆ |
| 时刻(时间戳) | Instant | 不可变 | UTC | 数据库存储时间戳、分布式系统序时 | ★★★★☆ |
| 时间段 | Duration / Period | 不可变 | — | 计算两个时间点之间的差值 | ★★★★☆ |
| 时间间隔(Cron) | TemporalAmount 接口实现类 | — | — | 加减年/月/日/时/分/秒 | ★★★☆☆ |
| 格式化 | DateTimeFormatter | 不可变 | 可带时区 | 所有格式化与解析 | ★★★★★ |
一句话记忆口诀:
“Local 系列不带时区最常用,Zoned 带时区跨境用,Instant 是 UTC 绝对时刻,Duration 算时分秒,Period 算年月日。”
二、最常用 20 种写法(生产直接抄)
1. 获取当前日期/时间(最常见)
LocalDate today = LocalDate.now(); // 2025-02-25
LocalTime nowTime = LocalTime.now(); // 14:35:27.123456789
LocalDateTime now = LocalDateTime.now(); // 2025-02-25T14:35:27.123456789
ZonedDateTime londonNow = ZonedDateTime.now(ZoneId.of("Europe/London"));
Instant instantNow = Instant.now(); // UTC 时间戳
2. 指定某个日期/时间
LocalDate birth = LocalDate.of(1990, 5, 20); // 1990-05-20
LocalDateTime meeting = LocalDateTime.of(2025, 3, 1, 14, 30);
LocalDateTime parseFromStr = LocalDateTime.parse("2025-02-25 14:30:00",
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
3. 日期计算(加减天/月/年)
LocalDate tomorrow = today.plusDays(1);
LocalDate nextMonth = today.plusMonths(1);
LocalDate lastYear = today.minusYears(1);
// 更精确的加减(支持 Duration / Period)
LocalDateTime after2Hours = now.plus(Duration.ofHours(2));
LocalDate after3Months = today.plus(Period.ofMonths(3));
4. 计算两个日期之间的差值
// 两个 LocalDate 之间相差多少天
long daysBetween = ChronoUnit.DAYS.between(startDate, endDate);
// Duration(精确到纳秒)
Duration duration = Duration.between(startDateTime, endDateTime);
long hours = duration.toHours();
long minutes = duration.toMinutesPart();
// Period(年月日)
Period period = Period.between(birthDate, today);
int yearsOld = period.getYears();
5. 格式化与解析(最常用模板)
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
DateTimeFormatter dtfChina = DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH时mm分", Locale.CHINA);
// 格式化
String str = now.format(dtf); // "2025-02-25 14:35:27"
// 解析
LocalDateTime parsed = LocalDateTime.parse("2025-02-25 14:35:27", dtf);
常用格式化字母表(背熟最常考):
- y → 年
- M → 月
- d → 日
- H → 24小时制
- h → 12小时制
- m → 分钟
- s → 秒
- S → 毫秒
- E → 星期几(英文)
- a → 上午/下午
6. 时区相关操作(跨境业务必考)
// 当前伦敦时间
ZonedDateTime londonTime = ZonedDateTime.now(ZoneId.of("Europe/London"));
// 北京时间
ZonedDateTime beijingTime = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
// 转换为另一个时区
ZonedDateTime tokyoTime = londonTime.withZoneSameInstant(ZoneId.of("Asia/Tokyo"));
// 转成 UTC Instant
Instant utcInstant = londonTime.toInstant();
7. 与旧 API 互转(遗留系统对接)
// Date → LocalDateTime
Date oldDate = new Date();
LocalDateTime ldt = oldDate.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
// LocalDateTime → Date
Date date = Date.from(now.atZone(ZoneId.systemDefault()).toInstant());
// Calendar → ZonedDateTime
Calendar cal = Calendar.getInstance();
ZonedDateTime zdt = cal.toInstant().atZone(ZoneId.systemDefault());
三、2025–2026 年生产中最常犯的 10 个坑(避坑指南)
- 直接用 new Date() 或 System.currentTimeMillis() 存创建时间
→ 应统一用Instant.now()或LocalDateTime.now(),数据库存 TIMESTAMP WITH TIME ZONE - SimpleDateFormat 多线程使用
→ 完全禁止!改用DateTimeFormatter(线程安全) - 时区写死 “GMT+8”
→ 用ZoneId.of("Asia/Shanghai"),支持夏令时和政治变化 - 误用 LocalDateTime 做全球时间比较
→ 应该转成 Instant 或 ZonedDateTime 再比较 - 生日用 LocalDateTime 存
→ 只用 LocalDate 就够了(不需要时间部分) - Duration.between 用错了顺序
→ between(start, end) → end 在前会返回负值 - 格式化带毫秒时漏写 SSS
→ “yyyy-MM-dd HH:mm:ss” 不会带毫秒,要写 “yyyy-MM-dd HH:mm:ss.SSS” - 跨年月计算时用 plusDays
→ 应该用 Period.ofMonths() 或 ChronoUnit.MONTHS.between() - 数据库取出的 Timestamp 直接 toLocalDateTime()
→ 必须指定时区:timestamp.toLocalDateTime().atZone(ZoneId.systemDefault()) - 以为 LocalDateTime 包含时区
→ 它不包含!要带时区用 ZonedDateTime 或 OffsetDateTime
四、总结:2026 年最推荐的使用原则
- 业务日期(生日、订单日) → LocalDate
- 业务时间点(创建时间、更新时间、日志) → LocalDateTime(数据库存无时区)
- 全球一致时间戳 → Instant(数据库存 UTC)
- 跨时区显示/计算 → ZonedDateTime
- 所有格式化/解析 → DateTimeFormatter(永远不要再碰 SimpleDateFormat)
- 时间计算 → 优先用 plus/minus + Duration / Period / ChronoUnit
需要继续深入哪一块?
- 与数据库(MySQL/PostgreSQL)交互的最佳实践
- ZonedDateTime 在夏令时切换时的行为
- Java 21+ 虚拟线程对时间 API 的影响(几乎无)
- 常见时间计算面试题 20 道(带代码)
- 三方库(Joda-Time → java.time 迁移指南)
告诉我你的需求,我继续展开~