LocalDateTime加减运算最安全,应使用plusDays()、minusHours()等方法;解析字符串需指定DateTimeFormatter;计算时间差要区分Duration(纳秒级)和Period(日历级);涉及时区必须用ZonedDateTime或Instant。

用 LocalDateTime 做加减运算最安全
Java 8 引入的 java.time 包彻底替代了过时的 Date 和 Calendar。日常日期加减(如“3天后”“2小时前”)直接用 LocalDateTime 的 plusDays()、minusHours() 等方法,线程安全、不可变、语义清晰。
- 不要用
new Date().getTime() + 3 * 24 * 60 * 60 * 1000手动算毫秒——易出错,不处理闰秒/夏令时 -
LocalDateTime不带时区,适合纯业务日期逻辑(如订单有效期、倒计时) - 若需跨时区计算(如“北京时间上午9点对应纽约时间几点”),必须升级为
ZonedDateTime
LocalDateTime now = LocalDateTime.now(); LocalDateTime threeDaysLater = now.plusDays(3); LocalDateTime twoHoursAgo = now.minusHours(2);
解析字符串日期必须指定 DateTimeFormatter
LocalDateTime.parse() 默认只认 ISO 格式("2024-05-20T14:30:00")。遇到 "2024/05/20 14:30" 或 "2024-05-20" 这类常见格式,不传 DateTimeFormatter 会抛 DateTimeParseException。
- 预定义常量如
DateTimeFormatter.ISO_LOCAL_DATE_TIME仅覆盖标准场景 - 中文习惯的
"yyyy年MM月dd日 HH:mm:ss"必须手写 pattern:DateTimeFormatter.ofPattern("yyyy年MM月dd日 HH:mm:ss") - pattern 中大小写敏感:
"mm"是分钟,"MM"是月份;"yyyy"是4位年,"yy"是2位年
String input = "2024-05-20 14:30:00";
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
LocalDateTime dt = LocalDateTime.parse(input, formatter);
Duration 和 Period 别混用
计算两个时间点之间的差值时,选错类型会导致结果不符合预期:
-
Duration用于精确到纳秒的时间跨度(适合Instant、LocalTime),返回的是“总秒数+纳秒”,不关心日历含义 -
Period用于日期跨度(适合LocalDate、LocalDateTime),返回“几年几月几天”,会按日历规则进位(比如 1月31日 + 1个月 = 2月28/29日) - 错误示例:
Period.between(startDate, endDate)对LocalDateTime只比较年月日,忽略时分秒——如果需要完整时间差,先转成Instant再用Duration
LocalDateTime start = LocalDateTime.of(2024, 1, 31, 10, 0);
LocalDateTime end = LocalDateTime.of(2024, 2, 29, 10, 0);
Period period = Period.between(start.toLocalDate(), end.toLocalDate()); // 得到 P1M
Duration duration = Duration.between(start.atZone(ZoneId.systemDefault()).toInstant(),
end.atZone(ZoneId.systemDefault()).toInstant()); // 得到 PT2505600S(29天)时区处理不当是线上故障高发区
系统默认时区(ZoneId.systemDefault())在容器或云环境里可能不是你期望的(比如 Docker 镜像默认 UTC)。用 LocalDateTime 存储带时区含义的时间(如用户提交的“2024-05-20 14:00”),等于埋雷。
立即学习“Java免费学习笔记(深入)”;
- 入库前统一转为
Instant(UTC 时间戳),数据库字段用TIMESTAMP WITH TIME ZONE或等效类型 -
前端传来的带时区时间(如 ISO 8601 格式
"2024-05-20T14:30:00+08:00"),用ZonedDateTime.parse()直接解析,别先切掉时区再塞进LocalDateTime - 日志打印时间务必显式指定时区:
LocalDateTime.now(ZoneId.of("Asia/Shanghai")),否则排查问题时容易看错时间
最隐蔽的坑:LocalDateTime.now() 和 LocalDateTime.parse("2024-05-20") 看似都“没时区”,但前者隐含系统默认时区上下文,后者完全无上下文——混在一起做比较或计算,结果取决于服务器配置,极难复现。









