gorm写入mysql时间差8小时是因dsn缺少parsetime=true和匹配loc参数;必须同时启用解析并指定正确时区(如asia%2fshanghai或utc),且数据库、驱动、应用三者时区需严格一致。

GORM 写入 MySQL 的时间总是差 8 小时,不是代码写错了,而是 DSN 缺了 parseTime=true 和 loc 参数,且两者必须匹配业务真实时区或统一用 UTC。
DSN 中必须同时启用 parseTime=true 并指定 loc
MySQL 驱动默认不解析时间字符串,created_at 字段会被当成普通字符串塞进 time.Time{},结果就是零值或 panic。光加 parseTime=true 不够——它只是开启解析,但解析后套哪个时区,由 loc 决定。
-
loc=UTC:适合全栈 UTC 统一存储的系统,要求 Go runtime、MySQL server、连接层全部对齐为 UTC(启动时需time.Local = time.UTC) -
loc=Asia%2FShanghai:开发机/测试环境用北京时间,URL 编码必须把/写成%2F,否则解析失败 -
loc=Local:依赖宿主机/etc/localtime,Docker 容器里常是 UTC,线上行为不可控,不推荐
time.LoadLocation("Asia/Shanghai") 返回 nil 怎么办
常见原因是传了非法时区名:"CST"、"GMT+8"、"China/Beijing" 全都不认。IANA 官方名只接受 "Asia/Shanghai" 这类格式,且区分大小写。
- 调试时先打印
time.Now().Zone(),确认当前环境识别出的时区名 -
"UTC"和"Local"是内置名,可直接用;其他所有时区都必须通过time.LoadLocation加载 - 加载失败会返回
nil,后续调t.In(loc)必 panic,务必检查 err
GORM 自动填充字段(CreatedAt)的时间来源
GORM v2 默认用 time.Now() 填充 CreatedAt,但它不自动转时区。如果你设了 time.Local = time.UTC,那填充的就是 UTC 时间;如果没设,就按宿主机时区走——而宿主机在容器里大概率是 UTC,本地却是 CST,这就埋了坑。
- 最稳做法:显式注册回调,强制用
time.Now().UTC()或time.Now().In(shanghai) - 注册方式:
db.Callback().Create().Replace("gorm:create_time_stamp", yourCallback) - 别依赖 GORM 默认行为,尤其当部署环境跨本地/Docker/K8s 时
从数据库读出来的时间显示带 +0800 却不是北京时间
现象:查出来的 CreatedAt 是 2024-05-20 10:00:00 +0800 CST,但数据库里存的是 2024-05-20 02:00:00 ——说明驱动把字符串按 loc 解析对了,但数据库本身存的其实是 UTC 值,你却当成本地时间用了。
- 根本矛盾:数据库存的是什么时区?驱动按什么时区解析?应用逻辑按什么时区理解?三者必须一致
- 验证方法:用 MySQL CLI 直连,执行
SELECT @@global.time_zone, @@session.time_zone; - 若返回
SYSTEM,再查cat /etc/timezone,确认 MySQL 实际用的时区
时区问题最难 debug 的地方,是它在单环境里完全正常,一上容器或换服务器就崩——因为 time.Local、MySQL 配置、DSN 参数这三者,任何一个没对齐,时间就悄悄偏移了。











