Time in Go
Introduction
The time package offers all the needed for processing of time.
The most important are those two data types: Time and Duration. The Time type represents a time
instant (aka timestamp). To represent time interval might be used the Duration type. Both mentioned
types are described in this article.
Other than that, time package offers one function and two types for waiting the desired time interval
based on channel. For single-shot waiting is time.After
function. More complex waiting scenarios of waiting is supported either by
time.Timer or by
time.Ticker.
Time
Go keeps a time instant as a struct type time.Time. Time is represented with nanosecond precision.
Time can be accessed concurrently for reading purpose, therefore methods for decoding/unmarshalling
(e.g. from Gob, binary, JSON, or text) are not considered concurrency-safe. Time is almost an immutable
type, the only exception are the following methods of Time type (which are not concurrency-safe):
GobDecode,
UnmarshalBinary,
UnmarshalJSON,
and UnmarshalText.
Obtaining time
There are several ways of obtaining Time value from nowhere. Probably the most often used is
time.Now that returns the current time instant (inclusive
monotonic time) in the local timezone.
For explicit time instant construction from all basic timestamp elements (like year, month, day, etc.)
is available time.Date. Never try to pass nil instead of
valid location pointer (the last argument) as it will panic.
For extraction of Time out of textual representation might be used either
time.Parse or
time.ParseInLocation.
Both functions await two string arguments, layout schema and input text. It is recommended to use
pre-defined layout schema constants like time.RFC3339 and time.DateTime (representing
“2006-01-02T15:04:05Z07:00” and “2006-01-02 15:04:05”). The latter function has one more parameter,
the default value of Location to be used iff location cannot be parsed.
There are some differences in behavior of
ParseandParseInLocation. NamelyParseseems to always return possible timezone name (for “+01:00” is stored location of “CET”), but daylight might be ignored (for “CEST” is stored location of “CET” as well). ButParseInLocationsometimes returns anonymous timezones (for “+01:00” is stored location of “” but still having the right offset) and daylight is not ignored (for “CEST” is stored location of “CEST”).
Local Time might be also constructed from a duration since 1970-01-01T00:00Z:
Unix(duration of seconds and nanoseconds);UnixMicro(duration of microseconds);UnixMilli(duration of milliseconds).
Location (aka timezone)
Each time has associated so-called Location that maps time instant to the time zone in use at that time
together with the current time offset against UTC. For many locations the time offset varies depending
on whether daylight saving time is in use at the time instant.
The time package offers two Location “constants” Local and UTC.
Location might be also loaded by one of available timezone names (e.g. “CET” or “Europe/Prague”) via function
time.LoadLocation.
Value of Time might be used to construct another instance of the same instant in another timezone
using method time.Time.In. E.g. UTC time instant with
timestamp 2025-03-08T13:35Z might be moved to CET timezone with timestamp 2025-03-08T14:35+01.
Mind that both timestamps represent exactly the same time instant, but with two different time zones.
To extract abbreviated timezone name and offset (in seconds) might be used method
time.Time.Zone. Mind that many known timezone names
might map to the same timezone name returned by Zone method.
func TestZone(t *testing.T) {
instant := time.Date(2025, time.March, 8, 13, 35, 0, 0, time.UTC)
for _, timezoneName := range []string{"CET", "Europe/Prague", "Europe/Berlin", "Europe/Vienna"} {
loc, err := time.LoadLocation(timezoneName)
zone, offset = instant.In(loc).Zone()
required.Nil(t, err)
assert.Equal(t, "CET", zone)
assert.Equal(t, 3600, offset)
}
}
Wall time and monotonic time
OS provides both “wall clock” and “monotonic clock”. The main difference is that wall clock is subject to changes of clock synchronization while monotonic clock is not. Imagine, that your application is running just during the instant of transition from summer daylight saving time back to standard time in your current timezone. At this time, you might face one-hour difference between two consecutive wall clock values taken in just a second. This discontinuity might happen just for wall time measurement. A similar scenario might be when your PC synchronize time via internet using NTP that might cause skip of your operating system’s wall clock even by several seconds.
There are different use cases for wall time and monotonic time. Wall time is used for presentation of the current time to the used in a human-readable form, while monotonic time is used for time measurements.
Go does not split this into two separate APIs. The time instant returned by
time.Now always contains both wall time and monotonic time.
Some methods will be using wall time for time-telling operations, while another methods will be
utilizing monotonic time for time-measurement operations.
Values of Time created by parsing does not contain a monotonic time.
If you need to get rid of a monotonic time, you shall use either t.Round(0) or t.Trunc(0).
Time comparison and zero time value
The zero value of time is 0001-01-01T00:00:00.000000000Z. To detect that time has its zero value, one might
use time.Time.IsZero predicate method. It might be used to
check whether a time value was explicitly initialized or not.
Never use ==, because of monotonic time and location differences might fail time comparison pretty easily.
Rather always use time.Equal method that prefers comparison of monotonic time (iff both compared Time
values has monotonic time) over wall time comparison. Anyway, it always ignores a location of time value.
Although time is comparable type, it might not be used as map key without time normalization. As one might expect,
time normalization should consist from two separate steps: location normalization and strip off monotonic time
(Trunc or Round function and then UTC method).
Implementation details
Time is implemented by a struct that is based on two 64b integers and a pointer to Location
(or nil for UTC). The following illustration presents Time exclusive monotonic time:
|-|---------------------------------|------------------------------|
|0|000000000000000000000000000000000| wall time nanoseconds [30b] |
|-|---------------------------------|------------------------------|
|------------------------------------------------------------------|
| wall time in seconds since 0001-01-01 [64b] |
|------------------------------------------------------------------|
|------------------------------------------------------------------|
| location pointer |
|------------------------------------------------------------------|
Representation of Time inclusive monotonic time:
|-|---------------------------------|------------------------------|
|1| wall time seconds since 1885 | wall time nanoseconds [30b] |
|-|---------------------------------|------------------------------|
|------------------------------------------------------------------|
| monotonic time nanoseconds since process start [64b] |
|------------------------------------------------------------------|
|------------------------------------------------------------------|
| location pointer |
|------------------------------------------------------------------|
Duration
Duration represents the elapsed time between two instants as nanosecond count represented as int64-based
defined type. The selected representation limits the largest representable duration to approximately 290 years.
One might use pre-defined constants from time.Nanosecond to time.Hour to define magic constants of Duration.
Duration might be also obtained by:
time.Time.Submethod as a difference between twoTimevalues;time.Time.Sincemethod returning time elapsed sinceTimereceiver (a shorthand fortime.Now().Sub(t));time.Time.Untilmethod returning duration untilTimereceiver (a shorthand fort.Sub(time.Now())).
Always use [time.Duration.Abs] for absolute value of the Duration as it handles edge case of minimum value.
Summary
A small reminder on usage of Time (t0 and t1), Duration (d) and Location (l):
- Always use
t0.Equal(t1)for equality checks (as location and monotonic time affects comparison by==). - For comparison two time instants on a time axis are prepared methods
t0.Before(t1)andt1.After(t0). - All methods for changing location (namely
t0.UTC(),t0.Local(),t0.In(time.UTC),t0.In(time.Local), and the most general formt0.In(l)) keeps the same instant (so they are usable for normalization). - To strip of monotonic time use
t0.Round(0)ort0.Truncate(0). - Never use concurrently decoding/unmarshalling methods (namely
t0.GobDecode(data),t0.UnmarshalBinary(data),t0.UnmarshalJSON(data),t0.UnmarshalText(data)). - Always normalize
Timeused asmapkeys usingt0.UTC().Truncate(0). - To get timezone name and/or offset used by the current location in seconds, one might use
t0.Zone(). - To check if monotonic time is included or not might be used
t0.Truncate(0) == t0. - To get the time interval (as a
Duration) between two instants uset0.Sub(t1). - For simple time shifts might be used
t0.Add(d)ort0.Add(10*time.Minute). - For more complex time shifts might be used
t0.AddDate(yearShift, monthShift, dayShift), but be careful as adding a month to the last day of October will return 1st December (aka normalized form). - To strip of time information use
t0.Date()(extraction of date). - To strip of date information use
t0.Clock()(extraction of time of the day). - To extract year and week of the year use
t0.ISOWeek(). - For extraction of day of the year is prepared
t0.YearDay().