Epoch
Unix epoch seconds and nanoseconds, converting back with time.Unix, and best practices for storing timestamps in databases.
The Unix epoch is January 1, 1970 UTC. Go's time package provides direct access to epoch timestamps and can convert them back to time.Time values.
t.Unix() returns the number of seconds elapsed since the Unix epoch. t.UnixMilli() and t.UnixNano() return milliseconds and nanoseconds respectively. These are plain int64 values - portable across every language and database.
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println(now.Unix()) // e.g. 1714000000
fmt.Println(now.UnixMilli()) // milliseconds
fmt.Println(now.UnixNano()) // nanoseconds
// Construct a specific time
t := time.Date(2024, 1, 1, 0, 0, 0, 0, time.UTC)
fmt.Println(t.Unix()) // 1704067200
}Convert an epoch integer back to time.Time with time.Unix(sec, nsec). Always call .UTC() immediately to ensure the result is in UTC, regardless of the server's local timezone.
package main
import (
"fmt"
"time"
)
func main() {
// Seconds since epoch → time.Time
ts := int64(1704067200)
t := time.Unix(ts, 0).UTC()
fmt.Println(t) // 2024-01-01 00:00:00 +0000 UTC
// Milliseconds since epoch → time.Time
ms := int64(1704067200000)
tms := time.UnixMilli(ms).UTC()
fmt.Println(tms) // 2024-01-01 00:00:00 +0000 UTC
// Nanoseconds
ns := int64(1704067200000000000)
tns := time.Unix(0, ns).UTC()
fmt.Println(tns)
}Measure elapsed time precisely by subtracting two epoch values. For benchmarking short operations, prefer nanoseconds. For human-readable durations, convert the difference to a time.Duration.
package main
import (
"fmt"
"time"
)
func doWork() {
time.Sleep(10 * time.Millisecond)
}
func main() {
start := time.Now().UnixNano()
doWork()
end := time.Now().UnixNano()
elapsedNs := end - start
elapsed := time.Duration(elapsedNs)
fmt.Printf("elapsed: %v\n", elapsed) // e.g. 10.123ms
// Prefer time.Since for this - it's cleaner
t := time.Now()
doWork()
fmt.Printf("elapsed: %v\n", time.Since(t))
}In production
Store timestamps as UTC Unix seconds (int64) in databases for maximum portability - every language and database understands them, they sort correctly, and they never drift with timezone changes. Convert to time.Time in UTC immediately after reading from storage, and format for display only at the presentation layer. Never store a time.Time directly in SQL without UTC normalization: Go's default time.Time.String() includes the local timezone, which produces inconsistent data if your service runs in multiple regions. Use t.UTC().Format(time.RFC3339) for string storage or a UTC-aware SQL driver. When receiving epoch timestamps from external APIs, confirm whether the value is in seconds or milliseconds - off-by-1000 bugs produce dates in 1970 or 55,000 years in the future, both of which slip through typical validation.
Enjoyed this? Get more essays on software craft delivered to your inbox.
Subscribe free