GORM时区设置

日月星辰 发布在Programming

在使用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进行更新。

go-sql-driver/mysql

// 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"