aboutsummaryrefslogtreecommitdiff
path: root/src/time
diff options
context:
space:
mode:
Diffstat (limited to 'src/time')
-rw-r--r--src/time/format.go44
-rw-r--r--src/time/format_test.go10
-rw-r--r--src/time/genzabbrs.go7
-rw-r--r--src/time/internal_test.go2
-rw-r--r--src/time/tick_test.go60
-rw-r--r--src/time/time.go29
-rw-r--r--src/time/time_test.go29
-rw-r--r--src/time/zoneinfo_read.go3
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
}