Let's say that you have a Go
time.Time
value,
tstamp
, that you happen to know for sure was collected at 18:00 and some seconds (in your time zone). Now suppose that you use
time.Time.Format()
on
tstamp
to get a string form of its hours and minutes (using the format string
"15:04"
). Will you always get the string '18:00' as the output from formatting
tstamp
this way?
Perhaps surprisingly, the answer is no; you aren't guaranteed to always get '18:00'. In theory you can get a wide variety of output instead of 18:00, although in practice often the most likely other output is whatever 18:00 in your time zone is in UTC.
What's going on is that Go
time.Time
values have a
Location
, also known as a time zone. When you format a
time.Time
into a string, it uses its location (time zone) to decide how to represent itself. So if
tstamp
's location is your local time zone, you get the 18:00 that you expect. If
tstamp
's location has wound up as UTC for some reason, you will get your local 18:00 in UTC, whatever that is. And so on.
Go's
time.Now()
specifically returns a
time.Time
that is located in local time, whatever that is, so if you have a timestamp that comes from that it will behave as you expect. However,
time.Time
values that you get from elsewhere may carry different locations along with them. In particular, if you decode a DateTime value from JSON (in Go), that time value may have carried with it a time zone which will be faithfully propagated into the
time.Time
value you get. Depending on what generated the JSON, that time zone may be UTC, it may be your local time zone, or it may be something else. This behavior may be surprising if your mental model of decoding a JSON date and time value is that you basically generate a Unix style seconds since epoch value that's neutral on time zones.
(JSON dates and times are commonly represented and decoded in RFC 3339 format or some variant of it.)
The solution is to say what you mean. If you want to format a
time.Time
value as local time, instead of whatever random time zone it came to you as, you should first make it in local time with
.Local()
. So instead of
tstamp.Format("15:04")
, use
tstamp.Local().Format("15:04")
. Similarly, if you want to format the time in UTC, say that with
.UTC()
. Unless you're sure you know what you're doing and where your
time.Time
values came from, using a bare
.Format()
is probably a mistake.
One potentially surprising place this can come up is in templates . If you have a
time.Time
value as a template variable, it feels natural to format it with (eg) '
{{ .StartsAt.Format "15:04" }}
'. But that's probably a mistake unless the time zone for the
StartsAt
template variable is explicitly documented. Instead, you want '
{{ .StartsAt.Local.Format
"15:04" }}
', which will avoid current and future surprises.