LocalDateTime 无时区,仅表示本地日历时间;ZonedDateTime 绑定时区(如 Asia/Shanghai),可正确处理夏令时和历史变更,适用于跨系统比对与持久化。
用 LocalDateTime 表示“2025-05-20T14:30:00”只是本地日历时间,不带时区信息;ZonedDateTime 则明确绑定到某个时区(如 Asia/Shanghai),能正确处理夏令时、历史时区变更等场景。
常见错误:把服务器本地时间直接转成 ZonedDateTime 却没指定时区,结果默认用了系统时区,部署到海外服务器就出错。
LocalDateTime.now() 永远不包含时区,不能用于跨系统时间比对或持久化存储
ZonedDateTime.now(ZoneId.of("Asia/Shanghai")) 才是真正代表东八区当前时刻的可靠值timestamptz 存 ZonedDateTime,别用 timestamp without time zone
Instant 表示从 1970-01-01T00:00:00Z 开始的纳秒偏移量,本质就是 UTC 时间轴上的一个点。它没有时区概念,也不受本地格式影响,是跨系统、跨语言最安全的时间表示方式。
常见错误:用 System.currentTimeMillis() 构造 LocalDateTime,结果丢失了时区上下文,再转回 Instant 可能偏差一整天。
Instant:zonedDateTime.toInstant() 或 localDateTime.atZone(ZoneId.of("UTC")).toInstant()
"2025-05-20T14:30:00Z")可直接用 Instant.parse()
new Date().toInstant() —— Date 是遗留类,语义模糊,容易引发隐式时区转换yyyy 和 YYYY 不是一回事:yy 是“年份”,
yyYYYY 是“基于周的年份”,在每年年初或年末可能差一年;MM 是月份,mm 是分钟,写错就解析失败或结果错乱。
常见错误:用 DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss") 解析带毫秒的字符串(如 "2025-05-20T14:30:00.123"),抛出 DateTimeParseException。
DateTimeFormatter.ISO_LOCAL_DATE_TIME 或 DateTimeFormatter.ISO_INSTANT
SSS,纳秒用 nnn,时区用 XXX(如 +08:00)或 ZZZ(如 GMT+08:00)LocalDateTime.format() 而不是 String.format(),后者无法处理时区偏移ZoneOffset 是固定偏移(如 +08:00),不随夏令时变化;ZoneId 是真实地理时区(如 Europe/London),会自动应用 DST 规则。两者不能混用。
常见错误:用 ZoneOffset.of("+08:00") 替代 ZoneId.of("Asia/Shanghai"),看似一样,但遇到中国未来调整时区规则时,前者永远不变,后者可通过 JDK 更新支持新规则。
ZoneOffset(如日志时间统一打成 UTC+0)ZoneId
ZoneId.systemDefault() 不可靠——服务器时区可能被运维修改,应显式配置并注入,比如 Spring Boot 中通过 @Value("${app.timezone:Asia/Shanghai}")
ZoneId shanghai = ZoneId.of("Asia/Shanghai");
LocalDateTime local = LocalDateTime.of(2025, 3, 31, 2, 30, 0); // 凌晨2:30
ZonedDateTime zdt = local.atZone(shanghai); // 自动按规则处理是否为夏令时(中国不实行,但逻辑存在)
System.out.println(zdt); // 2025-03-31T02:30+08:00[Asia/Shanghai]
时区规则更新、夏令时边界、毫秒精度截断——这些细节不会报错,但会在特定日期、特定地区悄悄出错。写时间逻辑时,别依赖“看起来对”。