Commit 5fbf35dc authored by Russ Cox's avatar Russ Cox

time: be consistent about representation of UTC location in Time struct

In the zero Time, the (not user visible) nil *Location indicates UTC.
In the result of t.UTC() and other ways to create times in specific
zones, UTC is indicated by a non-nil *Location, specifically &utcLoc.
This creates a representation ambiguity exposed by comparison with ==
or reflect.DeepEqual or the like.

Change time.Time representation to use only nil, never &utcLoc,
to represent UTC. This eliminates the ambiguity.

Fixes #15716.

Change-Id: I7dcc2c20ce6b073e1daae323d3e49d17d1d52802
Reviewed-on: https://go-review.googlesource.com/31144Reviewed-by: default avatarBrad Fitzpatrick <bradfitz@golang.org>
parent 79c03623
...@@ -1021,12 +1021,12 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) ...@@ -1021,12 +1021,12 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error)
// If that zone was in effect at the given time, use it. // If that zone was in effect at the given time, use it.
name, offset, _, _, _ := local.lookup(t.sec + internalToUnix) name, offset, _, _, _ := local.lookup(t.sec + internalToUnix)
if offset == zoneOffset && (zoneName == "" || name == zoneName) { if offset == zoneOffset && (zoneName == "" || name == zoneName) {
t.loc = local t.setLoc(local)
return t, nil return t, nil
} }
// Otherwise create fake zone to record offset. // Otherwise create fake zone to record offset.
t.loc = FixedZone(zoneName, zoneOffset) t.setLoc(FixedZone(zoneName, zoneOffset))
return t, nil return t, nil
} }
...@@ -1037,7 +1037,7 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) ...@@ -1037,7 +1037,7 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error)
offset, _, ok := local.lookupName(zoneName, t.sec+internalToUnix) offset, _, ok := local.lookupName(zoneName, t.sec+internalToUnix)
if ok { if ok {
t.sec -= int64(offset) t.sec -= int64(offset)
t.loc = local t.setLoc(local)
return t, nil return t, nil
} }
...@@ -1046,7 +1046,7 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) ...@@ -1046,7 +1046,7 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error)
offset, _ = atoi(zoneName[3:]) // Guaranteed OK by parseGMT. offset, _ = atoi(zoneName[3:]) // Guaranteed OK by parseGMT.
offset *= 3600 offset *= 3600
} }
t.loc = FixedZone(zoneName, offset) t.setLoc(FixedZone(zoneName, offset))
return t, nil return t, nil
} }
......
...@@ -50,11 +50,18 @@ type Time struct { ...@@ -50,11 +50,18 @@ type Time struct {
// loc specifies the Location that should be used to // loc specifies the Location that should be used to
// determine the minute, hour, month, day, and year // determine the minute, hour, month, day, and year
// that correspond to this Time. // that correspond to this Time.
// Only the zero Time has a nil Location. // The nil location means UTC.
// In that case it is interpreted to mean UTC. // All UTC times are represented with loc==nil, never loc==&utcLoc.
loc *Location loc *Location
} }
func (t *Time) setLoc(loc *Location) {
if loc == &utcLoc {
loc = nil
}
t.loc = loc
}
// After reports whether the time instant t is after u. // After reports whether the time instant t is after u.
func (t Time) After(u Time) bool { func (t Time) After(u Time) bool {
return t.sec > u.sec || t.sec == u.sec && t.nsec > u.nsec return t.sec > u.sec || t.sec == u.sec && t.nsec > u.nsec
...@@ -788,13 +795,13 @@ func Now() Time { ...@@ -788,13 +795,13 @@ func Now() Time {
// UTC returns t with the location set to UTC. // UTC returns t with the location set to UTC.
func (t Time) UTC() Time { func (t Time) UTC() Time {
t.loc = UTC t.setLoc(&utcLoc)
return t return t
} }
// Local returns t with the location set to local time. // Local returns t with the location set to local time.
func (t Time) Local() Time { func (t Time) Local() Time {
t.loc = Local t.setLoc(Local)
return t return t
} }
...@@ -805,7 +812,7 @@ func (t Time) In(loc *Location) Time { ...@@ -805,7 +812,7 @@ func (t Time) In(loc *Location) Time {
if loc == nil { if loc == nil {
panic("time: missing Location in call to Time.In") panic("time: missing Location in call to Time.In")
} }
t.loc = loc t.setLoc(loc)
return t return t
} }
...@@ -846,7 +853,7 @@ const timeBinaryVersion byte = 1 ...@@ -846,7 +853,7 @@ const timeBinaryVersion byte = 1
func (t Time) MarshalBinary() ([]byte, error) { func (t Time) MarshalBinary() ([]byte, error) {
var offsetMin int16 // minutes east of UTC. -1 is UTC. var offsetMin int16 // minutes east of UTC. -1 is UTC.
if t.Location() == &utcLoc { if t.Location() == UTC {
offsetMin = -1 offsetMin = -1
} else { } else {
_, offset := t.Zone() _, offset := t.Zone()
...@@ -907,11 +914,11 @@ func (t *Time) UnmarshalBinary(data []byte) error { ...@@ -907,11 +914,11 @@ func (t *Time) UnmarshalBinary(data []byte) error {
offset := int(int16(buf[1])|int16(buf[0])<<8) * 60 offset := int(int16(buf[1])|int16(buf[0])<<8) * 60
if offset == -1*60 { if offset == -1*60 {
t.loc = &utcLoc t.setLoc(&utcLoc)
} else if _, localoff, _, _, _ := Local.lookup(t.sec + internalToUnix); offset == localoff { } else if _, localoff, _, _, _ := Local.lookup(t.sec + internalToUnix); offset == localoff {
t.loc = Local t.setLoc(Local)
} else { } else {
t.loc = FixedZone("", offset) t.setLoc(FixedZone("", offset))
} }
return nil return nil
...@@ -1104,7 +1111,9 @@ func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) T ...@@ -1104,7 +1111,9 @@ func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) T
unix -= int64(offset) unix -= int64(offset)
} }
return Time{unix + unixToInternal, int32(nsec), loc} t := Time{unix + unixToInternal, int32(nsec), nil}
t.setLoc(loc)
return t
} }
// Truncate returns the result of rounding t down to a multiple of d (since the zero time). // Truncate returns the result of rounding t down to a multiple of d (since the zero time).
......
...@@ -1117,6 +1117,8 @@ var defaultLocTests = []struct { ...@@ -1117,6 +1117,8 @@ var defaultLocTests = []struct {
{"Truncate", func(t1, t2 Time) bool { return t1.Truncate(Hour).Equal(t2.Truncate(Hour)) }}, {"Truncate", func(t1, t2 Time) bool { return t1.Truncate(Hour).Equal(t2.Truncate(Hour)) }},
{"Round", func(t1, t2 Time) bool { return t1.Round(Hour).Equal(t2.Round(Hour)) }}, {"Round", func(t1, t2 Time) bool { return t1.Round(Hour).Equal(t2.Round(Hour)) }},
{"== Time{}", func(t1, t2 Time) bool { return (t1==Time{}) == (t2==Time{}) }},
} }
func TestDefaultLoc(t *testing.T) { func TestDefaultLoc(t *testing.T) {
...@@ -1126,7 +1128,7 @@ func TestDefaultLoc(t *testing.T) { ...@@ -1126,7 +1128,7 @@ func TestDefaultLoc(t *testing.T) {
t1 := Time{} t1 := Time{}
t2 := Time{}.UTC() t2 := Time{}.UTC()
if !tt.f(t1, t2) { if !tt.f(t1, t2) {
t.Errorf("Default fail on fuction: %s", tt.name) t.Errorf("Time{} and Time{}.UTC() behave differently for %s", tt.name)
} }
} }
} }
...@@ -1213,3 +1215,18 @@ func BenchmarkDay(b *testing.B) { ...@@ -1213,3 +1215,18 @@ func BenchmarkDay(b *testing.B) {
_ = t.Day() _ = t.Day()
} }
} }
func TestMarshalBinaryZeroTime(t *testing.T) {
t0 := Time{}
enc, err := t0.MarshalBinary()
if err != nil {
t.Fatal(err)
}
t1 := Now() // not zero
if err := t1.UnmarshalBinary(enc); err != nil {
t.Fatal(err)
}
if t1 != t0 {
t.Errorf("t0=%#v\nt1=%#v\nwant identical structures", t0, t1)
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment