旧系统迁移到 PostgreSQL 后,日期结果可能相差数小时,也可能跨到前一天或后一天。排查时应沿着旧数据、迁移转换、数据库存储、驱动读取、接口序列化和客户端显示逐层验证。关键不是先把所有时区改成同一个值,而是确定每个字段表达的是一个全球唯一的时刻,还是日历上的日期与时间。
timestamptz 表示确定的时刻。带偏移的输入会被换算为统一时刻,读取时再按会话时区格式化;该类型不保留输入中的原始时区名称。timestamp without time zone 仅保存年月日时分秒等日历字段,不附带时区,也不能单独确定全球唯一时刻。date 只表达年月日,适用于生日、账期或营业日期等纯日期语义。
选择类型前先查业务定义。一个预约开始时刻通常要能确定具体瞬间,可能需要时区信息;营业日则通常应保留纯日期。若把旧系统的本地时间当作 UTC,或在导入时对本来不含时区的时间自动套用连接时区,显示结果就可能发生偏移。夏令时地区的本地时间还可能落入不存在或重复的时段。
这套逐层验证适用于迁移后出现固定小时差、日期跨日、仅部分应用节点异常,或数据库查询与 API 返回不一致的情况。为每个问题字段准备可重复样本,至少包括带明确偏移的时间、无偏移时间、纯日期、接近午夜的时刻,以及夏令时转换日期(适用时)。样本使用虚构或脱敏数据,并记录业务期望值。
TimeZone、驱动连接参数。可在只读诊断查询中使用 pg_typeof(字段名) 核实类型,并查看当前会话时区。注意,SQL 客户端的显示也会受会话设置影响;屏幕上看到的格式本身不能证明底层语义正确。
若数据库原始结果正确、应用对象已偏移,优先检查驱动类型映射、连接时区与 ORM 转换;若对象正确而 JSON 偏移,检查序列化器及格式是否附带偏移;若 JSON 正确而页面日期错误,检查客户端对无时区字符串和纯日期的解析方式。若数据库查询本身已偏移,则回查导入解析、列类型与会话时区。差异恰好是固定小时数只是线索,不能单凭它断定是 UTC 转换错误。
常见误区包括把所有字段都改为 timestamptz、把全库会话时区改为 UTC 后宣布修复、对错误结果统一加减小时,以及只用非夏令时日期验收。这些做法可能暂时改变显示,却会损坏纯日期语义,或在不同季节和客户端重现问题。不要通过直接改写历史数据来掩盖原因;先在隔离样本和可回滚的迁移流程中验证规则。
验收时,每种字段语义都应有明确的预期:时刻在各层表示同一个瞬间,纯日期在各层保持同一日历日,带偏移的输入按约定转换。覆盖午夜边界、不同会话时区、夏令时切换(适用时)、API 往返及各支持客户端。保存字段映射说明、测试样本、转换规则和逐层观测结果,便于后续审计与复查。只有写入、查询、序列化、解析和展示均符合业务定义,迁移才可验收。✅