Java 的日期时间 API?Java 8 Date/Time API?
介绍 Java 8 java.time 的核心用法:按需选择 LocalDate、LocalTime、LocalDateTime、Instant、ZonedDateTime,掌握不可变对象的加减、DateTimeFormatter 格式化解
学完这篇你能把 Java 8 的 java.time 用起来:创建日期、加减时间、格式化解析、处理时区、计算间隔,以及和老的 Date/Calendar 互相转换,全程不踩常见坑。
Java 8 引入了 java.time 包(JSR-310),从 Java 8 起新建项目就不该再用 java.util.Date 和 SimpleDateFormat 做业务日期计算了。下面按步骤走一遍,代码可直接复制进 main 方法跑。
第一步:认识三个核心类,按需求选类型
这一步要搞清楚「日期」「时间」「日期+时间」分别用哪个类,选错类型后面全是麻烦。规则很简单:只要年月日用 LocalDate,只要时分秒用 LocalTime,两者都要用 LocalDateTime;需要绝对时间点(时间戳)用 Instant;需要带时区用 ZonedDateTime。
LocalDate date = LocalDate.of(2024, 3, 15); // 2024-03-15
LocalTime time = LocalTime.of(14, 30, 0); // 14:30
LocalDateTime dt = LocalDateTime.of(date, time); // 2024-03-15T14:30
LocalDate today = LocalDate.now(); // 系统默认时区的今天
注意:
LocalDate.of(2024, 3, 15)的月份是从 1 开始(3 就是 3 月),这一点和老的Calendar.MARCH(值为 2)正好相反,从旧代码迁过来最容易在这翻车。月份写错会直接抛DateTimeException(比如写 13),不会静默出错。
第二步:加减和修改,记住对象不可变
这一步要掌握日期计算,做完你就能替代掉以前一堆 Calendar.add() 的代码。所有 plusXxx / minusXxx / withXxx 方法都返回新对象,原对象不变,所以必须接收返回值。
LocalDate d = LocalDate.of(2024, 1, 31);
LocalDate next = d.plusMonths(1); // 2024-02-29,自动处理月末和闰年
LocalDate firstDay = d.withDayOfMonth(1); // 2024-01-01
LocalDateTime later = LocalDateTime.now().plusHours(2).minusMinutes(30);
需要「本月最后一天」用 d.with(TemporalAdjusters.lastDayOfMonth()),需要「下一个周一」用 TemporalAdjusters.next(DayOfWeek.MONDAY),都在 java.time.temporal 包里。
注意:
d.plusDays(1);单独写一行是无效操作,编译不报错但结果丢了,这是新手最常见的低级 bug。
第三步:格式化和解析,用 DateTimeFormatter
这一步解决「日期和字符串互转」。用 DateTimeFormatter 替换 SimpleDateFormat,它是线程安全的,可以定义成 static final 常量复用。
DateTimeFormatter fmt = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
String s = LocalDateTime.now().format(fmt); // 2024-03-15 14:30:00
LocalDateTime parsed = LocalDateTime.parse("2024-03-15 14:30:00", fmt);
LocalDate iso = LocalDate.parse("2024-03-15"); // 不传 formatter,默认 ISO_LOCAL_DATE
模式字母别写错:yyyy 年、MM 月、dd 日、HH 24 小时制、hh 12 小时制、mm 分、ss 秒。
注意:
YYYY(大写)是「按周算的年份」,跨年那几天会得到错误年份,一定要用小写yyyy;同理DD是一年中的第几天,别和dd混。
第四步:处理时区和绝对时间点
这一步解决「服务器在 UTC、用户在 +08:00」这类问题,做完你能正确存库和展示。跨时区存储统一用 Instant(UTC 时间戳),展示时再转成本地时区。
Instant now = Instant.now();
ZonedDateTime bj = now.atZone(ZoneId.of("Asia/Shanghai"));
LocalDateTime local = bj.toLocalDateTime();
ZonedDateTime utc = now.atZone(ZoneId.of("UTC"));
注意:
ZonedDateTime.now()依赖 JVM 默认时区,服务器和本地不一致就会差 8 小时,务必显式写ZoneId.of("Asia/Shanghai"),别依赖默认值。
第五步:算间隔,Duration 和 Period 别用混
这一步要算「两个时间差多久」。Duration 算的是时分秒级别的量(基于 Instant),Period 算的是年月日级别的量(基于 LocalDate)。
Duration dur = Duration.between(
LocalDateTime.of(2024,3,15,9,0),
LocalDateTime.of(2024,3,15,17,30));
long minutes = dur.toMinutes(); // 510
Period p = Period.between(LocalDate.of(2024,1,1), LocalDate.of(2024,3,15));
// P2M14D,用 p.getMonths()、p.getDays() 取各段
long days = ChronoUnit.DAYS.between(LocalDate.of(2024,1,1), LocalDate.of(2024,3,15));
注意:算「总天数」用
ChronoUnit.DAYS.between(),不要用Period.getDays(),后者只返回「天数那一段」,上面例子是 14 而不是 74。
第六步:和旧 API 互转
这一步处理老代码兼容。核心桥梁是 Instant:
// Date -> LocalDateTime
LocalDateTime ldt = LocalDateTime.ofInstant(new Date().toInstant(), ZoneId.systemDefault());
// LocalDateTime -> Date
Date legacy = Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());
// 数据库字段:java.sql.Timestamp
Timestamp ts = Timestamp.valueOf(ldt);
LocalDate 与 java.sql.Date 之间用 java.sql.Date.valueOf(localDate) 和 sqlDate.toLocalDate()。
注意:
java.sql.Date.toInstant()会抛UnsupportedOperationException,必须走toLocalDate()中转,别照抄java.util.Date的写法。
碰到 UnsupportedTemporalTypeException,多半是给 LocalDate 加了小时(LocalDate 没有时分秒),换成 LocalDateTime 即可。实际项目里遇到类似报错,可以在 GJ站长论坛的「后端开发 → Java」子版块搜一下报错关键字,通常有人贴过完整堆栈。
小结
- Java 8 起用
java.time,日期LocalDate、时间LocalTime、日期时间LocalDateTime、时间戳Instant、带时区ZonedDateTime。 - 月份从 1 开始,与
Calendar相反;格式化年份用小写yyyy,别用YYYY。 - 所有
plus/minus/with都返回新对象,必须接收返回值。 DateTimeFormatter线程安全,可直接做static final常量;SimpleDateFormat不是。- 算总天数用
ChronoUnit.DAYS.between(),Duration管时分秒、Period管年月日。 - 跨时区存储统一用
Instant(UTC),展示时再转本地时区,不依赖 JVM 默认时区。 - 新老 API 互转以
Instant为桥梁,java.sql.Date走toLocalDate()。
转载请注明出处,版权归原作者所有。