New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
time: Now() and time.unixTime() do not call setLoc, causing timezone to not be nil when system time is UTC #19486
Comments
CL https://golang.org/cl/38002 mentions this issue. |
@sbuss note that the "docs" you linked to are comments on an unexported field of time.Time, not part of the public documentation of the type. I've also noticed this in the following context: if you have a test that creates a timestamp with In this case the fix is to not write tests that use == on time.Times. But it is an interesting gotcha. |
I'm not persuaded. I don't think the time package needs to cater to people who explicitly set If we're going to make this change, it needs a better rationale than I see so far in this description. For example, it needs an explanation in terms of something else that matters and can be clearly understood, like #15716. |
I included
This is exactly how I discovered this issue, in fact. I was storing and retrieving some data to Google Cloud Datastore and doing a |
@ianlancetaylor well, more or less the same justification as #15716 applies here (it could be phrased as "Time.MarshalJSON and Time.UnmarshalJSON aren't symmetrical if the local timezone is UTC"): package main
import (
"fmt"
"log"
"time"
)
func main() {
t0 := time.Now()
s := t0.Format(time.RFC3339Nano)
t1, err := time.Parse(time.RFC3339Nano, s)
if err != nil {
log.Fatal(err)
}
fmt.Printf("t0==t1: %t\n", t0 == t1) // true, unless the location is UTC
fmt.Printf("t0: %#v\n", t0)
fmt.Printf("t1: %#v\n", t1)
} https://play.golang.org/p/xx0fU4gn45 The commit message for 5fbf35d that fixed #15716 is relevant. /cc @rsc |
@cespare Thanks, I'm fine with fixing that problem, but then let's update the CL to be about that problem, with an appropriate test, rather than a test that explicitly sets |
SGTM I will do this tomorrow |
While trying to write the test you recommended, I discovered another potential bug in the time marshaling functions (#19502). I'll wait until learning more in that bug before revising my patch for this issue. Edit: I figured out that that was intended behavior and know how to do this correctly. Should have a new patch soon. |
I can't write a test which uses |
Actually, I can't use @cespare's code snippet, either, because of the same issue with the monotonic clock. You'll always get different structs if the monotonic clock isn't disabled, but the act of disabling it hides the bug. @ianlancetaylor Is there something else I could do to the patch or tests to make you more in favor of it? Note that this issue doesn't show up on the golang playground yet because it's running a version of go that doesn't include the monotonic clock. |
If we consider the test example, in Go 1.9 it will fail in all timezones; there's nothing special about UTC. So the motivation here kind of goes away. Perhaps we should just leave it alone. |
I believe you can use |
Yup, that did it. Thanks for the pointer! I just posted an updated patch which tests round trips through the Marshal/Unmarshal functions explicitly. Without the fix to
I don't know how I'd affect the local timezone of the test runner without explicitly setting |
For testing purposes I suspect we need a new function like Seems like another way to fix this problem is to change |
I do not believe we support packages overwriting package time's locations. That is, clients are not expected to do:
That's not OK. It may cause arbitrary breakage (as it does here), and that breakage may change from release to release. |
@rsc setting time.Local = time.UTC is a red herring; it was just how @sbuss was demonstrating the issue. If I run this code (1.8 or tip): package main
import (
"encoding/json"
"fmt"
"log"
"time"
)
func main() {
t0 := time.Now().Round(0)
b, err := json.Marshal(t0)
if err != nil {
log.Fatal(err)
}
var t1 time.Time
if err := json.Unmarshal(b, &t1); err != nil {
log.Fatal(err)
}
fmt.Printf("t0 == t1: %t\n", t0 == t1)
fmt.Printf("t0.Equal(t1): %t\n", t0.Equal(t1))
fmt.Printf("t0: %#v\n", t0)
fmt.Printf("t1: %#v\n", t1)
} on my local machine in PDT, I get:
and if I run on a server in UTC, I get
So the issue is that we got a timestamp that round-trips through JSON in PDT but not in UTC. (The same applies to other encodings as @sbuss's patch shows.) Maybe we don't care about people using == to compare time.Times (commonly done by accident using reflect.DeepEqual), but your change 5fbf35d seems to indicate that we do care a little bit. |
@cespare Do you know of any reason we can't change |
@ianlancetaylor that might be okay, though I suppose on the margin it could break (strange) code that expects JSON-encoded UTC timestamps to use Z. Wouldn't we need to do similar fixes for the other encodings too? (MarshalText MarshalBinary/gob). I guess my concern is that 5fbf35d purports to introduce the invariant that the UTC time.Times are only ever represented using a nil loc:
This bug is highlighting two cases -- time.Now and time.Unix -- where this doesn't hold. Now this invariant isn't part of the public API, but it seems to me that the reasoning given before should apply in these cases as well. From this perspective, changing the JSON and other encodings seems to be papering over an underlying ambiguity. |
I don't believe we can change MarshalJSON at this point, and I don't believe there is an actual problem here. All time formattings are lossy. It may well be that t0!=t1 but after a round trip serialization they are equal. For example if they differ only in the final nanosecond bit but the time only records microseconds, non-equal times become equal. The same thing (but different) is happening here. JSON cannot distinguish "UTC 00:00" from "Local 00:00" the way Go does. |
Change 5fbf35d was about making sure that the internal location pointer in a time.Time has a consistent internal representation for UTC times, namely nil and not &utcLoc. The problem in the program in #19486 (comment) is different. Go always maintains a distinction between local and UTC times, even when TZ=UTC in the environment. Given that, the problem is that JSON encoding does not preserve this distinction, so it can't round trip back to the same representation:
There's no way JSON unmarshaling can decode that pair of strings back to two different time.Times. One must be mapped to the other. |
Please answer these questions before submitting your issue. Thanks!
What version of Go are you using (
go version
)?go version devel +4cce27a3fa Sat Jan 21 03:20:55 2017 +0000 linux/amd64
What operating system and processor architecture are you using (
go env
)?What did you do?
When running go code in a system set to
UTC
, or by settingtime.Local = time.UTC
, golang does not correctly setloc
to nil when eithertime.Now()
ortime.Unix()
are called. You can reproduce the issue via this play link.What did you expect to see?
I expected the timestamps generated by
Now()
to haveloc = nil
, as stated by the docs ontime.Time
What did you see instead?
The timestamps actually have this weird
Location
:&{UTC [] [] %!s(int64=0) %!s(int64=0) %!s(*time.zone=<nil>)}
The text was updated successfully, but these errors were encountered: