A subtle trap when formatting Go time.Time
values
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.
Comments on this page:
|
|