time: time.Parse's Location

144 views
Skip to first unread message

nrjo...@gmail.com

unread,
Aug 24, 2016, 9:45:18 AM8/24/16
to golang-nuts

I was running into issues with comparing time.Time objects in a unit test that would pass on a CI server, but fail on my local machine. I wanted to see if anyone could double check my understanding of how `time.Parse` treats locations (I posted on Stack Overflow about it if it's helpful to see), though I think I now understand why; I was initially confused by the fact that `time.Parse` wasn't setting "2017-08-15T22:30:00+00:00"'s timezone to be UTC, since the docs for `time.Parse` read: "In the absence of a time zone indicator, Parse returns a time in UTC."


I thought I was seeing inconsistencies with how an RFC 3339 time was interpreted on my local machine vs. on a CI server (and on the Go Playground), but came up with a few examples that may explain the behavior I was seeing. Please bear with my examples to see if I understand correctly!


(1) "2017-08-15T22:30:00+00:00" is interpreted as having 0 hour offset. If the system's local time has a 0 hour offset (such as UTC), then the Location in the parsed time.Time value has UTC as its location string. If the system's local time has any other offset, then the Location has an empty string for its location string. This is suggested by this line in the docs of Parse:

When parsing a time with a zone offset like -0700, if the offset corresponds to a time zone used by the current location (Local), then Parse uses that location and zone in the returned time. Otherwise it records the time as being in a fabricated location with time fixed at the given zone offset.

Since the CI server in question has its time set to UTC (as does the Go Playground), then UTC is set to be the location. Since my local machine does not have its timezone set to UTC, a "fabricated" location is set.


(2) 2017-08-15T22:30:00Z is interpreted as being UTC no matter what; the Z carries both the fact that there is a 0 hour offset, and that it is in UTC. Parsing such a string works the same way on my local machine as our CI server and the Go Playground.

Those two examples are shown here: https://play.golang.org/p/mYkMhS9sJT (with the output I see on my local machine included).

If all of that is correct, I guess my last question is: is +00:00 supposed to imply UTC? I'm assuming someone else has thought harder about this before, but I was a little confused by the wording in RFC 3339 here:

This differs semantically from an offset of "Z" or "+00:00", which imply that UTC is the preferred reference point for the specified time.

If anyone can confirm, I'd greatly appreciate it - thanks for the help!

Ian Lance Taylor

unread,
Aug 24, 2016, 10:16:00 AM8/24/16
to nrjo...@gmail.com, golang-nuts
On Tue, Aug 23, 2016 at 10:58 PM, <nrjo...@gmail.com> wrote:
>
> I was running into issues with comparing time.Time objects in a unit test
> that would pass on a CI server, but fail on my local machine. I wanted to
> see if anyone could double check my understanding of how `time.Parse` treats
> locations (I posted on Stack Overflow about it if it's helpful to see),
> though I think I now understand why; I was initially confused by the fact
> that `time.Parse` wasn't setting "2017-08-15T22:30:00+00:00"'s timezone to
> be UTC, since the docs for `time.Parse` read: "In the absence of a time zone
> indicator, Parse returns a time in UTC."

Note that timezones don't matter when comparing time.Time values,
assuming you use the Equal method.

As far as Parse returning UTC, whether there is a timezone indicator
or not is a function of the format string passed to Parse. It's not a
function of the string being parsed.


> I thought I was seeing inconsistencies with how an RFC 3339 time was
> interpreted on my local machine vs. on a CI server (and on the Go
> Playground), but came up with a few examples that may explain the behavior I
> was seeing. Please bear with my examples to see if I understand correctly!
>
>
> (1) "2017-08-15T22:30:00+00:00" is interpreted as having 0 hour offset. If
> the system's local time has a 0 hour offset (such as UTC), then the Location
> in the parsed time.Time value has UTC as its location string. If the
> system's local time has any other offset, then the Location has an empty
> string for its location string. This is suggested by this line in the docs
> of Parse:
>
> When parsing a time with a zone offset like -0700, if the offset corresponds
> to a time zone used by the current location (Local), then Parse uses that
> location and zone in the returned time. Otherwise it records the time as
> being in a fabricated location with time fixed at the given zone offset.
>
> Since the CI server in question has its time set to UTC (as does the Go
> Playground), then UTC is set to be the location. Since my local machine does
> not have its timezone set to UTC, a "fabricated" location is set.

Sounds right.

> (2) 2017-08-15T22:30:00Z is interpreted as being UTC no matter what; the Z
> carries both the fact that there is a 0 hour offset, and that it is in UTC.
> Parsing such a string works the same way on my local machine as our CI
> server and the Go Playground.
>
> Those two examples are shown here: https://play.golang.org/p/mYkMhS9sJT
> (with the output I see on my local machine included).
>
> If all of that is correct, I guess my last question is: is +00:00 supposed
> to imply UTC? I'm assuming someone else has thought harder about this
> before, but I was a little confused by the wording in RFC 3339 here:

No. A "Z" in the string being parsed implies UTC. +00:00 implies the
zero offset, which may have daylight savings time.

At least, that is my understanding.

Ian

nrjo...@gmail.com

unread,
Aug 24, 2016, 1:05:33 PM8/24/16
to golang-nuts, nrjo...@gmail.com
Thanks for the response!


On Wednesday, August 24, 2016 at 7:16:00 AM UTC-7, Ian Lance Taylor wrote:
On Tue, Aug 23, 2016 at 10:58 PM,  <nrjo...@gmail.com> wrote:
>
> I was running into issues with comparing time.Time objects in a unit test
> that would pass on a CI server, but fail on my local machine. I wanted to
> see if anyone could double check my understanding of how `time.Parse` treats
> locations (I posted on Stack Overflow about it if it's helpful to see),
> though I think I now understand why; I was initially confused by the fact
> that `time.Parse` wasn't setting "2017-08-15T22:30:00+00:00"'s timezone to
> be UTC, since the docs for `time.Parse` read: "In the absence of a time zone
> indicator, Parse returns a time in UTC."

Note that timezones don't matter when comparing time.Time values,
assuming you use the Equal method.

Interesting, the test in question was using `reflect.DeepEqual`, which explains why it failed.
 

As far as Parse returning UTC, whether there is a timezone indicator
or not is a function of the format string passed to Parse.  It's not a
function of the string being parsed.

Ah yes, I really mean `Parse` with `time.RFC3339` as the format string (the issue is actually coming from decoding a JSON string into a `time.Time` field, which expects a quoted string in RFC 3339 format - so I was conflating the two). However, I believe the implementation details of `Parse` do seem to affect the timezone indicator. If the timezone of the system happens to have the same offset as the offset present in the string being parsed, then the timezone indicator is set to the timezone of the machine; if not, it's a "fabricated location" with an empty string. So parsing the same string (and using the same format string) on different machines will return different timezone indicators.
 


> I thought I was seeing inconsistencies with how an RFC 3339 time was
> interpreted on my local machine vs. on a CI server (and on the Go
> Playground), but came up with a few examples that may explain the behavior I
> was seeing. Please bear with my examples to see if I understand correctly!
>
>
> (1) "2017-08-15T22:30:00+00:00" is interpreted as having 0 hour offset. If
> the system's local time has a 0 hour offset (such as UTC), then the Location
> in the parsed time.Time value has UTC as its location string. If the
> system's local time has any other offset, then the Location has an empty
> string for its location string. This is suggested by this line in the docs
> of Parse:
>
> When parsing a time with a zone offset like -0700, if the offset corresponds
> to a time zone used by the current location (Local), then Parse uses that
> location and zone in the returned time. Otherwise it records the time as
> being in a fabricated location with time fixed at the given zone offset.
>
> Since the CI server in question has its time set to UTC (as does the Go
> Playground), then UTC is set to be the location. Since my local machine does
> not have its timezone set to UTC, a "fabricated" location is set.

Sounds right.

Thanks for clarifying, this is the crux of what I had been confused by.
 

> (2) 2017-08-15T22:30:00Z is interpreted as being UTC no matter what; the Z
> carries both the fact that there is a 0 hour offset, and that it is in UTC.
> Parsing such a string works the same way on my local machine as our CI
> server and the Go Playground.
>
> Those two examples are shown here: https://play.golang.org/p/mYkMhS9sJT
> (with the output I see on my local machine included).
>
> If all of that is correct, I guess my last question is: is +00:00 supposed
> to imply UTC? I'm assuming someone else has thought harder about this
> before, but I was a little confused by the wording in RFC 3339 here:

No.  A "Z" in the string being parsed implies UTC.  +00:00 implies the
zero offset, which may have daylight savings time.

At least, that is my understanding.

I see. For context - the `time.Time` I'm trying to parse is part of a JSON blob being created by PHP code and sent to a Go app. It appears (perhaps not shockingly) that PHP has a different interpretation of RFC 3339's serialization of a UTC timestamp, and just tacks on the "+00:00" instead of "Z." See this gist for an example. Sigh.

Nick

 

Ian

Matt Harden

unread,
Aug 24, 2016, 11:26:47 PM8/24/16
to nrjo...@gmail.com, golang-nuts
Note that ParseInLocation(..., time.UTC) should give you the same behavior in both test scenarios. If you have to use Parse, you can set time.Location = time.UTC, with the same result.

--
You received this message because you are subscribed to the Google Groups "golang-nuts" group.
To unsubscribe from this group and stop receiving emails from it, send an email to golang-nuts...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Matt Harden

unread,
Aug 24, 2016, 11:27:26 PM8/24/16
to nrjo...@gmail.com, golang-nuts
Sorry, that's time.Local = time.UTC.
Reply all
Reply to author
Forward
0 new messages