From 41b9d8c75e45636a153c2a31d117196a22a7fc6c Mon Sep 17 00:00:00 2001 From: hopehook Date: Tue, 10 May 2022 12:30:43 +0800 Subject: [PATCH] time: add Time.ZoneBounds The method Location.lookup returns the "start" and "end" times bracketing seconds when that zone is in effect. This CL does these things: 1. Exported the "start" and "end" times as time.Time form 2. Keep the "Location" of the returned times be the same as underlying time Fixes #50062. Change-Id: I88888a100d0fc68f4984a85c75a85a83aa3e5d80 Reviewed-on: https://go-review.googlesource.com/c/go/+/405374 Run-TryBot: Ian Lance Taylor Reviewed-by: Ian Lance Taylor Run-TryBot: Ian Lance Taylor TryBot-Result: Gopher Robot Reviewed-by: Dmitri Shuralyov Auto-Submit: Ian Lance Taylor --- api/next/50062.txt | 1 + src/time/time.go | 18 +++++++++++ src/time/time_test.go | 74 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+) create mode 100644 api/next/50062.txt diff --git a/api/next/50062.txt b/api/next/50062.txt new file mode 100644 index 0000000000..0a5efcc70b --- /dev/null +++ b/api/next/50062.txt @@ -0,0 +1 @@ +pkg time, method (Time) ZoneBounds() (Time, Time) #50062 \ No newline at end of file diff --git a/src/time/time.go b/src/time/time.go index 4cf3a5cd68..47b26e39a8 100644 --- a/src/time/time.go +++ b/src/time/time.go @@ -1141,6 +1141,24 @@ func (t Time) Zone() (name string, offset int) { return } +// ZoneBounds returns the bounds of the time zone in effect at time t. +// The zone begins at start and the next zone begins at end. +// If the zone begins at the beginning of time, start will be returned as a zero Time. +// If the zone goes on forever, end will be returned as a zero Time. +// The Location of the returned times will be the same as t. +func (t Time) ZoneBounds() (start, end Time) { + _, _, startSec, endSec, _ := t.loc.lookup(t.unixSec()) + if startSec != alpha { + start = unixTime(startSec, 0) + start.setLoc(t.loc) + } + if endSec != omega { + end = unixTime(endSec, 0) + end.setLoc(t.loc) + } + return +} + // Unix returns t as a Unix time, the number of seconds elapsed // since January 1, 1970 UTC. The result does not depend on the // location associated with t. diff --git a/src/time/time_test.go b/src/time/time_test.go index 695d48b1b5..6fde5f6470 100644 --- a/src/time/time_test.go +++ b/src/time/time_test.go @@ -1693,3 +1693,77 @@ func TestTimeWithZoneTransition(t *testing.T) { } } } + +func TestZoneBounds(t *testing.T) { + undo := DisablePlatformSources() + defer undo() + loc, err := LoadLocation("Asia/Shanghai") + if err != nil { + t.Fatal(err) + } + + // The ZoneBounds of a UTC location would just return two zero Time. + for _, test := range utctests { + sec := test.seconds + golden := &test.golden + tm := Unix(sec, 0).UTC() + start, end := tm.ZoneBounds() + if !(start.IsZero() && end.IsZero()) { + t.Errorf("ZoneBounds of %+v expects two zero Time, got:\n start=%v\n end=%v", *golden, start, end) + } + } + + // If the zone begins at the beginning of time, start will be returned as a zero Time. + // Use math.MinInt32 to avoid overflow of int arguments on 32-bit systems. + beginTime := Date(math.MinInt32, January, 1, 0, 0, 0, 0, loc) + start, end := beginTime.ZoneBounds() + if !start.IsZero() || end.IsZero() { + t.Errorf("ZoneBounds of %v expects start is zero Time, got:\n start=%v\n end=%v", beginTime, start, end) + } + + // If the zone goes on forever, end will be returned as a zero Time. + // Use math.MaxInt32 to avoid overflow of int arguments on 32-bit systems. + foreverTime := Date(math.MaxInt32, January, 1, 0, 0, 0, 0, loc) + start, end = foreverTime.ZoneBounds() + if start.IsZero() || !end.IsZero() { + t.Errorf("ZoneBounds of %v expects end is zero Time, got:\n start=%v\n end=%v", foreverTime, start, end) + } + + // Check some real-world cases to make sure we're getting the right bounds. + boundOne := Date(1990, September, 16, 1, 0, 0, 0, loc) + boundTwo := Date(1991, April, 14, 3, 0, 0, 0, loc) + boundThree := Date(1991, September, 15, 1, 0, 0, 0, loc) + makeLocalTime := func(sec int64) Time { return Unix(sec, 0) } + realTests := [...]struct { + giveTime Time + wantStart Time + wantEnd Time + }{ + // The ZoneBounds of "Asia/Shanghai" Daylight Saving Time + 0: {Date(1991, April, 13, 17, 50, 0, 0, loc), boundOne, boundTwo}, + 1: {Date(1991, April, 13, 18, 0, 0, 0, loc), boundOne, boundTwo}, + 2: {Date(1991, April, 14, 1, 50, 0, 0, loc), boundOne, boundTwo}, + 3: {boundTwo, boundTwo, boundThree}, + 4: {Date(1991, September, 14, 16, 50, 0, 0, loc), boundTwo, boundThree}, + 5: {Date(1991, September, 14, 17, 0, 0, 0, loc), boundTwo, boundThree}, + 6: {Date(1991, September, 15, 0, 50, 0, 0, loc), boundTwo, boundThree}, + + // The ZoneBounds of a local time would return two local Time. + // Note: We preloaded "America/Los_Angeles" as time.Local for testing + 7: {makeLocalTime(0), makeLocalTime(-5756400), makeLocalTime(9972000)}, + 8: {makeLocalTime(1221681866), makeLocalTime(1205056800), makeLocalTime(1225616400)}, + 9: {makeLocalTime(2152173599), makeLocalTime(2145916800), makeLocalTime(2152173600)}, + 10: {makeLocalTime(2152173600), makeLocalTime(2152173600), makeLocalTime(2172733200)}, + 11: {makeLocalTime(2152173601), makeLocalTime(2152173600), makeLocalTime(2172733200)}, + 12: {makeLocalTime(2159200800), makeLocalTime(2152173600), makeLocalTime(2172733200)}, + 13: {makeLocalTime(2172733199), makeLocalTime(2152173600), makeLocalTime(2172733200)}, + 14: {makeLocalTime(2172733200), makeLocalTime(2172733200), makeLocalTime(2177452800)}, + } + for i, tt := range realTests { + start, end := tt.giveTime.ZoneBounds() + if !start.Equal(tt.wantStart) || !end.Equal(tt.wantEnd) { + t.Errorf("#%d:: ZoneBounds of %v expects right bounds:\n got start=%v\n want start=%v\n got end=%v\n want end=%v", + i, tt.giveTime, start, tt.wantStart, end, tt.wantEnd) + } + } +} -- 2.48.1