★ gtime 时区处理与 MySQL 时间交互
面试问法:GoFrame 里时间字段为什么经常差 8 小时?gtime 和 time.Time 怎么选? 一句话结论:时间偏移几乎都出在“进程时区、数据库连接时区、驱动换算”三者不一致。gtime 解决对象好用与格式统一,
SetTimeZone定进程全局时区,连接串带loc=Local&parseTime=true保证驱动换算一致——三条主线对齐,八小时问题基本消失。
gtime 与 time.Time 的区别
| 维度 | time.Time(标准库) | gtime.Time |
|---|---|---|
| 底层 | 标准库对象 | 内部仍是 time.Time,加便捷方法 |
| JSON 序列化 | RFC3339(带 T 与 Z) | 2006-01-02 15:04:05,前端友好 |
| 格式化 | 2006-01-02 15:04:05 | 支持 PHP 风格 Y-m-d H:i:s |
| 解析 | 需指定布局 | 自动识别常见字符串格式 |
| gen dao 字段 | 需 stdTime: true | 默认生成 *gtime.Time |
三步消灭时区坑
// 1. 进程全局时区:只能成功设置一次,且要在 time 包“记住”时区之前执行
// 建议放独立包并在 main 最前面 import _,否则可能设置无效
func init() {
if err := gtime.SetTimeZone("Asia/Shanghai"); err != nil { panic(err) }
}
# 2. 数据库连接:loc + parseTime,让驱动按本地时区换算
database:
default:
link: "mysql:root:pass@tcp(127.0.0.1:3306)/db?loc=Local&parseTime=true"
// 3. 条件时间用本地时区构造,与 loc=Local 一致
t, _ := time.ParseInLocation("2006-01-02 15:04:05", "2024-01-02 10:00:00", time.Local)
dao.User.Ctx(ctx).Where("created_at > ?", t).All()
// 坑:time.Parse 解析出 UTC,会被驱动按 +8 换算,条件就偏了 8 小时
注意点
- 服务器时区、Docker 容器时区、数据库时区建议统一同一时区。
created_at/updated_at这类由数据库DEFAULT CURRENT_TIMESTAMP维护的字段,写入时交给数据库即可。- gen dao 想用标准库时间类型:配置
stdTime: true;默认时间字段生成*gtime.Time。
常见追问
- 追问:为什么查询结果和库里显示差 8 小时?——库值本身无时区(DATETIME),读出后驱动按 loc 转成 time 对象;连接串没带 loc 时按 UTC 语义处理,展示层再转本地就差了。
- 追问:存 UTC 还是存本地时间?——规范做法是内部统一(推荐 UTC 或业务时区二选一),展示层转换;最忌“库里一种、代码一种、前端再猜一种”。
记忆点
- 三件事:gtime 管格式、SetTimeZone 管进程、
loc=Local&parseTime=true管驱动。 - 追问高频点:SetTimeZone 只能设一次、time.Parse 的 UTC 陷阱、gen dao 的 gtime/stdTime 选择。