API时间字段必须统一用UTC。所有存储、传输、解析、序列化均需强制归一化为UTC,避免依赖客户端时区;若需时区转换,须显式通过query参数声明并安全处理。
API 返回时间字段必须用 UTC,别信客户端传来的时区
Go 的
默认不带时区语义,一旦你用
解析或格式化,就埋下跨服务器、跨用户行为不一致的坑。API 是无状态契约,时间字段必须统一为 UTC 存储和传输。
所有数据库写入前,用
归一化;读出后也别自动转本地,直接返回 UTC 时间字符串(如
)
别依赖 HTTP Header 里的
或前端 JS 的
做服务端转换——它们不可靠、可伪造、且违反 REST 的幂等性原则
如果非要支持“按用户时区返回”,应在 query 参数里显式声明,例如
,并在 handler 中用
转换输出,但原始数据仍保持 UTC
解析客户端时间字符串时,
比
安全得多
前端传来的 ISO 8601 字符串可能带偏移(如
),也可能不带(如
)。后者在 Go 里默认按
解析,而你的服务器时区很可能不是上海或纽约。
永远优先用
强制按 UTC 解析,再根据业务逻辑决定是否转时区
如果客户端明确声明了时区(比如带
),可用
得到带 zone 的
,但它内部已做归一化,后续调
是安全的
警惕
这种写法——它会把日期塞进本地时区的零点,导致夏令时错位或跨天偏差
默认输出 UTC,但
的 JSON tag 可能悄悄改掉它
Go 标准库对
的 JSON 序列化默认走
方法,输出的是 UTC 时间字符串。但很多人加了
这类 tag,以为只是控制格式,其实它会绕过默认逻辑,改用
,而
默认用
——这就翻车了。
删掉所有含
的 struct tag,让标准序列化机制工作
真要自定义格式,重写
方法,确保调用的是
测试时抓包看响应体,确认字段值结尾是
而不是
,否则说明没走 UTC
日志和监控里的本地时间容易误导排查,
不够用
默认
输出的时间是本地时区,当你的服务部署在 UTC 服务器上,而运维在东八区看日志,时间戳差 8 小时,查问题时容易误判顺序或超时点。
立即学习
“
go语言免费学习笔记(深入)
”;
初始化日志时,用
包一层,重写
方法,在时间部分强制用
Prometheus metrics 的
是纳秒差值,不涉及时区,但如果你用
打点做告警阈值,务必确认所有节点 NTP 同步且未手动改时钟
用
打印调试信息可以,但上线前得删掉——它只适合临时定位,不该出现在生产日志路径里
事情说清了就结束。最麻烦的从来不是代码怎么写,而是团队里有人坚持“前端传啥我就存啥”,或者觉得“反正我们服务器都在上海,用 Local 没问题”。时区问题不会报错,只会悄悄让订单时间早 8 小时、让定时任务漏跑、让两个数据中心的日志对不上。
time.Timetime.Localt.UTC()"2024-05-20T08:30:00Z"Time-ZoneIntl.DateTimeFormat().resolvedOptions().timeZone?tz=Asia/Shanghaitime.LoadLocation()time.ParseInLocationtime.Parse"2024-05-20T16:30:00+08:00""2024-05-20T16:30:00"time.Localtime.ParseInLocation(layout, value, time.UTC)+08:00time.Parsetime.Time.UTC()time.Parse("2006-01-02", "2024-05-20")json.Marshaltime.Timetime.TimeMarshalJSONjson:"created_at,omitempty,time_rfc3339"Format()Format()time.Local,time_*MarshalJSONt.UTC().Format(time.RFC3339)Z+08:00log.SetFlags(log.LstdFlags | log.Lmicroseconds)loglog.SetOutput(&logWriter{w: os.Stderr})Writetime.Now().UTC().Format(...)time.Since()time.Now().Unix()time.Now().In(loc)