diff options
Diffstat (limited to 'src/time')
-rw-r--r-- | src/time/format.go | 44 | ||||
-rw-r--r-- | src/time/format_test.go | 10 | ||||
-rw-r--r-- | src/time/genzabbrs.go | 7 | ||||
-rw-r--r-- | src/time/internal_test.go | 2 | ||||
-rw-r--r-- | src/time/tick_test.go | 60 | ||||
-rw-r--r-- | src/time/time.go | 29 | ||||
-rw-r--r-- | src/time/time_test.go | 29 | ||||
-rw-r--r-- | src/time/zoneinfo_read.go | 3 |
8 files changed, 147 insertions, 37 deletions
diff --git a/src/time/format.go b/src/time/format.go index 9115609f60..07f1f804c1 100644 --- a/src/time/format.go +++ b/src/time/format.go @@ -4,7 +4,11 @@ package time -import "errors" +import ( + "errors" + "internal/stringslite" + _ "unsafe" // for linkname +) // These are predefined layouts for use in [Time.Format] and [time.Parse]. // The reference time used in these layouts is the specific time stamp: @@ -181,6 +185,16 @@ func startsWithLowerCase(str string) bool { // nextStdChunk finds the first occurrence of a std string in // layout and returns the text before, the std string, and the text after. +// +// nextStdChunk should be an internal detail, +// but widely used packages access it using linkname. +// Notable members of the hall of shame include: +// - github.com/searKing/golang/go +// +// Do not remove or change the type signature. +// See go.dev/issue/67401. +// +//go:linkname nextStdChunk func nextStdChunk(layout string) (prefix string, std int, suffix string) { for i := 0; i < len(layout); i++ { switch c := int(layout[i]); c { @@ -827,17 +841,11 @@ type ParseError struct { // newParseError creates a new ParseError. // The provided value and valueElem are cloned to avoid escaping their values. func newParseError(layout, value, layoutElem, valueElem, message string) *ParseError { - valueCopy := cloneString(value) - valueElemCopy := cloneString(valueElem) + valueCopy := stringslite.Clone(value) + valueElemCopy := stringslite.Clone(valueElem) return &ParseError{layout, valueCopy, layoutElem, valueElemCopy, message} } -// cloneString returns a string copy of s. -// Do not use strings.Clone to avoid dependency on strings package. -func cloneString(s string) string { - return string([]byte(s)) -} - // These are borrowed from unicode/utf8 and strconv and replicate behavior in // that package, since we can't take a dependency on either. const ( @@ -1245,6 +1253,20 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) if err == nil { ss, _, err = getnum(seconds, true) } + + // The range test use > rather than >=, + // as some people do write offsets of 24 hours + // or 60 minutes or 60 seconds. + if hr > 24 { + rangeErrString = "time zone offset hour" + } + if mm > 60 { + rangeErrString = "time zone offset minute" + } + if ss > 60 { + rangeErrString = "time zone offset second" + } + zoneOffset = (hr*60+mm)*60 + ss // offset is in seconds switch sign[0] { case '+': @@ -1368,7 +1390,7 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) } // Otherwise create fake zone to record offset. - zoneNameCopy := cloneString(zoneName) // avoid leaking the input value + zoneNameCopy := stringslite.Clone(zoneName) // avoid leaking the input value t.setLoc(FixedZone(zoneNameCopy, zoneOffset)) return t, nil } @@ -1389,7 +1411,7 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) offset, _ = atoi(zoneName[3:]) // Guaranteed OK by parseGMT. offset *= 3600 } - zoneNameCopy := cloneString(zoneName) // avoid leaking the input value + zoneNameCopy := stringslite.Clone(zoneName) // avoid leaking the input value t.setLoc(FixedZone(zoneNameCopy, offset)) return t, nil } diff --git a/src/time/format_test.go b/src/time/format_test.go index 29b9c280e6..4b598f6bdf 100644 --- a/src/time/format_test.go +++ b/src/time/format_test.go @@ -661,6 +661,16 @@ var parseErrorTests = []ParseErrorTest{ {"06-01-02", "a2-10-25", `parsing time "a2-10-25" as "06-01-02": cannot parse "a2-10-25" as "06"`}, {"03:04PM", "12:03pM", `parsing time "12:03pM" as "03:04PM": cannot parse "pM" as "PM"`}, {"03:04pm", "12:03pM", `parsing time "12:03pM" as "03:04pm": cannot parse "pM" as "pm"`}, + + // issue 67470 + {"-07", "-25", "time zone offset hour out of range"}, + {"-07:00", "+25:00", "time zone offset hour out of range"}, + {"-07:00", "-23:61", "time zone offset minute out of range"}, + {"-07:00:00", "+23:59:61", "time zone offset second out of range"}, + {"Z07", "-25", "time zone offset hour out of range"}, + {"Z07:00", "+25:00", "time zone offset hour out of range"}, + {"Z07:00", "-23:61", "time zone offset minute out of range"}, + {"Z07:00:00", "+23:59:61", "time zone offset second out of range"}, } func TestParseErrors(t *testing.T) { diff --git a/src/time/genzabbrs.go b/src/time/genzabbrs.go index 7dbd22f4ea..7657717825 100644 --- a/src/time/genzabbrs.go +++ b/src/time/genzabbrs.go @@ -21,7 +21,8 @@ import ( "log" "net/http" "os" - "sort" + "slices" + "strings" "text/template" "time" ) @@ -109,8 +110,8 @@ func main() { if err != nil { log.Fatal(err) } - sort.Slice(zs, func(i, j int) bool { - return zs[i].UnixName < zs[j].UnixName + slices.SortFunc(zs, func(a, b *zone) int { + return strings.Compare(a.UnixName, b.UnixName) }) var v = struct { URL string diff --git a/src/time/internal_test.go b/src/time/internal_test.go index 619f605ae7..ef82d4acfb 100644 --- a/src/time/internal_test.go +++ b/src/time/internal_test.go @@ -47,7 +47,7 @@ func CheckRuntimeTimerPeriodOverflow() { // We manually create a runtimeTimer with huge period, but that expires // immediately. The public Timer interface would require waiting for // the entire period before the first update. - t := (*Timer)(newTimer(runtimeNano(), 1<<63-1, empty, nil, nil)) + t := newTimer(runtimeNano(), 1<<63-1, empty, nil, nil) defer t.Stop() // If this test fails, we will either throw (when siftdownTimer detects diff --git a/src/time/tick_test.go b/src/time/tick_test.go index 42ef6d3217..750aa90f4d 100644 --- a/src/time/tick_test.go +++ b/src/time/tick_test.go @@ -361,7 +361,11 @@ func testTimerChan(t *testing.T, tim timer, C <-chan Time, synctimerchan bool) { drainTries = 5 ) - drain := func() { + // drain1 removes one potential stale time value + // from the timer/ticker channel after Reset. + // When using Go 1.23 sync timers/tickers, draining is never needed + // (that's the whole point of the sync timer/ticker change). + drain1 := func() { for range drainTries { select { case <-C: @@ -371,6 +375,34 @@ func testTimerChan(t *testing.T, tim timer, C <-chan Time, synctimerchan bool) { Sleep(sched) } } + + // drainAsync removes potential stale time values after Stop/Reset. + // When using Go 1 async timers, draining one or two values + // may be needed after Reset or Stop (see comments in body for details). + drainAsync := func() { + if synctimerchan { + // sync timers must have the right semantics without draining: + // there are no stale values. + return + } + + // async timers can send one stale value (then the timer is disabled). + drain1() + if isTicker { + // async tickers can send two stale values: there may be one + // sitting in the channel buffer, and there may also be one + // send racing with the Reset/Stop+drain that arrives after + // the first drain1 has pulled the value out. + // This is rare, but it does happen on overloaded builder machines. + // It can also be reproduced on an M3 MacBook Pro using: + // + // go test -c strings + // stress ./strings.test & # chew up CPU + // go test -c -race time + // stress -p 48 ./time.test -test.count=10 -test.run=TestChan/asynctimerchan=1/Ticker + drain1() + } + } noTick := func() { t.Helper() select { @@ -439,9 +471,7 @@ func testTimerChan(t *testing.T, tim timer, C <-chan Time, synctimerchan bool) { assertTick() Sleep(sched) tim.Reset(10000 * Second) - if isTicker { - assertTick() - } + drainAsync() noTick() // Test that len sees an immediate tick arrive @@ -453,9 +483,7 @@ func testTimerChan(t *testing.T, tim timer, C <-chan Time, synctimerchan bool) { // Test that len sees an immediate tick arrive // for Reset of timer NOT in heap. tim.Stop() - if !synctimerchan { - drain() - } + drainAsync() tim.Reset(1) assertLen() assertTick() @@ -465,10 +493,7 @@ func testTimerChan(t *testing.T, tim timer, C <-chan Time, synctimerchan bool) { // Test that Reset does not lose the tick that should have happened. Sleep(sched) tim.Reset(10000 * Second) - if !synctimerchan && isTicker { - assertLen() - assertTick() - } + drainAsync() noTick() notDone := func(done chan bool) { @@ -495,9 +520,7 @@ func testTimerChan(t *testing.T, tim timer, C <-chan Time, synctimerchan bool) { // Reset timer in heap (already reset above, but just in case). tim.Reset(10000 * Second) - if !synctimerchan { - drain() - } + drainAsync() // Test stop while timer in heap (because goroutine is blocked on <-C). done := make(chan bool) @@ -527,17 +550,12 @@ func testTimerChan(t *testing.T, tim timer, C <-chan Time, synctimerchan bool) { } tim.Stop() - if isTicker || !synctimerchan { - t.Logf("drain") - drain() - } + drainAsync() noTick() // Again using select and with two goroutines waiting. tim.Reset(10000 * Second) - if !synctimerchan { - drain() - } + drainAsync() done = make(chan bool, 2) done1 := make(chan bool) done2 := make(chan bool) diff --git a/src/time/time.go b/src/time/time.go index 8c24e1c481..43efe4b11d 100644 --- a/src/time/time.go +++ b/src/time/time.go @@ -53,7 +53,10 @@ // // On some systems the monotonic clock will stop if the computer goes to sleep. // On such a system, t.Sub(u) may not accurately reflect the actual -// time that passed between t and u. +// time that passed between t and u. The same applies to other functions and +// methods that subtract times, such as [Since], [Until], [Before], [After], +// [Add], [Sub], [Equal] and [Compare]. In some cases, you may need to strip +// the monotonic clock to get accurate results. // // Because the monotonic clock reading has no meaning outside // the current process, the serialized forms generated by t.GobEncode, @@ -576,6 +579,16 @@ func (t Time) Clock() (hour, min, sec int) { } // absClock is like clock but operates on an absolute time. +// +// absClock should be an internal detail, +// but widely used packages access it using linkname. +// Notable members of the hall of shame include: +// - github.com/phuslu/log +// +// Do not remove or change the type signature. +// See go.dev/issue/67401. +// +//go:linkname absClock func absClock(abs uint64) (hour, min, sec int) { sec = int(abs % secondsPerDay) hour = sec / secondsPerHour @@ -985,6 +998,17 @@ func (t Time) date(full bool) (year int, month Month, day int, yday int) { } // absDate is like date but operates on an absolute time. +// +// absDate should be an internal detail, +// but widely used packages access it using linkname. +// Notable members of the hall of shame include: +// - github.com/phuslu/log +// - gitee.com/quant1x/gox +// +// Do not remove or change the type signature. +// See go.dev/issue/67401. +// +//go:linkname absDate func absDate(abs uint64, full bool) (year int, month Month, day int, yday int) { // Split into time and day. d := abs / secondsPerDay @@ -1128,6 +1152,9 @@ func runtimeNano() int64 // (Callers may want to use 0 as "time not set".) var startNano int64 = runtimeNano() - 1 +// x/tools uses a linkname of time.Now in its tests. No harm done. +//go:linkname Now + // Now returns the current local time. func Now() Time { sec, nsec, mono := now() diff --git a/src/time/time_test.go b/src/time/time_test.go index 86335e3796..70eb614784 100644 --- a/src/time/time_test.go +++ b/src/time/time_test.go @@ -1539,6 +1539,13 @@ func BenchmarkSecond(b *testing.B) { } } +func BenchmarkDate(b *testing.B) { + t := Now() + for i := 0; i < b.N; i++ { + _, _, _ = t.Date() + } +} + func BenchmarkYear(b *testing.B) { t := Now() for i := 0; i < b.N; i++ { @@ -1546,6 +1553,20 @@ func BenchmarkYear(b *testing.B) { } } +func BenchmarkYearDay(b *testing.B) { + t := Now() + for i := 0; i < b.N; i++ { + _ = t.YearDay() + } +} + +func BenchmarkMonth(b *testing.B) { + t := Now() + for i := 0; i < b.N; i++ { + _ = t.Month() + } +} + func BenchmarkDay(b *testing.B) { t := Now() for i := 0; i < b.N; i++ { @@ -1567,6 +1588,14 @@ func BenchmarkGoString(b *testing.B) { } } +func BenchmarkDateFunc(b *testing.B) { + var t Time + for range b.N { + t = Date(2020, 8, 22, 11, 27, 43, 123456789, UTC) + } + _ = t +} + func BenchmarkUnmarshalText(b *testing.B) { var t Time in := []byte("2020-08-22T11:27:43.123456789-02:00") diff --git a/src/time/zoneinfo_read.go b/src/time/zoneinfo_read.go index 9ce735d279..5314b6ff9a 100644 --- a/src/time/zoneinfo_read.go +++ b/src/time/zoneinfo_read.go @@ -14,10 +14,13 @@ import ( "internal/bytealg" "runtime" "syscall" + _ "unsafe" // for linkname ) // registerLoadFromEmbeddedTZData is called by the time/tzdata package, // if it is imported. +// +//go:linkname registerLoadFromEmbeddedTZData func registerLoadFromEmbeddedTZData(f func(string) (string, error)) { loadFromEmbeddedTZData = f } |