Go 的 time.Format 使用固定参考时间“Mon Jan 2 15:04:05 MST 2006”定义格式字符串,大小写敏感且不支持常见模板如 yyyy-MM-dd;需注意 nil 指针解引用 panic、时区默认为 Local、性能敏感场景慎用及建议格式常量化。

time.Format 的格式字符串不是任意写的
Go 的 time.Format 不接受类似 "yyyy-MM-dd HH:mm:ss" 这种常见模板,它用的是固定参考时间 Mon Jan 2 15:04:05 MST 2006(即 Go 的诞生时刻)——所有格式动词都对应这个时间点的值。写错一个数字或大小写,结果就完全不对。
比如想输出 2024-03-15 14:23:07,得用 "2006-01-02 15:04:05",而不是 "YYYY-MM-DD hh:mm:ss"。大小写敏感:"M" 是月份数字(1–12),"m" 是分钟;"Y" 是四位年份,"y" 是两位年份(如 24)。
-
"2006-01-02T15:04:05Z07:00"→ ISO8601 带时区偏移(推荐用于 API 传输) -
"2006-01-02 03:04:05 PM"→ 12 小时制带 AM/PM -
"Jan 2, 2006 at 3:04pm"→ 英文本地化风格(注意"pm"小写) - 中文环境若需“2024年03月15日”,只能拼接:
t.Format("2006年01月02日"),Go 原生不支持语言感知格式
time.Format 会 panic 如果传入 nil time.Time
这是新手高频踩坑点:time.Time 是值类型,但零值是 time.Time{},调用 .Format() 不 panic;真正 panic 的是当你把一个未初始化的指针 *time.Time 解引用后调用 —— 比如从 JSON 反序列化得到一个可能为 null 的字段,你没判空就直接 t.Format(...),就会触发 panic: runtime error: invalid memory address。
- 检查是否为零值:
if t.IsZero() { /* 处理空时间 */ } - 处理指针:
if t != nil && !t.IsZero() { s := t.Format(...) } - API 返回默认时间比 panic 更友好:
if t.IsZero() { return "N/A" }
时区问题:Format 默认用 Local,但 time.Time 内部存的是 UTC
time.Time 底层始终以纳秒精度存储自 Unix epoch 起的 UTC 时间,.Format() 只是按当前时区(通常是 Local)渲染字符串。如果你从数据库读出一个带时区的时间(如 PostgreSQL 的 TIMESTAMP WITH TIME ZONE),Go 的 Scan 会自动转成 Local;但若原始数据是 UTC 字符串(如 "2024-03-15T06:23:07Z"),用 time.Parse(time.RFC3339, s) 得到的是 UTC 时间,再调用 .Format(...) 会按本地时区显示 —— 可能差 8 小时。
- 强制按 UTC 格式化:
t.UTC().Format("2006-01-02 15:04:05") - 指定其他时区:
t.In(loc).Format(...),其中loc, _ := time.LoadLocation("Asia/Shanghai") - 避免隐式转换:在日志或存储前统一转成 UTC 或明确时区,别依赖系统默认 Local
性能敏感场景慎用 Format,考虑预计算或字符串拼接
time.Format 内部涉及大量字符串构建和格式解析,压测下比直接拼接慢 3–5 倍。如果是在高频日志打点、HTTP 响应头生成等场景,且格式固定(如 "2006-01-02"),可提前用 time.Date 截断再格式化,或用 fmt.Sprintf 拼接:
year, month, day := t.Date()
s := fmt.Sprintf("%d-%02d-%02d", year, int(month), day)但要注意:手动拼接无法处理时区转换、闰秒、夏令时等逻辑,仅适用于简单日期截取类需求。真正需要时区/历法语义的地方,还是得用 Format。
最常被忽略的是格式字符串的硬编码位置 —— 它散落在业务代码里,一旦多处使用同一格式,后续改格式就得全局搜 "2006-01-02",建议定义为常量:const LayoutDate = "2006-01-02"。


















