How to use time package
You are building a background worker that syncs data from a remote API. The network is unreliable. You need to retry the request after a delay, but if the server does not respond within a few seconds, you must abort and return an error. You also need to log exactly how long the operation took and record the timestamp for auditing. In other languages, you might juggle separate libraries for dates, durations, and sleep functions. Go provides the time package, which unifies these concerns with a strict type system. Durations are typed integers. Times are structs that carry location and monotonic clock data. There is no magic. There is only nanoseconds and reference layouts.
Durations are integers, times are structs
The time package defines two core types. time.Duration is a typed int64 representing nanoseconds. time.Time is a struct that holds a wall clock reading, a location, and a monotonic clock value.
Because durations are integers, you can perform arithmetic directly. The package exports constants like time.Nanosecond, time.Millisecond, time.Second, time.Minute, and time.Hour. You can combine them freely. 1*time.Hour + 30*time.Minute evaluates to a duration of 90 minutes. This design keeps the API small and predictable. You do not need helper functions to add minutes to a duration. You just add the integers.
time.Time is a value type. Copying a time is cheap and safe. Methods on time.Time use the receiver name t. The convention is (t Time) MethodName(...). You will see this pattern throughout the standard library.
Durations are integers. Math is cheap.
Minimal example
Here is the core loop: capture a moment, wait, measure the gap.
package main
import (
"fmt"
"time"
)
func main() {
// Capture start time with monotonic clock for accurate measurement
start := time.Now()
// Sleep blocks the goroutine for exactly 1 second
time.Sleep(1 * time.Second)
// Since calculates duration from start to now using monotonic clock
elapsed := time.Since(start)
fmt.Println(elapsed)
}
time.Now() returns a time.Time struct. Inside this struct, Go stores the wall clock time and a monotonic clock reading. The monotonic clock is a hardware counter that only moves forward. It is immune to NTP adjustments or manual clock changes. time.Sleep accepts a time.Duration. The runtime puts the current goroutine to sleep for that many nanoseconds. When the sleep ends, the scheduler wakes the goroutine. time.Since subtracts the argument from time.Now(). Because both times have monotonic readings, the subtraction uses the monotonic clock, giving you an accurate elapsed time even if the system clock jumped during the sleep.
Timers and deadlines
Real code rarely just sleeps. You usually need to wait for an event with a deadline. This pattern combines a channel and a timer using select.
// PollResult represents the outcome of a check
type PollResult struct {
Value string
Err error
}
// PollWithTimeout attempts to get a value from a channel within a deadline
func PollWithTimeout(ch <-chan PollResult, timeout time.Duration) (PollResult, error) {
// Create a timer that fires once after the duration
timer := time.NewTimer(timeout)
defer timer.Stop()
select {
case result := <-ch:
// Channel received data before timer expired
return result, nil
case <-timer.C:
// Timer fired first, operation timed out
return PollResult{}, fmt.Errorf("poll timed out after %v", timeout)
}
}
time.NewTimer creates a timer that sends the current time on channel C after the duration. defer timer.Stop ensures the timer is cancelled if the function returns early. This prevents a goroutine leak. If you do not stop a timer, the runtime keeps a goroutine alive until the timer fires. In a long-running server, forgotten timers accumulate and consume memory. The select statement waits on both the channel and the timer. Whichever is ready first wins. If the channel sends data, the timer is stopped and the result returns. If the timer fires first, the function returns an error.
Stop your timers. Leaks are silent until they crash.
Formatting and parsing
Go's approach to formatting is unique. Other languages use format codes like %Y-%m-%d. Go uses a reference time. The layout string is the reference time formatted the way you want the output. The reference time is Mon Jan 2 15:04:05 MST 2006. This is January 2, 2006, at 3:04:05 PM. The numbers correspond to parts of the date. 2006 is the year. 1 is the month. 2 is the day. 15 is the hour. 04 is the minute. 05 is the second.
Here is how to parse and format a timestamp.
package main
import (
"fmt"
"time"
)
func main() {
// Layout must match the reference time structure
layout := "2006-01-02 15:04:05"
input := "2024-05-20 14:30:00"
// Parse returns a time in UTC if no timezone is specified
t, err := time.Parse(layout, input)
if err != nil {
// Handle parsing error
panic(err)
}
// Format converts back to string using the layout
output := t.Format(layout)
fmt.Println(output)
}
time.Parse interprets the input string according to the layout. If the layout lacks timezone information, time.Parse returns a time in UTC. This can cause subtle bugs if you expect local time. Always include timezone in the layout when parsing user input, or explicitly convert the result. time.RFC3339 is a predefined layout for ISO 8601 timestamps. Use it for APIs and databases. The compiler rejects type mismatches. cannot use 1 (untyped int constant) as time.Duration value in argument appears if you pass a plain integer where a duration is expected. You must multiply by a duration constant. time.Sleep(1) fails. time.Sleep(1 * time.Second) works.
The reference time is a layout, not a format string.
Monotonic clocks and wall clocks
time.Now() returns a time with a monotonic clock reading attached. This is important for measuring elapsed time accurately. The monotonic clock measures intervals. The wall clock measures moments. If you format a time, you lose the monotonic part. t.Format(...) returns a string representation of the wall clock time only. If you parse that string back, the resulting time has no monotonic reading.
Comparing times with == checks both wall clock and monotonic clock. If one time has a monotonic reading and the other does not, == returns false even if the wall clock times are identical. Use time.Equal to compare wall clock times only. time.Equal ignores the monotonic clock. This is useful when comparing times that came from different sources, such as a parsed string versus a time.Now() call.
When you need to send a time over the network or store it in a database, strip the monotonic clock. time.Now().UTC() returns a time in UTC with no monotonic reading. This ensures the time serializes correctly. If you pass a time with a monotonic clock to a JSON encoder, the encoder strips it automatically. However, explicit conversion makes the intent clear.
Monotonic clocks measure intervals. Wall clocks measure moments.
Pitfalls and errors
Parsing errors are common. The compiler rejects invalid layouts at runtime. parsing time "..." as "...": cannot parse "..." as "..." indicates a mismatch between the input string and the layout. Check the reference time carefully. 1 is the month, 2 is the day. Swapping them is a frequent mistake.
Goroutine leaks happen when you create a timer or ticker and never stop it. time.After creates a timer and returns a channel. The timer is not exposed, so you cannot stop it. Use time.After only for quick one-off timeouts where the duration is short and the goroutine leak is acceptable. For long-running loops, use time.NewTimer and stop it manually.
Comparing times across time zones requires care. time.Time stores the location. Two times can represent the same instant but have different locations. t1.Equal(t2) handles this correctly. t1 == t2 does not. If you need to compare instants, use Equal. If you need to compare wall clock times in the same location, == works.
Context is plumbing. Run it through every long-lived call site.
Decision matrix
Use time.Sleep when you need to pause a goroutine for a fixed duration and do not need to cancel the wait.
Use time.Timer when you need a one-shot deadline that can be cancelled or reset.
Use time.Ticker when you need to perform an action repeatedly at fixed intervals.
Use time.After when you want a quick one-off timeout in a select statement and do not care about stopping the timer.
Use time.Now().UTC() when you need to serialize a time value for storage or transmission.
Use time.Since when measuring elapsed time from a past moment.
Use time.Parse with explicit layouts when reading timestamps from external sources.
Trust the monotonic clock for intervals. Trust the wall clock for display.