Introduction: That bug that caught everyone
\n\nHave you ever seen the date 0001-01-01 00:00:00 +0000 UTC in your logs and thought: "Where did this come from? I didn't initialize anything!"? Congratulations, you've encountered the zero value of time.Time in Go. This is a classic pitfall that gathers thousands of views on Stack Overflow and hundreds of hours of debugging worldwide.
In this article, we'll break down what the zero value for time.Time is, why it behaves unexpectedly, how to distinguish an "empty" date from a real one, and most importantly — how to avoid stepping on this rake in production. If you write in Go and work with dates, this material will save you from a nervous tic.
\n\nWhat is a zero value in Go and why time.Time is a special case
\n\nIn Go, every type has a zero value. For numbers it's 0, for strings it's "", for pointers it's nil. The logic is simple: if you declare a variable without initialization, it gets a "dummy" value. But with time.Time, it's more complicated.
The zero value of time.Time is not Unix timestamp 0 (1970-01-01), as you might expect. It is 0001-01-01 00:00:00 +0000 UTC. Why? Because internally time.Time stores nanoseconds since January 1, year 1 (Gregorian calendar). Zero in this representation gives year 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 (negative number!)\n}\n\n\nSee the problem already? If you try to check if t == 0 or compare it with Unix zero, your code will break. Let's break down the top errors with zero value time.Time and ways to avoid them.
Top 5 errors with zero value of time.Time
\n\n1. Comparison with Unix timestamp 0
\n\nThe most common mistake is trying to check if the time is "empty" by comparing it with time.Unix(0, 0). This won't work because the zero value is not the year 1970.
// ❌ Error: will never be true\nvar t time.Time\nif t == time.Unix(0, 0) {\n fmt.Println("Time is empty")\n}\n\n// ✅ Correct: use the IsZero() method\nif t.IsZero() {\n fmt.Println("Time is empty")\n}\n\n\nThe IsZero() method is your main tool. It returns true only for the zero value, not for any "early" date.
2. JSON serialization: unexpected null
\n\nWhen you serialize a struct with time.Time to JSON, the zero value turns into the string "0001-01-01T00:00:00Z". This breaks APIs, frontends, and databases. Clients see a strange date instead of 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 // ❌ Should be null or the field should be absent\n}\n\n\nSolution: use a pointer to time.Time (*time.Time) or a custom type with serialization logic.
// ✅ Option with pointer\ntype Event struct {\n Name string `json:"name"`\n StartTime *time.Time `json:"start_time,omitempty"`\n}\n\n// ✅ Or custom type with 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. Problems with databases (SQL)
\n\nWhen inserting a zero time.Time into PostgreSQL or MySQL, you will get the date 0001-01-01, which will cause a range constraint error (year 1 is outside the valid range).