gorm连接mysql时必须在dsn中显式指定loc=utc,避免因环境时区差异导致time.time解析错误;时区名须用iana标准名如asia/shanghai,禁用cst等缩写;查询后应验证t.string()与t.utc().string()是否一致。

gorm.Open 时没传 loc=UTC,time.Time 字段默认按本地时区解析
MySQL 驱动在 parseTime=true 开启后,若 DSN 中未显式指定 loc 参数,会 fallback 到 time.Local。而容器或 CI 环境里 time.Local 往往是 UTC,开发机却是 CST,结果同一段代码查出的 created_at 在不同环境显示为不同时间点。
实操建议:
- 检查你的 DSN 是否包含
&loc=UTC,不是&loc=Local,也不是漏写 - 别依赖环境变量
TZ—— Go 的time包完全不读它 - 用
time.Now().Location().String()打印当前生效时区,确认不是Local或空字符串
time.LoadLocation("CST") 返回 nil 导致 panic
IANA 时区数据库不认缩写名 CST、GMT+8、China/Beijing,只接受标准名如 Asia/Shanghai。调用 time.LoadLocation("CST") 会返回 nil,后续 t.In(loc) 直接 panic。
实操建议:
- 硬编码时区名必须用 IANA 官方名称:
"Asia/Shanghai"、"America/New_York"、"UTC" - 不要拼接字符串构造时区名,避免大小写或斜杠错误
- 调试时加一行
if loc == nil { log.Fatal("failed to load location") }
GORM 查询结果中 time.Time 值带错时区标签
即使数据库存的是 UTC 时间,如果驱动没正确绑定时区,扫描进 struct 的 time.Time 可能被套上 +0800 CST 标签(实际应为 +0000 UTC),后续调用 .Format() 或 .Sub() 就会出错。
实操建议:
- 查询后立刻验证:打印
t.String()和t.UTC().String(),看是否一致 - 避免直接用
db.Raw().Scan()扫到裸time.Time;优先用sql.NullTime或自定义Scan()方法 - 对外输出一律走
t.UTC().Format("2006-01-02T15:04:05Z"),不依赖结构体字段默认行为
手动设置 time.Local = time.UTC 的时机和风险
这个操作必须在 main() 最开头、任何 goroutine 启动前完成,否则部分 goroutine 仍会拿到旧的 time.Local 值。而且它会影响整个进程,包括第三方库中未显式指定时区的 time.Now() 调用。
实操建议:
- 仅用于单例应用;微服务多实例部署时不推荐全局覆盖
- 设完立即验证:
fmt.Println(time.Now().Location())应输出UTC - 更安全的做法是:所有时间操作显式调用
.UTC()或.In(loc),不依赖time.Local
loc=UTC 和 time.LoadLocation 这两个点,比到处加 .UTC() 更治本。











