If you've written any Go for a while, you know the time package is both elegant and... quirky. The infamous "reference time" (Mon Jan 2 15:04:05 MST 2006) still catches people off guard. But when it comes to Unix timestamps, Go actually shines. The API is clean, the footguns are fewer, and once you learn a few patterns, you'll wonder why other languages make it so hard.
Let's walk through what it's like to work with Unix timestamps in Go — from real APIs tossing 13-digit numbers at you, to formatting timestamps for users around the world.
The Basics: Unix Timestamp to time.Time
A Unix timestamp is just an integer — seconds (or milliseconds) since January 1, 1970 UTC. Converting one to a Go time.Time is straightforward:
import "time"
func main() {
// Timestamp in seconds (10 digits)
var ts int64 = 1719820800
t := time.Unix(ts, 0)
fmt.Println(t) // 2024-07-01 08:00:00 +0800 CST
}
Wait — why does it show 08:00:00? Because time.Unix() returns a time.Time in the local time zone of the machine. That's the default behavior. If you want UTC, just call .UTC():
t := time.Unix(ts, 0).UTC()
fmt.Println(t) // 2024-07-01 00:00:00 UTC
Seconds vs Milliseconds: The Eternal Confusion
A timestamp from a JavaScript frontend or a Java backend often comes in milliseconds (13 digits). Feed that directly into time.Unix() and you'll get a date tens of thousands of years in the future — way past your retirement.
// Wrong!
ts := 1719820800000 // milliseconds
t := time.Unix(ts, 0) // year ~56468 — not what you want
The fix is simple: time.UnixMilli() was added in Go 1.17.
t := time.UnixMilli(ts).UTC()
fmt.Println(t) // 2024-07-01 00:00:00 UTC
There's also time.UnixMicro() for microsecond precision. If you're stuck on an older Go version, just divide:
t := time.Unix(ts/1000, 0)
Formatting a Timestamp for Humans
Once you have a time.Time, formatting it is where Go's reference time layout catches everyone. Go doesn't use YYYY-MM-DD like every other language. You use 2006-01-02.
t := time.Unix(1719820800, 0).UTC()
// 2006 = year, 01 = month, 02 = day
fmt.Println(t.Format("2006-01-02")) // 2024-07-01
// 15 = 24-hour hour, 04 = minute, 05 = second
fmt.Println(t.Format("2006-01-02 15:04:05")) // 2024-07-01 00:00:00
// RFC3339 is a common standard
fmt.Println(t.Format(time.RFC3339)) // 2024-07-01T00:00:00Z
You don't need to memorize the reference time. Just remember this sequence:
Mon Jan 2 15:04:05 MST 2006
Or more practically: 2006-01-02 15:04:05. Once you've written it three or four times, the muscle memory kicks in.
Going the Other Way: time.Time to Unix Timestamp
Need to send a timestamp back to the frontend or store it in a database? time.Time has you covered:
t := time.Date(2024, 7, 1, 0, 0, 0, 0, time.UTC)
fmt.Println(t.Unix()) // 1719820800 (seconds)
fmt.Println(t.UnixMilli()) // 1719820800000 (milliseconds)
fmt.Println(t.UnixMicro()) // 1719820800000000 (microseconds)
fmt.Println(t.UnixNano()) // 1719820800000000000 (nanoseconds)
These methods are timezone-aware, meaning they return the absolute epoch time regardless of the time.Time's zone. No surprises there.
Parsing Strings Back to time.Time
When you receive a date string from an API or a CSV file, use time.Parse():
// Parse an RFC3339 string
t, err := time.Parse(time.RFC3339, "2024-07-01T00:00:00Z")
if err != nil {
panic(err)
}
fmt.Println(t.Unix()) // 1719820800
Custom formats work the same way — but with Go's layout:
// Parse "2024-07-01 00:00:00"
t, err := time.Parse("2006-01-02 15:04:05", "2024-07-01 00:00:00")
if err != nil {
panic(err)
}
Important: time.Parse() returns a time in UTC by default if no timezone is in the string. If you want it parsed as local time, use time.ParseInLocation():
loc, _ := time.LoadLocation("Asia/Shanghai")
t, _ := time.ParseInLocation("2006-01-02 15:04:05", "2024-07-01 08:00:00", loc)
fmt.Println(t.UTC()) // 2024-07-01 00:00:00 UTC
A Real-World Scenario: Parsing an API Response
Imagine you're hitting a REST API that returns timestamps in seconds, and you need to generate a human-readable report in your local timezone:
type ApiResponse struct {
CreatedAt int64 `json:"created_at"` // Unix seconds
Message string `json:"message"`
}
func formatTimestamp(ts int64) string {
t := time.Unix(ts, 0)
return t.Format("2006-01-02 15:04:05 (MST)")
}
func main() {
resp := ApiResponse{CreatedAt: 1719820800, Message: "Hello"}
fmt.Println(formatTimestamp(resp.CreatedAt))
// 2024-07-01 08:00:00 (CST) — on my machine
}
If your app runs on servers in different regions, consider always working in UTC internally and converting to local time only at the display layer. This prevents the classic "server in Virginia, users in Tokyo" off-by-day bug.
Common Mistakes and How to Avoid Them
1. Passing Milliseconds to time.Unix()
As covered above — always check if your timestamp is 10 digits (seconds) or 13 digits (milliseconds). A quick sanity check: if the year looks wildly wrong, you probably forgot UnixMilli().
2. Forgetting that time.Unix() Returns Local Time
t := time.Unix(1719820800, 0)
// This is local time, not UTC! Use .UTC() explicitly.
3. Misunderstanding time.Parse() Timezone Behavior
// This returns UTC, not local time!
t, _ := time.Parse("2006-01-02", "2024-07-01")
If you want to parse a date string as local time, use ParseInLocation. If you always want UTC (recommended for storage), time.Parse() actually helps you here.
4. Using time.Now() Without Considering Precision
fmt.Println(time.Now().Unix()) // seconds
fmt.Println(time.Now().UnixMilli()) // milliseconds
If you're generating timestamps for a primary key or ordering, milliseconds might not be enough under high concurrency. Consider monotonic clock readings or UUIDs instead.
Performance: Time Conversions Are Cheap
One thing Go gets right — time.Unix() and time.Time.Unix() are fast. They boil down to integer arithmetic plus a bit of math. You can happily convert millions of timestamps without worrying about GC pressure or hidden allocations.
Wrapping Up
Go's time package, despite its unusual layout format, provides a clean and predictable API for working with Unix timestamps. The key takeaways:
- Use
time.Unix()for seconds,time.UnixMilli()for milliseconds - Remember that
time.Unix()returns local time — call.UTC()explicitly when needed - The reference layout
2006-01-02 15:04:05is your friend, not your enemy - For string parsing,
time.Parse()defaults to UTC; useParseInLocationfor local time
Next time you're debugging a timestamp that seems off by hours or years, check the units first. Nine times out of ten, that's the culprit.
For quick timestamp checks without writing any code, the FastUnix Timestamp Converter is always handy.