time.time 默认无法解析自定义日期格式,因 json.unmarshal 硬编码仅支持 rfc3339 格式;需定义包装类型实现 unmarshaljson 方法,按优先级尝试多种 layout 并处理空值与时区。

为什么 time.Time 默认解析不了自定义日期格式
Go 的 json.Unmarshal 对 time.Time 有硬编码的默认格式:"2006-01-02T15:04:05Z07:00"(RFC3339)。只要 JSON 字段值不是这个格式,比如 "2024-05-20 14:30:00" 或 "2024/05/20",就会报错 cannot unmarshal string into Go struct field X of type time.Time。这不是 bug,是设计使然——标准库不猜格式。
用自定义类型 + UnmarshalJSON 方法接管解析逻辑
最直接可控的方式是定义一个包装类型,实现 UnmarshalJSON 方法。它接收原始字节,手动调用 time.Parse,并处理多种可能格式。
常见做法:
- 定义新类型如
CustomDate,底层仍为time.Time - 在
UnmarshalJSON中按优先级尝试多个 layout,例如先试"2006-01-02 15:04:05",再试"2006-01-02" - 遇到空字符串或
null时返回零值或错误,需明确策略(比如设为time.Time{}或返回 error) - 注意:必须用指针接收者(
func (d *CustomDate) UnmarshalJSON(data []byte)),否则无法修改原值
示例片段:
type CustomDate time.Time
func (d *CustomDate) UnmarshalJSON(data []byte) error {
s := strings.Trim(string(data), `"`)
if s == "" || s == "null" {
*d = CustomDate(time.Time{})
return nil
}
for _, layout := range []string{
"2006-01-02 15:04:05",
"2006-01-02T15:04:05Z07:00",
"2006-01-02",
} {
if t, err := time.Parse(layout, s); err == nil {
*d = CustomDate(t)
return nil
}
}
return fmt.Errorf("cannot parse %q as date", s)
}
结构体字段怎么用这个自定义类型
不能直接把 CustomDate 当作 time.Time 用——它没有 time.Time 的方法(如 .Format、.Add)。所以实际使用时有两种主流选择:
- 在结构体中声明为
CustomDate字段,读取后显式转成time.Time:time.Time(*d) - 嵌入
time.Time并重写UnmarshalJSON(更少侵入性,但需小心零值和方法集) - 如果项目大量使用,建议封装一个通用解析函数,配合
map[string]interface{}先解出原始字符串,再统一转换,避免每个结构体都重复定义类型
字段定义示例:
type Event struct {
ID int `json:"id"`
At CustomDate `json:"at"`
Title string `json:"title"`
}
注意时区和性能开销
每次反序列化都要遍历多个 time.Parse 尝试,虽快但非零成本。高频服务中若字段固定格式,应精简 layout 列表;若含时区信息(如 "2024-05-20 14:30:00+08:00"),layout 必须带时区标识(Z07:00 或 GMT-0700),否则解析失败且无提示。
另一个易忽略点:JSON 中日期若带毫秒("2024-05-20T14:30:00.123Z"),layout 要写成 "2006-01-02T15:04:05.000Z07:00",小数位数必须严格匹配,否则 time.Parse 返回 nil error。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











