在使用
GORM
框架往MySQL写入datetime
类型数据时,发现时间少了8小时。
在使用gorm进行数据写入时,字段定义的是createAt
,自动生成当前时间。插入表后时间少了8小时。
首先怀疑是不是数据库时区设置有问题。检查MySQL时区变量:
MySQL > show variables like '%time_zone%';
+------------------+--------+
| Variable_name | Value |
+------------------+--------+
| system_time_zone | CST |
| time_zone | +08:00 |
+------------------+--------+
2 rows in set (0.01 sec)
MySQL的时区设置是没有问题的。那MySQL的时区会影响哪些值呢?NOW()
,CURTIME()
系统函数的返回值会受当前seesion的时区影响。timestamp
数据类型会存储当时session的时区信息,读取的时候根据当前session的时区进行转换,datetime
数据类型插入是什么值,读取的就是什么值,不受时区影响。
从上面MySQL的配置看,时区是没有问题的,那就是连接的session设置时区有问题了。通过查看GORM依赖的driver看出,默认值是用的UTC时区。而在解析DSN时会根据是否有设置时区loc
进行更新。
// NewConfig creates a new Config and sets default values.
func NewConfig() *Config {
return &Config{
Collation: defaultCollation,
Loc: time.UTC,
MaxAllowedPacket: defaultMaxAllowedPacket,
AllowNativePasswords: true,
CheckConnLiveness: true,
}
}
// parseDSNParams parses the DSN "query string"
// Values must be url.QueryEscape'ed
func parseDSNParams(cfg *Config, params string) (err error) {
for _, v := range strings.Split(params, "&") {
param := strings.SplitN(v, "=", 2)
if len(param) != 2 {
continue
}
// cfg params
switch value := param[1]; param[0] {
// Time Location
case "loc":
if value, err = url.QueryUnescape(value); err != nil {
return
}
cfg.Loc, err = time.LoadLocation(value)
if err != nil {
return
}
}
}
而在GORM的代码中,可以看到如果 类型为time.Time
会使用连接配置信息的Loc
进行时间的转换,
case time.Time:
paramTypes[i+i] = byte(fieldTypeString)
paramTypes[i+i+1] = 0x00
var a [64]byte
var b = a[:0]
if v.IsZero() {
b = append(b, "0000-00-00"...)
} else {
b = v.In(mc.cfg.Loc).AppendFormat(b, timeFormat)
}
paramValues = appendLengthEncodedInteger(paramValues,
uint64(len(b)),
)
paramValues = append(paramValues, b...)
所以在提供DSN时最好明确使用的时区"root:123456@tcp(192.168.1.101:33061)/test?charset=utf8mb4&parseTime=true&loc=Local"
。