
go 应用启动后 time.Now() 返回 UTC 时间,不是宿主机时区
这是最常见现象:docker 默认使用 UTC 时区,哪怕宿主机设了 Asia/Shanghai,Go 程序里 time.Now() 依然输出 UTC 时间。根本原因不是 Go 有问题,而是容器没加载本地时区数据。
- Go 的
time包依赖系统/usr/share/zoneinfo/下的时区文件,镜像里通常不带或只带 UTC -
docker run -e TZ=Asia/Shanghai对 Go 无效——Go 不读TZ环境变量,只认系统时区数据库和/etc/localtime软链 - Alpine 镜像尤其容易踩坑:
apk add tzdata必须显式安装,且要配合cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime
Dockerfile 中正确挂载时区的三种写法(按推荐顺序)
关键不是“设置环境变量”,而是让 /etc/localtime 指向有效的时区文件,并确保 /usr/share/zoneinfo/ 存在对应数据。
- 方案一(推荐,debian/ubuntu 基础镜像):
FROM golang:1.22-bookworm RUN cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime—— 简单直接,
/usr/share/zoneinfo/已预装 - 方案二(Alpine):
FROM golang:1.22-alpine RUN apk add --no-cache tzdata && cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime——
--no-cache避免残留包,tzdata是必须的 - 方案三(运行时挂载,适合调试):
docker run -v /etc/localtime:/etc/localtime:ro your-app—— 依赖宿主机时区,但上线环境不建议,有耦合风险
time.LoadLocation() 用错导致 panic 或返回 nil
有人想绕过系统配置,直接在代码里加载时区:loc, _ := time.LoadLocation("Asia/Shanghai"),结果 loc 是 nil,后续 time.Now().In(loc) panic。
-
time.LoadLocation()严格依赖/usr/share/zoneinfo/目录结构,路径不对或文件缺失就失败,不会 fallback 到 UTC - 错误写法:
time.LoadLocation("Shanghai")(缺Asia/前缀)、time.LoadLocation("CST")(缩写不可靠,且非标准 IANA 名) - 安全做法:加判空 + 日志,例如
loc, err := time.LoadLocation("Asia/Shanghai") if err != nil || loc == nil { log.Printf("failed to load timezone: %v", err) loc = time.UTC }
容器内 date 命令显示正确,但 Go 仍返回 UTC
说明系统级时区已生效(/etc/localtime 挂对了),但 Go 进程启动时可能缓存了旧时区信息——尤其用了 fork/exec 或 hot-reload 场景。
立即学习“go语言免费学习笔记(深入)”;
- Go 在首次调用
time.Now()时初始化时区,之后不会自动刷新;如果容器启动后改了/etc/localtime,已有进程不会感知 - 验证方法:重启容器,再跑
go run -e 'package main; import ("fmt"; "time"); func main() { fmt.Println(time.Now()) }',看输出是否含 +0800 - CI/CD 构建时注意:多阶段构建中,如果 build 阶段和 final 阶段基础镜像不一致(比如 build 用 golang:alpine,final 用 scratch),final 镜像很可能没带
zoneinfo
时区问题不是“设个环境变量就完事”,核心在于容器镜像里有没有真实可用的时区数据文件,以及 Go 进程是否在有数据的前提下启动。很多人卡在 Alpine 镜像漏装 tzdata,或者 multi-stage 构建时把 /usr/share/zoneinfo 丢掉了。