Timestamp2Date
← Back to blogs
•Unix TimestampGoTime

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

  1. Treating local display time as a different instant.
  2. Mixing seconds and milliseconds in one DB column.
  3. Parsing non-timezone strings and assuming they are UTC.
  4. Using timestamp units implicitly in API contracts.
  5. Using YYYY-MM-DD in Go Format/Parse layouts.

Practical naming convention

Use unit-explicit names:

  • created_at_sec
  • updated_at_ms
  • expires_at_sec

Useful tools