Java 时间全解析:Date 到 java.time,一篇看懂所有姿势
前言
“时间”是 Java 最劝退的 API 之一:
-
旧版 Date 月份从 0 开始,SimpleDateFormat 非线程安全;
-
国际化、夏令时、跨年周数……踩不完的坑。
直到 Java 8 发布 java.time(JSR-310),官方终于给出“能看又好用”的解决方案。 今天这篇,带你 从 0 到 1 掌握现代 Java 时间处理,并给出可直接落地的最佳实践。
一、为什么抛弃 Date/Calendar?
|
旧类 |
典型槽点 |
线程安全 |
|---|---|---|
|
java.util.Date |
月份 0 起算、同时承载日期+时间、毫秒精度 |
否 |
|
Calendar |
可变性 + 字段魔法数、周计算反直觉 |
否 |
|
SimpleDateFormat |
parse/format 前要先加锁,高并发必炸 |
否 |
结论:只维护老代码时用,新工程请直接 java.time 。
二、java.time 五件套(先背下来)
LocalDate ➜ 2025-12-22
LocalTime ➜ 14:30:45.123
LocalDateTime ➜ 2025-12-22T14:30:45.123
ZonedDateTime ➜ 2025-12-22T14:30:45+08:00[Asia/Shanghai]
Instant ➜ 秒 / 毫秒级时间戳
都是不可变对象,每次“加减”都会返回新实例,天然线程安全 。
三、30 秒速查表(收藏备用)
|
需求 |
代码 |
|---|---|
|
今天 |
LocalDate today = LocalDate.now(); |
|
此刻 |
LocalDateTime now = LocalDateTime.now(); |
|
解析 |
LocalDate d = LocalDate.parse("2025-12-22"); |
|
自定义格式 |
LocalDateTime.parse("12/22/2025 14:30", DateTimeFormatter.ofPattern("MM/dd/yyyy HH:mm")) |
|
格式化输出 |
String s = now.format(DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm")); |
|
加 1 天 |
LocalDate tomorrow = today.plusDays(1); |
|
减 1 月 |
LocalDate prev = today.minusMonths(1); |
|
间隔天数 |
long days = ChronoUnit.DAYS.between(today, endDate); |
|
间隔毫秒 |
long ms = Duration.between(start, end).toMillis(); |
|
时区转换 |
ZonedDateTime ny = shanghai.withZoneSameInstant(ZoneId.of("America/New_York")); |
四、4 个实战场景(复制即用)
① 合同到期提醒
LocalDate sign = LocalDate.of(2025, 6, 1);
LocalDate expire = sign.plusMonths(6).minusDays(1); // 2025-11-30
long remainDays = ChronoUnit.DAYS.between(LocalDate.now(), expire);
System.out.println("还有 " + remainDays + " 天到期");
② 解析前端参数(容错写法)
DateTimeFormatter FMT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
LocalDateTime dt = LocalDateTime.parse("20251222153000", FMT);
③ 跨时区开会(上海 → 纽约)
ZonedDateTime sh = ZonedDateTime.of( LocalDateTime.of(2025, 12, 25, 9, 0), ZoneId.of("Asia/Shanghai"));
ZonedDateTime ny = sh.withZoneSameInstant(ZoneId.of("America/New_York")); System.out.println("纽约时间:" + ny); // 2025-12-24T20:00-05:00[America/New_York]
④ 高并发计时(安全 + 无锁)
Instant start = Instant.now(); // ...业务...
long cost = Duration.between(start, Instant.now()).toMillis();
五、新旧代码共存(维护老系统必备)
|
方向 |
示例 |
|---|---|
|
Date → LocalDateTime |
LocalDateTime ldt = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); |
|
LocalDateTime → Date |
Date d = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()); |
SimpleDateFormat 务必加锁或使用 ThreadLocal;Java 8+ 推荐直接 DateTimeFormatter 。
六、常见坑 & 最佳实践
-
不可变! 加减后一定要接收返回值 date.plusDays(1); // ❌ 没意义 date = date.plusDays(1); // ✅
-
LocalDateTime 不带时区,不能直接转时间戳 long epoch = ldt.atZone(ZoneId.systemDefault()).toEpochSecond();
-
格式字母区分大小写 yyyy-MM-dd HH:mm:ss 别写成 YYYY-MM-DD hh:mm:ss,否则跨年周会翻车 。
-
高并发场景拒绝 SimpleDateFormat,用 DateTimeFormatter 线程安全 。
-
前端 ↔ 后端日期字符串 SpringBoot 项目直接加注解,零配置: @DateTimeFormat(pattern = "yyyy-MM-dd") // 入参 @JsonFormat(pattern = "yyyy-MM-dd", timezone = "GMT+8") // 出参 private LocalDate signDate;
七、一张思维导图(保存即可)
java.time
├─ LocalDate / LocalTime / LocalDateTime 人类可读
├─ ZonedDateTime 带时区
├─ Instant 机器时间戳
├─ Duration / Period 时间差
└─ DateTimeFormatter 格式/解析
八、总结一句话
新项目请默认 java.time,老项目逐步迁移;记住 Local-日期时间、Zoned-时区、Instant-时间戳、Duration-间隔、DateTimeFormatter-格式 五件套,所有时间操作都能优雅解决!
更多推荐


所有评论(0)