How to Get Unix Timestamp in Go

Use `time.Now().Unix()` to get the current Unix timestamp in seconds, or `time.Now().UnixNano()` for nanoseconds.

The drifting log problem

You are shipping a service that records every user action. Each API request gets a timestamp so you can replay events later or calculate latency across microservices. You save the numbers to a database, deploy to production, and everything looks fine until you compare logs from a server in Tokyo with logs from a server in New York. The numbers drift. Or you try to subtract two timestamps and get a duration that makes no sense because one was stored as seconds and the other as milliseconds. Go makes capturing the current time trivial, but the time package hides a few implementation details that trip up beginners.

What a Unix timestamp actually is

A Unix timestamp is a simple counter. It tracks how many seconds have passed since midnight UTC on January 1, 1970. That starting point is called the Unix epoch. The counter is an integer. It ignores time zones, leap seconds, and daylight saving rules. It just grows.

Think of it like a factory production line counter. The machine starts at zero on a specific date and clicks up by one every second. You do not need to know what time zone the factory is in. You just need the number to know exactly how long the machine has been running. In Go, you rarely touch that raw integer directly. You work with time.Time, a struct that holds the instant plus metadata about the clock source. When you call .Unix() on that struct, Go converts the instant to the integer count. The conversion handles all the timezone math internally. The result is always relative to UTC, regardless of your machine's local settings.

Integers do not carry timezone data. Convert early, convert late, but carry time.Time in the middle.

Getting the current timestamp

Here is the simplest way to capture a timestamp. You ask for the current instant, then extract the integer representation.

package main

import (
	"fmt"
	"time"
)

func main() {
	// time.Now captures the current instant with monotonic clock data
	now := time.Now()

	// Unix returns seconds since epoch as int64
	seconds := now.Unix()
	fmt.Printf("Seconds: %d\n", seconds)

	// UnixNano returns nanoseconds since epoch as int64
	nanos := now.UnixNano()
	fmt.Printf("Nanoseconds: %d\n", nanos)
}

Both Unix() and UnixNano() return an int64. The Unix() method gives you seconds. The UnixNano() method gives you nanoseconds. Go 1.17 added UnixMilli() for millisecond precision. The method names are explicit because precision matters when you hand the value to a database or an external API.

Inside time.Time: wall clock and monotonic clock

When you call time.Now(), Go queries the operating system and gets two values. The first is the wall clock time, the calendar date and time you see on your screen. The second is a monotonic clock reading, a hardware counter that only moves forward and is immune to system clock adjustments.

The time.Time struct stores both values side by side. The wall clock tells you what time it is. The monotonic clock tells you how much time has actually passed. When you call .Unix(), Go ignores the monotonic reading entirely. It takes the wall clock instant, adjusts for the local timezone if necessary, and calculates the offset from the Unix epoch.

The monotonic clock data is discarded during the conversion. Once you have the integer, the monotonic information is gone forever. If you convert a time.Time to a Unix timestamp and then back to a time.Time, the new struct has no monotonic clock data. Duration calculations using the recovered time will rely on the wall clock, which can jump backward or forward if NTP syncs the system clock. This is why Go developers keep time.Time values in memory for duration math and only convert to integers when crossing process boundaries.

Monotonic clocks measure time. Wall clocks label time. Keep them separate.

Parsing and storing in real code

Real code often involves converting between strings, timestamps, and time.Time values. Here is a round-trip that parses a string, extracts the timestamp, and reconstructs the time.

package main

import (
	"fmt"
	"time"
)

// parseAndStore demonstrates converting a string to a timestamp and back
func parseAndStore() {
	// Layout constants use the reference time Mon Jan 2 15:04:05 MST 2006
	// This layout matches ISO 8601 UTC format
	layout := "2006-01-02T15:04:05Z"

	// Parse converts the string to a time.Time in UTC
	t, err := time.Parse(layout, "2024-06-15T10:30:00Z")
	if err != nil {
		fmt.Println("Parse failed:", err)
		return
	}

	// Extract seconds for storage
	ts := t.Unix()
	fmt.Printf("Stored timestamp: %d\n", ts)

	// Recover the time.Time from the integer
	recovered := time.Unix(ts, 0)
	fmt.Printf("Recovered: %s\n", recovered.Format(layout))
}

func main() {
	parseAndStore()
}

Go's time parsing uses a reference time instead of format codes like %Y or %d. The reference time is Mon Jan 2 15:04:05 MST 2006. You write the layout by rearranging these parts. This looks unusual at first, but it is self-documenting. You can read the layout and see exactly what the output will look like. The community convention is to define layout constants at the package level so every developer uses the same format.

The compiler will not catch layout mismatches. If the layout does not match the string, time.Parse returns an error at runtime. Always check the error. The community convention is to handle the error immediately, usually with if err != nil { return err }. This boilerplate makes the unhappy path visible and prevents silent failures where a timestamp defaults to the Unix epoch.

Pitfalls and gotchas

A common mistake is assuming UnixNano() gives you milliseconds. It gives nanoseconds. If you need milliseconds, divide by a million or use UnixMilli(). Storing nanoseconds in a database column meant for seconds will break your queries and waste storage space.

Another trap is reconstructing a time.Time from a nanosecond timestamp. The function time.Unix(sec, nsec) takes two arguments: seconds since the epoch, and a sub-second remainder in nanoseconds. The nsec argument must be between 0 and 999,999,999. It is not the total nanoseconds.

If you pass the result of UnixNano() directly as the second argument to time.Unix, you get garbage. You must split the value into seconds and the remainder.

package main

import (
	"fmt"
	"time"
)

func main() {
	// UnixNano returns total nanoseconds since epoch
	totalNanos := time.Now().UnixNano()

	// WRONG: Passing total nanos as the nsec argument
	// nsec must be the sub-second remainder, 0 to 999999999
	// badTime := time.Unix(0, totalNanos) // This produces garbage

	// CORRECT: Extract seconds and nanoseconds separately
	sec := totalNanos / 1e9
	nsec := totalNanos % 1e9
	goodTime := time.Unix(sec, nsec)

	fmt.Printf("Correct reconstruction: %v\n", goodTime)
}

If you try to assign a time.Time to an int64 variable, the compiler rejects it with cannot use t (variable of struct type time.Time) as int64 value in assignment. You must call .Unix() explicitly. Go does not perform implicit conversions between types, even when the underlying representation seems compatible. This design choice prevents accidental precision loss and forces developers to think about what they are storing.

Precision matters for storage. Seconds are the standard convention for most databases and APIs. Milliseconds are common when interoperating with JavaScript or high-frequency logging. Nanoseconds are rarely needed for storage and can cause overflow issues in systems that use 32-bit integers. The int64 type in Go can hold nanosecond timestamps until the year 2262, but intermediate calculations or external systems might not share that luxury.

Pick the precision you need. Store the integer. Keep the struct.

When to use which method

Use time.Now().Unix() when you need a standard timestamp in seconds for databases or APIs where millisecond precision is unnecessary. Use time.Now().UnixMilli() when your system requires millisecond precision, such as JavaScript interoperability or high-frequency logging. Use time.Now().UnixNano() when you need maximum precision for scientific measurements or internal benchmarks, but verify your storage format can handle 64-bit integers without overflow. Use time.Time values directly in your Go code whenever possible to preserve timezone information and monotonic clock data for duration calculations. Use time.Unix(sec, nsec) when you need to reconstruct a time.Time from a stored integer timestamp, ensuring the nanosecond argument is the sub-second remainder.

Where to go next