Skip to content
鼓励作者:欢迎打赏犒劳

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 字符串

如有转载或 CV 的请标注本站原文地址