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 Parse and ParseInLocation. Namely Parse seems 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). But ParseInLocation sometimes 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.Sub method as a difference between two Time values;
  • time.Time.Since method returning time elapsed since Time receiver (a shorthand for time.Now().Sub(t));
  • time.Time.Until method returning duration until Time receiver (a shorthand for t.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) and t1.After(t0).
  • All methods for changing location (namely t0.UTC(), t0.Local(), t0.In(time.UTC), t0.In(time.Local), and the most general form t0.In(l)) keeps the same instant (so they are usable for normalization).
  • To strip of monotonic time use t0.Round(0) or t0.Truncate(0).
  • Never use concurrently decoding/unmarshalling methods (namely t0.GobDecode(data), t0.UnmarshalBinary(data), t0.UnmarshalJSON(data), t0.UnmarshalText(data)).
  • Always normalize Time used as map keys using t0.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 use t0.Sub(t1).
  • For simple time shifts might be used t0.Add(d) or t0.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().

<
Previous Post
IEEE 754 vol. 1: Representations of floating-point values
>
Next Post
ASCII