引言:那个每个人都遇到过的 Bug
\n\n你是否曾在日志中看到过日期 0001-01-01 00:00:00 +0000 UTC,然后想:“这是从哪来的?我明明没有初始化啊!”?恭喜你,你遇到了 Go 中 time.Time 的零值。这是一个经典的陷阱,在 Stack Overflow 上获得了数千次浏览,并在全球范围内耗费了数百小时的调试时间。
在本文中,我们将探讨什么是 time.Time 的零值,为什么它的行为出乎意料,如何区分“空”日期和真实日期,以及最重要的是——如何避免在生产环境中踩坑。如果你使用 Go 编写代码并处理日期,这篇文章将拯救你于神经紧张之中。
\n\nGo 中的零值是什么,为什么 time.Time 是个特例
\n\n在 Go 中,每个类型都有一个零值。对于数字是 0,对于字符串是 "",对于指针是 nil。逻辑很简单:如果你声明了一个变量但没有初始化,它会得到一个“空值”。但对于 time.Time,情况要复杂得多。
time.Time 的零值并不是你可能期望的 Unix 时间戳 0(1970-01-01)。而是 0001-01-01 00:00:00 +0000 UTC。为什么?因为在内部,time.Time 存储的是自公元 1 年 1 月 1 日(公历)以来的纳秒数。这种表示中的零值就对应了公元 1 年。
package main\n\nimport (\n "fmt"\n "time"\n)\n\nfunc main() {\n var t time.Time\n fmt.Println(t) // 0001-01-01 00:00:00 +0000 UTC\n fmt.Println(t.Unix()) // -62135596800 (负数!)\n}\n\n\n看到问题了吗?如果你尝试检查 if t == 0 或将其与 Unix 零值进行比较,你的代码就会出错。让我们来看看 time.Time 零值的常见错误以及如何避免它们。
time.Time 零值的 5 大常见错误
\n\n1. 与 Unix 时间戳 0 进行比较
\n\n最常见的错误是尝试通过将时间与 time.Unix(0, 0) 进行比较来检查时间是否为“空”。这不会起作用,因为零值并不是 1970 年。
// ❌ 错误:永远不会为 true\nvar t time.Time\nif t == time.Unix(0, 0) {\n fmt.Println("时间为空")\n}\n\n// ✅ 正确:使用 IsZero() 方法\nif t.IsZero() {\n fmt.Println("时间为空")\n}\n\n\nIsZero() 方法是你的主要工具。它仅对零值返回 true,而不是对任何“较早”的日期。
2. JSON 序列化:意外的 null
\n\n当你将包含 time.Time 的结构体序列化为 JSON 时,零值会变成字符串 "0001-01-01T00:00:00Z"。这会破坏 API、前端和数据库。客户端会看到一个奇怪的日期,而不是 null。
type Event struct {\n Name string `json:"name"`\n StartTime time.Time `json:"start_time"`\n}\n\nfunc main() {\n e := Event{Name: "test"}\n data, _ := json.Marshal(e)\n fmt.Println(string(data))\n // {"name":"test","start_time":"0001-01-01T00:00:00Z"}\n // ❌ 应该是 null 或该字段应缺失\n}\n\n\n解决方案:使用指向 time.Time 的指针(*time.Time)或带有自定义序列化逻辑的自定义类型。
// ✅ 使用指针的选项\ntype Event struct {\n Name string `json:"name"`\n StartTime *time.Time `json:"start_time,omitempty"`\n}\n\n// ✅ 或使用带有 omitempty 的自定义类型\ntype CustomTime struct {\n time.Time\n}\n\nfunc (ct CustomTime) MarshalJSON() ([]byte, error) {\n if ct.IsZero() {\n return []byte("null"), nil\n }\n return json.Marshal(ct.Time)\n}\n\n\n3. 数据库(SQL)问题
\n\n当将零值 time.Time 插入 PostgreSQL 或 MySQL 时,你会得到日期 0001-01-01,这会导致范围约束错误(公元 1 年超出了允许的范围)。