MySQL时间字段默认UTC,Gin+GORM读取“晚8小时”是因DSN缺parseTime=true和loc=Local;前者使驱动解析为time.Time,后者指定本地时区解析,配合模型中正确使用time.Time类型才能确保读写时区一致。

MySQL 返回的时间字段默认是 UTC,Gin + GORM 读出来却像“晚了 8 小时”,这不是框架 bug,而是连接配置漏了关键参数。
MySQL DSN 必须加 parseTime=true 和 loc=Local
不加这两个参数,time.Time 字段会被当成字符串返回(比如 "2024-06-15 14:30:00"),或者按 UTC 解析成错误时间。GORM 不会自动帮你转时区。
-
parseTime=true:让驱动把 MySQL 的DATETIME/TIMESTAMP字段解析为time.Time类型,而不是string -
loc=Local:指定解析时使用的时区,Local表示 Go 进程所在机器的本地时区(通常是东八区);也可写死为loc=Asia/Shanghai,避免依赖部署环境 - DSN 示例:
user:pass@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=true&loc=Local
GORM 模型字段必须用 time.Time 类型,不能用 string 或 int64
如果数据库字段是 DATETIME,但你在 struct 中定义成 CreatedAt string,那无论 DSN 怎么配,都得不到正确 time 值 —— 驱动根本不会尝试解析。
- 正确写法:
CreatedAt time.Time `gorm:"column:created_at"` - 带默认值或可空时,用
*time.Time,但注意解引用前判空 - 别为了“兼容前端”强行转成 Unix 时间戳存进 struct —— 那会绕过时区处理逻辑,后续做时间运算(如
time.Now().After(model.CreatedAt))反而出错
写入时间字段时,确保传的是本地时区的 time.Time
即使读取正确,写入时若传入一个 UTC 时间(比如 time.Now().UTC()),MySQL 会按当前 session 时区存储,结果可能比预期早/晚 8 小时。
立即学习“go语言免费学习笔记(深入)”;
- 推荐统一用
time.Now()(即本地时区),配合 DSN 中loc=Local,读写一致 - 如果业务强制要求 UTC 存储,那 DSN 应设
loc=UTC,且所有写入都用time.Now().UTC(),读出后如需本地展示再调.In(loc) - 避免混用:
time.Now().In(time.UTC)写入 +loc=Local读取 → 时间错乱
验证是否生效:查出的 time.Time 值应含正确时区信息
打印日志时别只看字符串格式,要确认 .Location().String() 是 Local 或 Asia/Shanghai,而不是 UTC。
fmt.Printf("CreatedAt: %+v, Location: %s\n", model.CreatedAt, model.CreatedAt.Location().String())
如果输出 Location: UTC,说明 DSN 没生效,或模型字段类型不对;如果 Location: Local 但时间仍偏差,检查服务器系统时区是否真为 CST(timedatectl status)。
真正麻烦的不是配置本身,而是读写两端时区逻辑不统一 —— 一个地方用 loc=Local,另一个地方手动调 .UTC(),问题就藏在这些看似无害的转换里。


















