深浅主题
时区和日期序列化偏差
问题现象
页面日期比数据库少一天或相差八小时,定时统计边界与接口筛选不一致,夏令时地区出现重复时刻。
适用环境
JDK 17 java.time、Jackson、MySQL 8;应用与客户端可能处于不同时区。
本文是可复用的排障示例,不代表某次个人生产事故。命令中的名称、账号和数据均为示例,执行前请替换为已确认的目标。
复现或诊断步骤
- 先标明字段语义:全球时间点、本地业务时间,还是不带时间的日期。
- 对照 JVM、数据库会话、JDBC、JSON 和浏览器各层时区。
- 使用一个带 Z 的 ISO 时间和一个纯日期进行端到端跟踪,保留原值、类型和偏移量。
原因分析
LocalDateTime 不携带时区,不能单独表示全球唯一时间点。把 LocalDate 当作午夜 UTC 再交给浏览器转本地时区,会改变日历日期。MySQL TIMESTAMP 和 DATETIME 的时区行为也不相同。
修复示例
java
ZoneId businessZone = ZoneId.of("Asia/Shanghai");
Instant occurredAt = Instant.parse("2026-10-10T01:30:00Z");
ZonedDateTime local = occurredAt.atZone(businessZone);
LocalDate businessDate = local.toLocalDate();
// 查询某个业务日使用左闭右开的时间点区间
Instant start = businessDate.atStartOfDay(businessZone).toInstant();
Instant end = businessDate.plusDays(1).atStartOfDay(businessZone).toInstant();时间点在接口使用带偏移的 ISO 8601;纯业务日期使用 YYYY-MM-DD 字符串,不构造成 JS Date。存储策略与 JDBC 配置统一,并明确旧数据的真实语义。历史修正先备份和抽样核对,禁止对全库直接统一加八小时;回退保留原值映射。
验证方法
检查 UTC、上海和含夏令时地区的转换,以及月末、年末、毫秒精度和查询边界。按下一日的本地起点求 end,不假定每个业务日都是固定 24 小时。
常见误区
仅设置 Jackson 时区不会修复 LocalDateTime 的业务歧义。字符串后直接加 Z 会把本地时间谎称为 UTC,不能作为正确转换。