Unix Timestamp Go Examples
Unix timestamp is the number of elapsed time units since 1970-01-01T00:00:00Z (Unix epoch).
In Go, two rules prevent most bugs:
- normalize to UTC for storage and comparison
- be explicit about seconds vs milliseconds
Go Unix Timestamp Cheatsheet
Get current timestamp
package main
import (
"fmt"
"time"
)
func main() {
now := time.Now()
fmt.Println(now.Unix()) // seconds
fmt.Println(now.UnixMilli()) // milliseconds
fmt.Println(now.UnixMicro()) // microseconds
fmt.Println(now.UnixNano()) // nanoseconds
}
Convert timestamp to time.Time
package main
import (
"fmt"
"time"
)
func main() {
sec := int64(1772668800)
ms := int64(1772668800000)
fromSec := time.Unix(sec, 0).UTC()
fromMs := time.UnixMilli(ms).UTC()
fmt.Println(fromSec.Format(time.RFC3339))
fmt.Println(fromMs.Format(time.RFC3339))
}
Convert time.Time to timestamp
package main
import (
"fmt"
"time"
)
func main() {
t := time.Date(2026, 3, 5, 12, 0, 0, 0, time.UTC)
fmt.Println(t.Unix()) // seconds
fmt.Println(t.UnixMilli()) // milliseconds
}
Parse RFC3339 / ISO 8601 and get Unix seconds
package main
import (
"fmt"
"time"
)
func main() {
raw := "2026-03-05T12:00:00Z"
t, err := time.Parse(time.RFC3339, raw)
if err != nil {
panic(err)
}
fmt.Println(t.Unix())
}
Go format layout pitfall (2006-01-02)
Go does not use YYYY-MM-DD style tokens. It uses the reference time layout 2006-01-02 15:04:05.
package main
import (
"fmt"
"time"
)
func main() {
t := time.Unix(1772668800, 0).UTC()
fmt.Println(t.Format("2006-01-02 15:04:05")) // correct layout
fmt.Println(t.Format("YYYY-MM-DD")) // prints literal text: YYYY-MM-DD
}
Prefer time package predefined layouts whenever possible. In production code, avoid inventing custom layout strings unless you must match an external contract.
// Prefer built-in layouts from the time package.
// Avoid hand-writing custom layout strings unless an external contract requires it.
fmt.Println(t.Format(time.Layout)) // "01/02 03:04:05PM '06 -0700"
fmt.Println(t.Format(time.ANSIC)) // "Mon Jan _2 15:04:05 2006"
fmt.Println(t.Format(time.UnixDate)) // "Mon Jan _2 15:04:05 MST 2006"
fmt.Println(t.Format(time.RubyDate)) // "Mon Jan 02 15:04:05 -0700 2006"
fmt.Println(t.Format(time.RFC822)) // "02 Jan 06 15:04 MST"
fmt.Println(t.Format(time.RFC822Z)) // "02 Jan 06 15:04 -0700"
fmt.Println(t.Format(time.RFC850)) // "Monday, 02-Jan-06 15:04:05 MST"
fmt.Println(t.Format(time.RFC1123)) // "Mon, 02 Jan 2006 15:04:05 MST"
fmt.Println(t.Format(time.RFC1123Z)) // "Mon, 02 Jan 2006 15:04:05 -0700"
fmt.Println(t.Format(time.RFC3339)) // "2006-01-02T15:04:05Z07:00"
fmt.Println(t.Format(time.RFC3339Nano)) // "2006-01-02T15:04:05.999999999Z07:00"
fmt.Println(t.Format(time.Kitchen)) // "3:04PM"
fmt.Println(t.Format(time.Stamp)) // "Jan _2 15:04:05"
fmt.Println(t.Format(time.StampMilli)) // "Jan _2 15:04:05.000"
fmt.Println(t.Format(time.StampMicro)) // "Jan _2 15:04:05.000000"
fmt.Println(t.Format(time.StampNano)) // "Jan _2 15:04:05.000000000"
fmt.Println(t.Format(time.DateTime)) // "2006-01-02 15:04:05"
fmt.Println(t.Format(time.DateOnly)) // "2006-01-02"
fmt.Println(t.Format(time.TimeOnly)) // "15:04:05"
UTC vs local display
package main
import (
"fmt"
"time"
)
func main() {
ts := int64(1772668800)
t := time.Unix(ts, 0)
fmt.Println(t.UTC().Format(time.RFC3339))
fmt.Println(t.Local().Format(time.RFC3339))
}
A Unix timestamp represents one instant. Timezone only changes display text.
Normalize unknown unit input (sec/ms)
package main
import "fmt"
func toMilliseconds(ts int64) (int64, error) {
abs := ts
if abs < 0 {
abs = -abs
}
if abs >= 1e9 && abs < 1e10 {
return ts * 1000, nil // seconds
}
if abs >= 1e12 && abs < 1e13 {
return ts, nil // milliseconds
}
return 0, fmt.Errorf("unsupported or ambiguous timestamp unit")
}
This heuristic is intentionally tuned for current-era production data (roughly year 2001 to 2286 by digit length).
Very early timestamps near the Unix epoch or far-future second values beyond 10 digits can be misclassified.
For long-lived systems, carry explicit unit metadata instead of inferring by length.
Compare expiry safely
package main
import "time"
func isExpired(expiresAtSec int64) bool {
expireAt := time.Unix(expiresAtSec, 0)
return !time.Now().Before(expireAt) // equal or after
}
High-throughput note for time.Now()
time.Now() is highly optimized in modern Go runtimes, but it can still become a hotspot in very tight loops.
If your business rule only needs second-level precision for a batch, read once and reuse:
nowSec := time.Now().Unix()
for _, item := range items {
expired := nowSec >= item.ExpiresAtSec
_ = expired
}
If you need per-event sub-second accuracy, keep per-event time.Now() calls and measure with benchmarks.
Digit-length quick guide
- 10 digits: usually seconds
- 13 digits: usually milliseconds
- 16 digits: usually microseconds
- 19 digits: usually nanoseconds
Common mistakes
- Treating local display time as a different instant.
- Mixing seconds and milliseconds in one DB column.
- Parsing non-timezone strings and assuming they are UTC.
- Using timestamp units implicitly in API contracts.
- Using
YYYY-MM-DDin GoFormat/Parselayouts.
Practical naming convention
Use unit-explicit names:
created_at_secupdated_at_msexpires_at_sec