Introduction
Every real-world program eventually needs to deal with time: scheduling tasks, measuring latency, or enforcing deadlines. Go's time package gives you two core building blocks, time.Time for instants and time.Duration for elapsed spans, and understanding both is essential for writing correct, production-grade code.
Key Concepts
- time.Time: An immutable value representing a single instant, carrying both a wall-clock reading and a monotonic clock reading.
- time.Duration: A signed, nanosecond-precision span of time stored internally as an
int64. - Monotonic clock: A clock that only moves forward, unaffected by NTP adjustments or manual system clock changes.
Real World Context
Imagine you are building an HTTP server and you want to log how long each request takes. If the system clock jumps backward due to an NTP correction between the start and end of a request, a naive wall-clock subtraction would give a negative or wildly wrong duration. Go silently includes a monotonic reading in time.Now() so that time.Since(start) always returns an accurate elapsed time, even across clock adjustments.
Deep Dive
You create a time.Time with time.Now() for the current moment or time.Date() for a specific point in time.
gonow := time.Now() specific := time.Date(2024, time.January, 1, 12, 0, 0, 0, time.UTC)
Both calls return a time.Time. The time.Date constructor lets you pin down year, month, day, hour, minute, second, nanosecond, and location.
Durations are built by multiplying typed constants. Because time.Duration is nanosecond-based, you never work with raw integers. Instead you compose human-readable expressions.
god := 5 * time.Second d := 100 * time.Millisecond d := 2 * time.Hour + 30 * time.Minute
These expressions are type-safe. You cannot accidentally add a plain int to a time.Duration without an explicit conversion.
Go's time.Now() embeds a monotonic clock reading alongside the wall clock. Functions like time.Since and the Sub method automatically use the monotonic component when both operands carry one.
gostart := time.Now() // do work elapsed := time.Since(start) // Uses monotonic clock
This means your measurements are immune to NTP updates, leap-second adjustments, or any wall-clock skew.
Time arithmetic lets you shift instants forward or backward and compute the gap between two instants.
gofuture := now.Add(24 * time.Hour) past := now.Add(-1 * time.Hour) diff := t1.Sub(t2) // Returns Duration
The Add method shifts a time.Time, while Sub returns a time.Duration. These are the only arithmetic operations you need.
Comparing two time.Time values is straightforward.
got1.Before(t2) t1.After(t2) t1.Equal(t2)
Always prefer Equal over == because Equal correctly handles different time zone representations of the same instant.
Common Pitfalls
- Using
==instead ofEqual— Twotime.Timevalues can represent the same instant but carry different internal locations. The==operator compares locations too, so it may returnfalsefor logically identical times. - Multiplying durations by int variables — Writing
time.Second * nwherenis anintvariable will not compile. You needtime.Duration(n) * time.Second. - Ignoring monotonic stripping on serialization — The monotonic reading is stripped when you marshal a
time.Time(e.g., to JSON). If you unmarshal and then callSub, you get a wall-clock difference, not a monotonic one.
Best Practices
- Use
time.Since(start)for elapsed time — It reads the monotonic clock and is the idiomatic one-liner for benchmarking or logging durations. - Prefer typed duration constants over raw numbers — Write
5 * time.Secondinstead oftime.Duration(5000000000). The constants are self-documenting and prevent magnitude errors.
Summary
time.Timerepresents an instant;time.Durationrepresents a span.- Go transparently records a monotonic clock reading in
time.Now()to protect elapsed-time measurements. - Use
Add/Subfor arithmetic andEqual(not==) for comparison. - Build durations from typed constants like
time.Secondandtime.Millisecondfor clarity and safety.
Code Examples
d := 10 * time.Second
fmt.Println(d.Milliseconds()) // 10000
fmt.Println(d.String()) // 10s