
go时区
时区真的很重要!!!我要彻底的给它弄明白。注意,仅仅就Go而言,其他服务端语言需重新验证
go链接mysql的dsn链接时区起什么作用?
一定要保证数据库时区和dsn的时区要一致!!!
loc 参数的作用:
- 查询时的解析:如何将数据库返回的时间字符串解析为 time.Time
- 写入时的格式化:如何将 time.Time 格式化为 SQL 字符串
举个例子,比如你数据库是东八区的时间。值为2025-09-29 22:28:43
,然后你用不同的时区去查询得到的时间则如下所示。
- 2025-09-29 22:28:43 +0800 CST(东八区)
- 2025-09-29 22:28:43 +0000 UTC
意思就是说其实看上去值是一样的,只不过后面的单位不一样。其实也说明了值也不一样,不过我之前以为的是,loc的作用是会自动作 转换的,比如如果你dsn的是东八区,你从数据库查出来的就是和数据库的一样,如果你是UTC,则你查出来的时间就自动转换成了UTC的时间。
但事实证明,不是这个样子的,它仅仅是将你数据库的时间定义成你指定的时间而已。但是这样的话,其实是有很大的问题的,比如如果你的dsn链接和 你的数据库的时区没有保持一致,那么你查出来的时间值都是有问题。
测试表
sql
-- 在数据库中创建一个时间字段
CREATE TABLE test_time (
id INT PRIMARY KEY AUTO_INCREMENT,
event_time DATETIME,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- 插入一个明确的时间(假设数据库时区是东八区)
INSERT INTO test_time (event_time) VALUES ('2025-09-29 22:28:43');
测试代码
go
package main
import (
"fmt"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"time"
)
type TestTime struct {
ID uint
EventTime time.Time
CreatedAt time.Time
}
func main() {
// 使用 loc=UTC
dsnUTC := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=UTC"
dbUTC, _ := gorm.Open(mysql.Open(dsnUTC), &gorm.Config{})
// 使用 loc=Local (假设本地是东八区)
dsnLocal := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Asia%2FShanghai"
dbLocal, _ := gorm.Open(mysql.Open(dsnLocal), &gorm.Config{})
var result1, result2 TestTime
dbUTC.Table("test_time").First(&result1)
dbLocal.Table("test_time").First(&result2)
fmt.Println("loc=UTC:", result1.EventTime)
fmt.Println("loc=Local:", result2.EventTime)
// 转换为Unix时间戳对比
fmt.Printf("loc=UTC Unix: %d\n", result1.EventTime.Unix())
fmt.Printf("loc=Local Unix: %d\n", result2.EventTime.Unix())
// 时间差
fmt.Printf("时间差: %v\n", result1.EventTime.Sub(result2.EventTime))
}
结果
text
loc=UTC: 2025-09-29 22:28:43 +0000 UTC
loc=Local: 2025-09-29 22:28:43 +0800 CST
loc=UTC Unix: 1759184923
loc=Local Unix: 1759156123
时间差: 8h0m0s
为什么go代码中全局设置时区没有生效?
原因是 Go 语言的时区处理机制比较特殊。Go 在程序启动时只读取一次时区信息,之后修改 TZ 环境变量是无效的:
问题
go
func main() {
// 程序启动时已经读取了系统时区并缓存
fmt.Println("初始时区:", time.Now().Location()) // Asia/Shanghai
// 这行代码不会生效!
os.Setenv("TZ", "UTC")
fmt.Println("修改TZ后:", time.Now().Location()) // 仍然是 Asia/Shanghai
}
解决方案
方案1:在程序启动前设置环境变量
shell
# 在启动程序时设置
TZ=UTC go run main.go
方案2:使用固定的时区(推荐)
go
func main() {
// 强制设置全局时区为 UTC
loc, err := time.LoadLocation("UTC")
if err != nil {
panic(err)
}
time.Local = loc
fmt.Println("设置后 time.Now():", time.Now()) // 显示为 UTC 时间
fmt.Println("时区:", time.Now().Location()) // UTC
}
方案3:使用特定时区创建时间
go
func main() {
utcLoc, _ := time.LoadLocation("UTC")
shanghaiLoc, _ := time.LoadLocation("Asia/Shanghai")
// 使用特定时区创建时间
nowUTC := time.Now().In(utcLoc)
nowShanghai := time.Now().In(shanghaiLoc)
fmt.Println("UTC时间:", nowUTC)
fmt.Println("上海时间:", nowShanghai)
}
插入数据库的时候时区会自动转行么?
场景:比如数据库时区是东八区,dsn也是东八区,gorm插入数据库的时候插入的时间是UTC时间,数据库的值应该是什么?
答案:go会自动转换成东八区的时间,所以数据库的值也是对应的东八区的时间。
也就是说,当你dsn的时间和代码中的Time如果不一样的话,则会自动转换成dsn对应的时间点!!!
go
package main
import (
"fmt"
"time"
"gorm.io/driver/mysql"
"gorm.io/gorm"
"gorm.io/gorm/logger"
)
type TestTime struct {
ID uint
EventTime time.Time
CreatedAt time.Time
}
func main() {
// 使用 loc=UTC
//dsnUTC := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=UTC"
//dbUTC, _ := gorm.Open(mysql.Open(dsnUTC), &gorm.Config{
// Logger: logger.Default.LogMode(logger.Info), // 设置日志级别为Info,显示所有SQL
//})
// 使用 loc=Local (假设本地是东八区)
dsnLocal := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Asia%2FShanghai"
dbLocal, _ := gorm.Open(mysql.Open(dsnLocal), &gorm.Config{
Logger: logger.Default.LogMode(logger.Info), // 设置日志级别为Info,显示所有SQL
})
// 时间差
fmt.Printf("Local时间: %v\n", time.Now())
utcLoc, _ := time.LoadLocation("UTC")
nowUTC := time.Now().In(utcLoc)
fmt.Printf("UTC时间: %v\n", nowUTC)
dbLocal.Table("test_time").Create(&TestTime{
EventTime: nowUTC,
})
}
GORM 的时间处理机制
我来总结一下,也就是说,sql打印的时间都是来源于go的处理,至于如何处理,主要是跟loc有关,如果Time和loc时间分区不一致,就转成loc的时间。 其实很简单!!!
时间转换确实在 Go 中完成
当你在 GORM 中插入数据时,所有的时间转换都在 Go 层面完成,包括:
- 你显式设置的字段(如 event_time)
- 自动生成的字段(如 created_at, updated_at)
created_at 的时间来源
created_at 字段的值取决于:
go
// GORM 内部处理 created_at 的伪代码
func (db *DB) Create(value interface{}) *DB {
// 获取当前时间
now := time.Now() // 关键!这里使用 Go 程序的当前时间
// 如果模型有 CreatedAt 字段,设置时间
if hasCreatedAtField(value) {
setCreatedAtField(value, now)
}
// 在生成 SQL 时,根据 DSN 的 loc 设置进行时区转换
sql := generateSQL(value) // 这里时间会被格式化为字符串
return db
}
DSN 中 loc 参数的作用
DSN 中的 loc 只影响:
- 查询时的解析:如何将数据库返回的时间字符串解析为 time.Time
- 写入时的格式化:如何将 time.Time 格式化为 SQL 字符串