Initial commit: Go 1.23 release state
This commit is contained in:
12
src/time/embed.go
Normal file
12
src/time/embed.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// This file is used with build tag timetzdata to embed tzdata into
|
||||
// the binary.
|
||||
|
||||
//go:build timetzdata
|
||||
|
||||
package time
|
||||
|
||||
import _ "time/tzdata"
|
||||
739
src/time/example_test.go
Normal file
739
src/time/example_test.go
Normal file
@@ -0,0 +1,739 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package time_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func expensiveCall() {}
|
||||
|
||||
func ExampleDuration() {
|
||||
t0 := time.Now()
|
||||
expensiveCall()
|
||||
t1 := time.Now()
|
||||
fmt.Printf("The call took %v to run.\n", t1.Sub(t0))
|
||||
}
|
||||
|
||||
func ExampleDuration_Round() {
|
||||
d, err := time.ParseDuration("1h15m30.918273645s")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
round := []time.Duration{
|
||||
time.Nanosecond,
|
||||
time.Microsecond,
|
||||
time.Millisecond,
|
||||
time.Second,
|
||||
2 * time.Second,
|
||||
time.Minute,
|
||||
10 * time.Minute,
|
||||
time.Hour,
|
||||
}
|
||||
|
||||
for _, r := range round {
|
||||
fmt.Printf("d.Round(%6s) = %s\n", r, d.Round(r).String())
|
||||
}
|
||||
// Output:
|
||||
// d.Round( 1ns) = 1h15m30.918273645s
|
||||
// d.Round( 1µs) = 1h15m30.918274s
|
||||
// d.Round( 1ms) = 1h15m30.918s
|
||||
// d.Round( 1s) = 1h15m31s
|
||||
// d.Round( 2s) = 1h15m30s
|
||||
// d.Round( 1m0s) = 1h16m0s
|
||||
// d.Round( 10m0s) = 1h20m0s
|
||||
// d.Round(1h0m0s) = 1h0m0s
|
||||
}
|
||||
|
||||
func ExampleDuration_String() {
|
||||
fmt.Println(1*time.Hour + 2*time.Minute + 300*time.Millisecond)
|
||||
fmt.Println(300 * time.Millisecond)
|
||||
// Output:
|
||||
// 1h2m0.3s
|
||||
// 300ms
|
||||
}
|
||||
|
||||
func ExampleDuration_Truncate() {
|
||||
d, err := time.ParseDuration("1h15m30.918273645s")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
trunc := []time.Duration{
|
||||
time.Nanosecond,
|
||||
time.Microsecond,
|
||||
time.Millisecond,
|
||||
time.Second,
|
||||
2 * time.Second,
|
||||
time.Minute,
|
||||
10 * time.Minute,
|
||||
time.Hour,
|
||||
}
|
||||
|
||||
for _, t := range trunc {
|
||||
fmt.Printf("d.Truncate(%6s) = %s\n", t, d.Truncate(t).String())
|
||||
}
|
||||
// Output:
|
||||
// d.Truncate( 1ns) = 1h15m30.918273645s
|
||||
// d.Truncate( 1µs) = 1h15m30.918273s
|
||||
// d.Truncate( 1ms) = 1h15m30.918s
|
||||
// d.Truncate( 1s) = 1h15m30s
|
||||
// d.Truncate( 2s) = 1h15m30s
|
||||
// d.Truncate( 1m0s) = 1h15m0s
|
||||
// d.Truncate( 10m0s) = 1h10m0s
|
||||
// d.Truncate(1h0m0s) = 1h0m0s
|
||||
}
|
||||
|
||||
func ExampleParseDuration() {
|
||||
hours, _ := time.ParseDuration("10h")
|
||||
complex, _ := time.ParseDuration("1h10m10s")
|
||||
micro, _ := time.ParseDuration("1µs")
|
||||
// The package also accepts the incorrect but common prefix u for micro.
|
||||
micro2, _ := time.ParseDuration("1us")
|
||||
|
||||
fmt.Println(hours)
|
||||
fmt.Println(complex)
|
||||
fmt.Printf("There are %.0f seconds in %v.\n", complex.Seconds(), complex)
|
||||
fmt.Printf("There are %d nanoseconds in %v.\n", micro.Nanoseconds(), micro)
|
||||
fmt.Printf("There are %6.2e seconds in %v.\n", micro2.Seconds(), micro2)
|
||||
// Output:
|
||||
// 10h0m0s
|
||||
// 1h10m10s
|
||||
// There are 4210 seconds in 1h10m10s.
|
||||
// There are 1000 nanoseconds in 1µs.
|
||||
// There are 1.00e-06 seconds in 1µs.
|
||||
}
|
||||
|
||||
func ExampleDuration_Hours() {
|
||||
h, _ := time.ParseDuration("4h30m")
|
||||
fmt.Printf("I've got %.1f hours of work left.", h.Hours())
|
||||
// Output: I've got 4.5 hours of work left.
|
||||
}
|
||||
|
||||
func ExampleDuration_Microseconds() {
|
||||
u, _ := time.ParseDuration("1s")
|
||||
fmt.Printf("One second is %d microseconds.\n", u.Microseconds())
|
||||
// Output:
|
||||
// One second is 1000000 microseconds.
|
||||
}
|
||||
|
||||
func ExampleDuration_Milliseconds() {
|
||||
u, _ := time.ParseDuration("1s")
|
||||
fmt.Printf("One second is %d milliseconds.\n", u.Milliseconds())
|
||||
// Output:
|
||||
// One second is 1000 milliseconds.
|
||||
}
|
||||
|
||||
func ExampleDuration_Minutes() {
|
||||
m, _ := time.ParseDuration("1h30m")
|
||||
fmt.Printf("The movie is %.0f minutes long.", m.Minutes())
|
||||
// Output: The movie is 90 minutes long.
|
||||
}
|
||||
|
||||
func ExampleDuration_Nanoseconds() {
|
||||
u, _ := time.ParseDuration("1µs")
|
||||
fmt.Printf("One microsecond is %d nanoseconds.\n", u.Nanoseconds())
|
||||
// Output:
|
||||
// One microsecond is 1000 nanoseconds.
|
||||
}
|
||||
|
||||
func ExampleDuration_Seconds() {
|
||||
m, _ := time.ParseDuration("1m30s")
|
||||
fmt.Printf("Take off in t-%.0f seconds.", m.Seconds())
|
||||
// Output: Take off in t-90 seconds.
|
||||
}
|
||||
|
||||
var c chan int
|
||||
|
||||
func handle(int) {}
|
||||
|
||||
func ExampleAfter() {
|
||||
select {
|
||||
case m := <-c:
|
||||
handle(m)
|
||||
case <-time.After(10 * time.Second):
|
||||
fmt.Println("timed out")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleSleep() {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
}
|
||||
|
||||
func statusUpdate() string { return "" }
|
||||
|
||||
func ExampleTick() {
|
||||
c := time.Tick(5 * time.Second)
|
||||
for next := range c {
|
||||
fmt.Printf("%v %s\n", next, statusUpdate())
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleMonth() {
|
||||
_, month, day := time.Now().Date()
|
||||
if month == time.November && day == 10 {
|
||||
fmt.Println("Happy Go day!")
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleDate() {
|
||||
t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||||
fmt.Printf("Go launched at %s\n", t.Local())
|
||||
// Output: Go launched at 2009-11-10 15:00:00 -0800 PST
|
||||
}
|
||||
|
||||
func ExampleNewTicker() {
|
||||
ticker := time.NewTicker(time.Second)
|
||||
defer ticker.Stop()
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
time.Sleep(10 * time.Second)
|
||||
done <- true
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-done:
|
||||
fmt.Println("Done!")
|
||||
return
|
||||
case t := <-ticker.C:
|
||||
fmt.Println("Current time: ", t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ExampleTime_Format() {
|
||||
// Parse a time value from a string in the standard Unix format.
|
||||
t, err := time.Parse(time.UnixDate, "Wed Feb 25 11:06:39 PST 2015")
|
||||
if err != nil { // Always check errors even if they should not happen.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
tz, err := time.LoadLocation("Asia/Shanghai")
|
||||
if err != nil { // Always check errors even if they should not happen.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// time.Time's Stringer method is useful without any format.
|
||||
fmt.Println("default format:", t)
|
||||
|
||||
// Predefined constants in the package implement common layouts.
|
||||
fmt.Println("Unix format:", t.Format(time.UnixDate))
|
||||
|
||||
// The time zone attached to the time value affects its output.
|
||||
fmt.Println("Same, in UTC:", t.UTC().Format(time.UnixDate))
|
||||
|
||||
fmt.Println("in Shanghai with seconds:", t.In(tz).Format("2006-01-02T15:04:05 -070000"))
|
||||
|
||||
fmt.Println("in Shanghai with colon seconds:", t.In(tz).Format("2006-01-02T15:04:05 -07:00:00"))
|
||||
|
||||
// The rest of this function demonstrates the properties of the
|
||||
// layout string used in the format.
|
||||
|
||||
// The layout string used by the Parse function and Format method
|
||||
// shows by example how the reference time should be represented.
|
||||
// We stress that one must show how the reference time is formatted,
|
||||
// not a time of the user's choosing. Thus each layout string is a
|
||||
// representation of the time stamp,
|
||||
// Jan 2 15:04:05 2006 MST
|
||||
// An easy way to remember this value is that it holds, when presented
|
||||
// in this order, the values (lined up with the elements above):
|
||||
// 1 2 3 4 5 6 -7
|
||||
// There are some wrinkles illustrated below.
|
||||
|
||||
// Most uses of Format and Parse use constant layout strings such as
|
||||
// the ones defined in this package, but the interface is flexible,
|
||||
// as these examples show.
|
||||
|
||||
// Define a helper function to make the examples' output look nice.
|
||||
do := func(name, layout, want string) {
|
||||
got := t.Format(layout)
|
||||
if want != got {
|
||||
fmt.Printf("error: for %q got %q; expected %q\n", layout, got, want)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%-16s %q gives %q\n", name, layout, got)
|
||||
}
|
||||
|
||||
// Print a header in our output.
|
||||
fmt.Printf("\nFormats:\n\n")
|
||||
|
||||
// Simple starter examples.
|
||||
do("Basic full date", "Mon Jan 2 15:04:05 MST 2006", "Wed Feb 25 11:06:39 PST 2015")
|
||||
do("Basic short date", "2006/01/02", "2015/02/25")
|
||||
|
||||
// The hour of the reference time is 15, or 3PM. The layout can express
|
||||
// it either way, and since our value is the morning we should see it as
|
||||
// an AM time. We show both in one format string. Lower case too.
|
||||
do("AM/PM", "3PM==3pm==15h", "11AM==11am==11h")
|
||||
|
||||
// When parsing, if the seconds value is followed by a decimal point
|
||||
// and some digits, that is taken as a fraction of a second even if
|
||||
// the layout string does not represent the fractional second.
|
||||
// Here we add a fractional second to our time value used above.
|
||||
t, err = time.Parse(time.UnixDate, "Wed Feb 25 11:06:39.1234 PST 2015")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// It does not appear in the output if the layout string does not contain
|
||||
// a representation of the fractional second.
|
||||
do("No fraction", time.UnixDate, "Wed Feb 25 11:06:39 PST 2015")
|
||||
|
||||
// Fractional seconds can be printed by adding a run of 0s or 9s after
|
||||
// a decimal point in the seconds value in the layout string.
|
||||
// If the layout digits are 0s, the fractional second is of the specified
|
||||
// width. Note that the output has a trailing zero.
|
||||
do("0s for fraction", "15:04:05.00000", "11:06:39.12340")
|
||||
|
||||
// If the fraction in the layout is 9s, trailing zeros are dropped.
|
||||
do("9s for fraction", "15:04:05.99999999", "11:06:39.1234")
|
||||
|
||||
// Output:
|
||||
// default format: 2015-02-25 11:06:39 -0800 PST
|
||||
// Unix format: Wed Feb 25 11:06:39 PST 2015
|
||||
// Same, in UTC: Wed Feb 25 19:06:39 UTC 2015
|
||||
//in Shanghai with seconds: 2015-02-26T03:06:39 +080000
|
||||
//in Shanghai with colon seconds: 2015-02-26T03:06:39 +08:00:00
|
||||
//
|
||||
// Formats:
|
||||
//
|
||||
// Basic full date "Mon Jan 2 15:04:05 MST 2006" gives "Wed Feb 25 11:06:39 PST 2015"
|
||||
// Basic short date "2006/01/02" gives "2015/02/25"
|
||||
// AM/PM "3PM==3pm==15h" gives "11AM==11am==11h"
|
||||
// No fraction "Mon Jan _2 15:04:05 MST 2006" gives "Wed Feb 25 11:06:39 PST 2015"
|
||||
// 0s for fraction "15:04:05.00000" gives "11:06:39.12340"
|
||||
// 9s for fraction "15:04:05.99999999" gives "11:06:39.1234"
|
||||
|
||||
}
|
||||
|
||||
func ExampleTime_Format_pad() {
|
||||
// Parse a time value from a string in the standard Unix format.
|
||||
t, err := time.Parse(time.UnixDate, "Sat Mar 7 11:06:39 PST 2015")
|
||||
if err != nil { // Always check errors even if they should not happen.
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Define a helper function to make the examples' output look nice.
|
||||
do := func(name, layout, want string) {
|
||||
got := t.Format(layout)
|
||||
if want != got {
|
||||
fmt.Printf("error: for %q got %q; expected %q\n", layout, got, want)
|
||||
return
|
||||
}
|
||||
fmt.Printf("%-16s %q gives %q\n", name, layout, got)
|
||||
}
|
||||
|
||||
// The predefined constant Unix uses an underscore to pad the day.
|
||||
do("Unix", time.UnixDate, "Sat Mar 7 11:06:39 PST 2015")
|
||||
|
||||
// For fixed-width printing of values, such as the date, that may be one or
|
||||
// two characters (7 vs. 07), use an _ instead of a space in the layout string.
|
||||
// Here we print just the day, which is 2 in our layout string and 7 in our
|
||||
// value.
|
||||
do("No pad", "<2>", "<7>")
|
||||
|
||||
// An underscore represents a space pad, if the date only has one digit.
|
||||
do("Spaces", "<_2>", "< 7>")
|
||||
|
||||
// A "0" indicates zero padding for single-digit values.
|
||||
do("Zeros", "<02>", "<07>")
|
||||
|
||||
// If the value is already the right width, padding is not used.
|
||||
// For instance, the second (05 in the reference time) in our value is 39,
|
||||
// so it doesn't need padding, but the minutes (04, 06) does.
|
||||
do("Suppressed pad", "04:05", "06:39")
|
||||
|
||||
// Output:
|
||||
// Unix "Mon Jan _2 15:04:05 MST 2006" gives "Sat Mar 7 11:06:39 PST 2015"
|
||||
// No pad "<2>" gives "<7>"
|
||||
// Spaces "<_2>" gives "< 7>"
|
||||
// Zeros "<02>" gives "<07>"
|
||||
// Suppressed pad "04:05" gives "06:39"
|
||||
|
||||
}
|
||||
|
||||
func ExampleTime_GoString() {
|
||||
t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||||
fmt.Println(t.GoString())
|
||||
t = t.Add(1 * time.Minute)
|
||||
fmt.Println(t.GoString())
|
||||
t = t.AddDate(0, 1, 0)
|
||||
fmt.Println(t.GoString())
|
||||
t, _ = time.Parse("Jan 2, 2006 at 3:04pm (MST)", "Feb 3, 2013 at 7:54pm (UTC)")
|
||||
fmt.Println(t.GoString())
|
||||
|
||||
// Output:
|
||||
// time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||||
// time.Date(2009, time.November, 10, 23, 1, 0, 0, time.UTC)
|
||||
// time.Date(2009, time.December, 10, 23, 1, 0, 0, time.UTC)
|
||||
// time.Date(2013, time.February, 3, 19, 54, 0, 0, time.UTC)
|
||||
}
|
||||
|
||||
func ExampleParse() {
|
||||
// See the example for Time.Format for a thorough description of how
|
||||
// to define the layout string to parse a time.Time value; Parse and
|
||||
// Format use the same model to describe their input and output.
|
||||
|
||||
// longForm shows by example how the reference time would be represented in
|
||||
// the desired layout.
|
||||
const longForm = "Jan 2, 2006 at 3:04pm (MST)"
|
||||
t, _ := time.Parse(longForm, "Feb 3, 2013 at 7:54pm (PST)")
|
||||
fmt.Println(t)
|
||||
|
||||
// shortForm is another way the reference time would be represented
|
||||
// in the desired layout; it has no time zone present.
|
||||
// Note: without explicit zone, returns time in UTC.
|
||||
const shortForm = "2006-Jan-02"
|
||||
t, _ = time.Parse(shortForm, "2013-Feb-03")
|
||||
fmt.Println(t)
|
||||
|
||||
// Some valid layouts are invalid time values, due to format specifiers
|
||||
// such as _ for space padding and Z for zone information.
|
||||
// For example the RFC3339 layout 2006-01-02T15:04:05Z07:00
|
||||
// contains both Z and a time zone offset in order to handle both valid options:
|
||||
// 2006-01-02T15:04:05Z
|
||||
// 2006-01-02T15:04:05+07:00
|
||||
t, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05Z")
|
||||
fmt.Println(t)
|
||||
t, _ = time.Parse(time.RFC3339, "2006-01-02T15:04:05+07:00")
|
||||
fmt.Println(t)
|
||||
_, err := time.Parse(time.RFC3339, time.RFC3339)
|
||||
fmt.Println("error", err) // Returns an error as the layout is not a valid time value
|
||||
|
||||
// Output:
|
||||
// 2013-02-03 19:54:00 -0800 PST
|
||||
// 2013-02-03 00:00:00 +0000 UTC
|
||||
// 2006-01-02 15:04:05 +0000 UTC
|
||||
// 2006-01-02 15:04:05 +0700 +0700
|
||||
// error parsing time "2006-01-02T15:04:05Z07:00": extra text: "07:00"
|
||||
}
|
||||
|
||||
func ExampleParseInLocation() {
|
||||
loc, _ := time.LoadLocation("Europe/Berlin")
|
||||
|
||||
// This will look for the name CEST in the Europe/Berlin time zone.
|
||||
const longForm = "Jan 2, 2006 at 3:04pm (MST)"
|
||||
t, _ := time.ParseInLocation(longForm, "Jul 9, 2012 at 5:02am (CEST)", loc)
|
||||
fmt.Println(t)
|
||||
|
||||
// Note: without explicit zone, returns time in given location.
|
||||
const shortForm = "2006-Jan-02"
|
||||
t, _ = time.ParseInLocation(shortForm, "2012-Jul-09", loc)
|
||||
fmt.Println(t)
|
||||
|
||||
// Output:
|
||||
// 2012-07-09 05:02:00 +0200 CEST
|
||||
// 2012-07-09 00:00:00 +0200 CEST
|
||||
}
|
||||
|
||||
func ExampleUnix() {
|
||||
unixTime := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||||
fmt.Println(unixTime.Unix())
|
||||
t := time.Unix(unixTime.Unix(), 0).UTC()
|
||||
fmt.Println(t)
|
||||
|
||||
// Output:
|
||||
// 1257894000
|
||||
// 2009-11-10 23:00:00 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleUnixMicro() {
|
||||
umt := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||||
fmt.Println(umt.UnixMicro())
|
||||
t := time.UnixMicro(umt.UnixMicro()).UTC()
|
||||
fmt.Println(t)
|
||||
|
||||
// Output:
|
||||
// 1257894000000000
|
||||
// 2009-11-10 23:00:00 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleUnixMilli() {
|
||||
umt := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
|
||||
fmt.Println(umt.UnixMilli())
|
||||
t := time.UnixMilli(umt.UnixMilli()).UTC()
|
||||
fmt.Println(t)
|
||||
|
||||
// Output:
|
||||
// 1257894000000
|
||||
// 2009-11-10 23:00:00 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleTime_Unix() {
|
||||
// 1 billion seconds of Unix, three ways.
|
||||
fmt.Println(time.Unix(1e9, 0).UTC()) // 1e9 seconds
|
||||
fmt.Println(time.Unix(0, 1e18).UTC()) // 1e18 nanoseconds
|
||||
fmt.Println(time.Unix(2e9, -1e18).UTC()) // 2e9 seconds - 1e18 nanoseconds
|
||||
|
||||
t := time.Date(2001, time.September, 9, 1, 46, 40, 0, time.UTC)
|
||||
fmt.Println(t.Unix()) // seconds since 1970
|
||||
fmt.Println(t.UnixNano()) // nanoseconds since 1970
|
||||
|
||||
// Output:
|
||||
// 2001-09-09 01:46:40 +0000 UTC
|
||||
// 2001-09-09 01:46:40 +0000 UTC
|
||||
// 2001-09-09 01:46:40 +0000 UTC
|
||||
// 1000000000
|
||||
// 1000000000000000000
|
||||
}
|
||||
|
||||
func ExampleTime_Round() {
|
||||
t := time.Date(0, 0, 0, 12, 15, 30, 918273645, time.UTC)
|
||||
round := []time.Duration{
|
||||
time.Nanosecond,
|
||||
time.Microsecond,
|
||||
time.Millisecond,
|
||||
time.Second,
|
||||
2 * time.Second,
|
||||
time.Minute,
|
||||
10 * time.Minute,
|
||||
time.Hour,
|
||||
}
|
||||
|
||||
for _, d := range round {
|
||||
fmt.Printf("t.Round(%6s) = %s\n", d, t.Round(d).Format("15:04:05.999999999"))
|
||||
}
|
||||
// Output:
|
||||
// t.Round( 1ns) = 12:15:30.918273645
|
||||
// t.Round( 1µs) = 12:15:30.918274
|
||||
// t.Round( 1ms) = 12:15:30.918
|
||||
// t.Round( 1s) = 12:15:31
|
||||
// t.Round( 2s) = 12:15:30
|
||||
// t.Round( 1m0s) = 12:16:00
|
||||
// t.Round( 10m0s) = 12:20:00
|
||||
// t.Round(1h0m0s) = 12:00:00
|
||||
}
|
||||
|
||||
func ExampleTime_Truncate() {
|
||||
t, _ := time.Parse("2006 Jan 02 15:04:05", "2012 Dec 07 12:15:30.918273645")
|
||||
trunc := []time.Duration{
|
||||
time.Nanosecond,
|
||||
time.Microsecond,
|
||||
time.Millisecond,
|
||||
time.Second,
|
||||
2 * time.Second,
|
||||
time.Minute,
|
||||
10 * time.Minute,
|
||||
}
|
||||
|
||||
for _, d := range trunc {
|
||||
fmt.Printf("t.Truncate(%5s) = %s\n", d, t.Truncate(d).Format("15:04:05.999999999"))
|
||||
}
|
||||
// To round to the last midnight in the local timezone, create a new Date.
|
||||
midnight := time.Date(t.Year(), t.Month(), t.Day(), 0, 0, 0, 0, time.Local)
|
||||
_ = midnight
|
||||
|
||||
// Output:
|
||||
// t.Truncate( 1ns) = 12:15:30.918273645
|
||||
// t.Truncate( 1µs) = 12:15:30.918273
|
||||
// t.Truncate( 1ms) = 12:15:30.918
|
||||
// t.Truncate( 1s) = 12:15:30
|
||||
// t.Truncate( 2s) = 12:15:30
|
||||
// t.Truncate( 1m0s) = 12:15:00
|
||||
// t.Truncate(10m0s) = 12:10:00
|
||||
}
|
||||
|
||||
func ExampleLoadLocation() {
|
||||
location, err := time.LoadLocation("America/Los_Angeles")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
timeInUTC := time.Date(2018, 8, 30, 12, 0, 0, 0, time.UTC)
|
||||
fmt.Println(timeInUTC.In(location))
|
||||
// Output: 2018-08-30 05:00:00 -0700 PDT
|
||||
}
|
||||
|
||||
func ExampleLocation() {
|
||||
// China doesn't have daylight saving. It uses a fixed 8 hour offset from UTC.
|
||||
secondsEastOfUTC := int((8 * time.Hour).Seconds())
|
||||
beijing := time.FixedZone("Beijing Time", secondsEastOfUTC)
|
||||
|
||||
// If the system has a timezone database present, it's possible to load a location
|
||||
// from that, e.g.:
|
||||
// newYork, err := time.LoadLocation("America/New_York")
|
||||
|
||||
// Creating a time requires a location. Common locations are time.Local and time.UTC.
|
||||
timeInUTC := time.Date(2009, 1, 1, 12, 0, 0, 0, time.UTC)
|
||||
sameTimeInBeijing := time.Date(2009, 1, 1, 20, 0, 0, 0, beijing)
|
||||
|
||||
// Although the UTC clock time is 1200 and the Beijing clock time is 2000, Beijing is
|
||||
// 8 hours ahead so the two dates actually represent the same instant.
|
||||
timesAreEqual := timeInUTC.Equal(sameTimeInBeijing)
|
||||
fmt.Println(timesAreEqual)
|
||||
|
||||
// Output:
|
||||
// true
|
||||
}
|
||||
|
||||
func ExampleTime_Add() {
|
||||
start := time.Date(2009, 1, 1, 12, 0, 0, 0, time.UTC)
|
||||
afterTenSeconds := start.Add(time.Second * 10)
|
||||
afterTenMinutes := start.Add(time.Minute * 10)
|
||||
afterTenHours := start.Add(time.Hour * 10)
|
||||
afterTenDays := start.Add(time.Hour * 24 * 10)
|
||||
|
||||
fmt.Printf("start = %v\n", start)
|
||||
fmt.Printf("start.Add(time.Second * 10) = %v\n", afterTenSeconds)
|
||||
fmt.Printf("start.Add(time.Minute * 10) = %v\n", afterTenMinutes)
|
||||
fmt.Printf("start.Add(time.Hour * 10) = %v\n", afterTenHours)
|
||||
fmt.Printf("start.Add(time.Hour * 24 * 10) = %v\n", afterTenDays)
|
||||
|
||||
// Output:
|
||||
// start = 2009-01-01 12:00:00 +0000 UTC
|
||||
// start.Add(time.Second * 10) = 2009-01-01 12:00:10 +0000 UTC
|
||||
// start.Add(time.Minute * 10) = 2009-01-01 12:10:00 +0000 UTC
|
||||
// start.Add(time.Hour * 10) = 2009-01-01 22:00:00 +0000 UTC
|
||||
// start.Add(time.Hour * 24 * 10) = 2009-01-11 12:00:00 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleTime_AddDate() {
|
||||
start := time.Date(2023, 03, 25, 12, 0, 0, 0, time.UTC)
|
||||
oneDayLater := start.AddDate(0, 0, 1)
|
||||
dayDuration := oneDayLater.Sub(start)
|
||||
oneMonthLater := start.AddDate(0, 1, 0)
|
||||
oneYearLater := start.AddDate(1, 0, 0)
|
||||
|
||||
zurich, err := time.LoadLocation("Europe/Zurich")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
// This was the day before a daylight saving time transition in Zürich.
|
||||
startZurich := time.Date(2023, 03, 25, 12, 0, 0, 0, zurich)
|
||||
oneDayLaterZurich := startZurich.AddDate(0, 0, 1)
|
||||
dayDurationZurich := oneDayLaterZurich.Sub(startZurich)
|
||||
|
||||
fmt.Printf("oneDayLater: start.AddDate(0, 0, 1) = %v\n", oneDayLater)
|
||||
fmt.Printf("oneMonthLater: start.AddDate(0, 1, 0) = %v\n", oneMonthLater)
|
||||
fmt.Printf("oneYearLater: start.AddDate(1, 0, 0) = %v\n", oneYearLater)
|
||||
fmt.Printf("oneDayLaterZurich: startZurich.AddDate(0, 0, 1) = %v\n", oneDayLaterZurich)
|
||||
fmt.Printf("Day duration in UTC: %v | Day duration in Zürich: %v\n", dayDuration, dayDurationZurich)
|
||||
|
||||
// Output:
|
||||
// oneDayLater: start.AddDate(0, 0, 1) = 2023-03-26 12:00:00 +0000 UTC
|
||||
// oneMonthLater: start.AddDate(0, 1, 0) = 2023-04-25 12:00:00 +0000 UTC
|
||||
// oneYearLater: start.AddDate(1, 0, 0) = 2024-03-25 12:00:00 +0000 UTC
|
||||
// oneDayLaterZurich: startZurich.AddDate(0, 0, 1) = 2023-03-26 12:00:00 +0200 CEST
|
||||
// Day duration in UTC: 24h0m0s | Day duration in Zürich: 23h0m0s
|
||||
}
|
||||
|
||||
func ExampleTime_After() {
|
||||
year2000 := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
year3000 := time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
isYear3000AfterYear2000 := year3000.After(year2000) // True
|
||||
isYear2000AfterYear3000 := year2000.After(year3000) // False
|
||||
|
||||
fmt.Printf("year3000.After(year2000) = %v\n", isYear3000AfterYear2000)
|
||||
fmt.Printf("year2000.After(year3000) = %v\n", isYear2000AfterYear3000)
|
||||
|
||||
// Output:
|
||||
// year3000.After(year2000) = true
|
||||
// year2000.After(year3000) = false
|
||||
}
|
||||
|
||||
func ExampleTime_Before() {
|
||||
year2000 := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
year3000 := time.Date(3000, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
|
||||
isYear2000BeforeYear3000 := year2000.Before(year3000) // True
|
||||
isYear3000BeforeYear2000 := year3000.Before(year2000) // False
|
||||
|
||||
fmt.Printf("year2000.Before(year3000) = %v\n", isYear2000BeforeYear3000)
|
||||
fmt.Printf("year3000.Before(year2000) = %v\n", isYear3000BeforeYear2000)
|
||||
|
||||
// Output:
|
||||
// year2000.Before(year3000) = true
|
||||
// year3000.Before(year2000) = false
|
||||
}
|
||||
|
||||
func ExampleTime_Date() {
|
||||
d := time.Date(2000, 2, 1, 12, 30, 0, 0, time.UTC)
|
||||
year, month, day := d.Date()
|
||||
|
||||
fmt.Printf("year = %v\n", year)
|
||||
fmt.Printf("month = %v\n", month)
|
||||
fmt.Printf("day = %v\n", day)
|
||||
|
||||
// Output:
|
||||
// year = 2000
|
||||
// month = February
|
||||
// day = 1
|
||||
}
|
||||
|
||||
func ExampleTime_Day() {
|
||||
d := time.Date(2000, 2, 1, 12, 30, 0, 0, time.UTC)
|
||||
day := d.Day()
|
||||
|
||||
fmt.Printf("day = %v\n", day)
|
||||
|
||||
// Output:
|
||||
// day = 1
|
||||
}
|
||||
|
||||
func ExampleTime_Equal() {
|
||||
secondsEastOfUTC := int((8 * time.Hour).Seconds())
|
||||
beijing := time.FixedZone("Beijing Time", secondsEastOfUTC)
|
||||
|
||||
// Unlike the equal operator, Equal is aware that d1 and d2 are the
|
||||
// same instant but in different time zones.
|
||||
d1 := time.Date(2000, 2, 1, 12, 30, 0, 0, time.UTC)
|
||||
d2 := time.Date(2000, 2, 1, 20, 30, 0, 0, beijing)
|
||||
|
||||
datesEqualUsingEqualOperator := d1 == d2
|
||||
datesEqualUsingFunction := d1.Equal(d2)
|
||||
|
||||
fmt.Printf("datesEqualUsingEqualOperator = %v\n", datesEqualUsingEqualOperator)
|
||||
fmt.Printf("datesEqualUsingFunction = %v\n", datesEqualUsingFunction)
|
||||
|
||||
// Output:
|
||||
// datesEqualUsingEqualOperator = false
|
||||
// datesEqualUsingFunction = true
|
||||
}
|
||||
|
||||
func ExampleTime_String() {
|
||||
timeWithNanoseconds := time.Date(2000, 2, 1, 12, 13, 14, 15, time.UTC)
|
||||
withNanoseconds := timeWithNanoseconds.String()
|
||||
|
||||
timeWithoutNanoseconds := time.Date(2000, 2, 1, 12, 13, 14, 0, time.UTC)
|
||||
withoutNanoseconds := timeWithoutNanoseconds.String()
|
||||
|
||||
fmt.Printf("withNanoseconds = %v\n", string(withNanoseconds))
|
||||
fmt.Printf("withoutNanoseconds = %v\n", string(withoutNanoseconds))
|
||||
|
||||
// Output:
|
||||
// withNanoseconds = 2000-02-01 12:13:14.000000015 +0000 UTC
|
||||
// withoutNanoseconds = 2000-02-01 12:13:14 +0000 UTC
|
||||
}
|
||||
|
||||
func ExampleTime_Sub() {
|
||||
start := time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||
end := time.Date(2000, 1, 1, 12, 0, 0, 0, time.UTC)
|
||||
|
||||
difference := end.Sub(start)
|
||||
fmt.Printf("difference = %v\n", difference)
|
||||
|
||||
// Output:
|
||||
// difference = 12h0m0s
|
||||
}
|
||||
|
||||
func ExampleTime_AppendFormat() {
|
||||
t := time.Date(2017, time.November, 4, 11, 0, 0, 0, time.UTC)
|
||||
text := []byte("Time: ")
|
||||
|
||||
text = t.AppendFormat(text, time.Kitchen)
|
||||
fmt.Println(string(text))
|
||||
|
||||
// Output:
|
||||
// Time: 11:00AM
|
||||
}
|
||||
|
||||
func ExampleFixedZone() {
|
||||
loc := time.FixedZone("UTC-8", -8*60*60)
|
||||
t := time.Date(2009, time.November, 10, 23, 0, 0, 0, loc)
|
||||
fmt.Println("The time is:", t.Format(time.RFC822))
|
||||
// Output: The time is: 10 Nov 09 23:00 UTC-8
|
||||
}
|
||||
16
src/time/export_android_test.go
Normal file
16
src/time/export_android_test.go
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package time
|
||||
|
||||
func ForceAndroidTzdataForTest() (undo func()) {
|
||||
allowGorootSource = false
|
||||
origLoadFromEmbeddedTZData := loadFromEmbeddedTZData
|
||||
loadFromEmbeddedTZData = nil
|
||||
|
||||
return func() {
|
||||
allowGorootSource = true
|
||||
loadFromEmbeddedTZData = origLoadFromEmbeddedTZData
|
||||
}
|
||||
}
|
||||
140
src/time/export_test.go
Normal file
140
src/time/export_test.go
Normal file
@@ -0,0 +1,140 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package time
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
func ResetLocalOnceForTest() {
|
||||
localOnce = sync.Once{}
|
||||
localLoc = Location{}
|
||||
}
|
||||
|
||||
func ForceUSPacificForTesting() {
|
||||
ResetLocalOnceForTest()
|
||||
localOnce.Do(initTestingZone)
|
||||
}
|
||||
|
||||
func ZoneinfoForTesting() *string {
|
||||
return zoneinfo
|
||||
}
|
||||
|
||||
func ResetZoneinfoForTesting() {
|
||||
zoneinfo = nil
|
||||
zoneinfoOnce = sync.Once{}
|
||||
}
|
||||
|
||||
var (
|
||||
DisablePlatformSources = disablePlatformSources
|
||||
GorootZoneSource = gorootZoneSource
|
||||
ParseTimeZone = parseTimeZone
|
||||
SetMono = (*Time).setMono
|
||||
GetMono = (*Time).mono
|
||||
ErrLocation = errLocation
|
||||
ReadFile = readFile
|
||||
LoadTzinfo = loadTzinfo
|
||||
NextStdChunk = nextStdChunk
|
||||
Tzset = tzset
|
||||
TzsetName = tzsetName
|
||||
TzsetOffset = tzsetOffset
|
||||
)
|
||||
|
||||
func LoadFromEmbeddedTZData(zone string) (string, error) {
|
||||
return loadFromEmbeddedTZData(zone)
|
||||
}
|
||||
|
||||
type RuleKind int
|
||||
|
||||
const (
|
||||
RuleJulian = RuleKind(ruleJulian)
|
||||
RuleDOY = RuleKind(ruleDOY)
|
||||
RuleMonthWeekDay = RuleKind(ruleMonthWeekDay)
|
||||
UnixToInternal = unixToInternal
|
||||
)
|
||||
|
||||
type Rule struct {
|
||||
Kind RuleKind
|
||||
Day int
|
||||
Week int
|
||||
Mon int
|
||||
Time int
|
||||
}
|
||||
|
||||
func TzsetRule(s string) (Rule, string, bool) {
|
||||
r, rs, ok := tzsetRule(s)
|
||||
rr := Rule{
|
||||
Kind: RuleKind(r.kind),
|
||||
Day: r.day,
|
||||
Week: r.week,
|
||||
Mon: r.mon,
|
||||
Time: r.time,
|
||||
}
|
||||
return rr, rs, ok
|
||||
}
|
||||
|
||||
// StdChunkNames maps from nextStdChunk results to the matched strings.
|
||||
var StdChunkNames = map[int]string{
|
||||
0: "",
|
||||
stdLongMonth: "January",
|
||||
stdMonth: "Jan",
|
||||
stdNumMonth: "1",
|
||||
stdZeroMonth: "01",
|
||||
stdLongWeekDay: "Monday",
|
||||
stdWeekDay: "Mon",
|
||||
stdDay: "2",
|
||||
stdUnderDay: "_2",
|
||||
stdZeroDay: "02",
|
||||
stdUnderYearDay: "__2",
|
||||
stdZeroYearDay: "002",
|
||||
stdHour: "15",
|
||||
stdHour12: "3",
|
||||
stdZeroHour12: "03",
|
||||
stdMinute: "4",
|
||||
stdZeroMinute: "04",
|
||||
stdSecond: "5",
|
||||
stdZeroSecond: "05",
|
||||
stdLongYear: "2006",
|
||||
stdYear: "06",
|
||||
stdPM: "PM",
|
||||
stdpm: "pm",
|
||||
stdTZ: "MST",
|
||||
stdISO8601TZ: "Z0700",
|
||||
stdISO8601SecondsTZ: "Z070000",
|
||||
stdISO8601ShortTZ: "Z07",
|
||||
stdISO8601ColonTZ: "Z07:00",
|
||||
stdISO8601ColonSecondsTZ: "Z07:00:00",
|
||||
stdNumTZ: "-0700",
|
||||
stdNumSecondsTz: "-070000",
|
||||
stdNumShortTZ: "-07",
|
||||
stdNumColonTZ: "-07:00",
|
||||
stdNumColonSecondsTZ: "-07:00:00",
|
||||
stdFracSecond0 | 1<<stdArgShift: ".0",
|
||||
stdFracSecond0 | 2<<stdArgShift: ".00",
|
||||
stdFracSecond0 | 3<<stdArgShift: ".000",
|
||||
stdFracSecond0 | 4<<stdArgShift: ".0000",
|
||||
stdFracSecond0 | 5<<stdArgShift: ".00000",
|
||||
stdFracSecond0 | 6<<stdArgShift: ".000000",
|
||||
stdFracSecond0 | 7<<stdArgShift: ".0000000",
|
||||
stdFracSecond0 | 8<<stdArgShift: ".00000000",
|
||||
stdFracSecond0 | 9<<stdArgShift: ".000000000",
|
||||
stdFracSecond9 | 1<<stdArgShift: ".9",
|
||||
stdFracSecond9 | 2<<stdArgShift: ".99",
|
||||
stdFracSecond9 | 3<<stdArgShift: ".999",
|
||||
stdFracSecond9 | 4<<stdArgShift: ".9999",
|
||||
stdFracSecond9 | 5<<stdArgShift: ".99999",
|
||||
stdFracSecond9 | 6<<stdArgShift: ".999999",
|
||||
stdFracSecond9 | 7<<stdArgShift: ".9999999",
|
||||
stdFracSecond9 | 8<<stdArgShift: ".99999999",
|
||||
stdFracSecond9 | 9<<stdArgShift: ".999999999",
|
||||
}
|
||||
|
||||
var Quote = quote
|
||||
|
||||
var AppendInt = appendInt
|
||||
var AppendFormatAny = Time.appendFormat
|
||||
var AppendFormatRFC3339 = Time.appendFormatRFC3339
|
||||
var ParseAny = parse
|
||||
var ParseRFC3339 = parseRFC3339[string]
|
||||
19
src/time/export_windows_test.go
Normal file
19
src/time/export_windows_test.go
Normal file
@@ -0,0 +1,19 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package time
|
||||
|
||||
func ForceAusFromTZIForTesting() {
|
||||
ResetLocalOnceForTest()
|
||||
localOnce.Do(func() { initLocalFromTZI(&aus) })
|
||||
}
|
||||
|
||||
func ForceUSPacificFromTZIForTesting() {
|
||||
ResetLocalOnceForTest()
|
||||
localOnce.Do(func() { initLocalFromTZI(&usPacific) })
|
||||
}
|
||||
|
||||
func ToEnglishName(stdname, dstname string) (string, error) {
|
||||
return toEnglishName(stdname, dstname)
|
||||
}
|
||||
1714
src/time/format.go
Normal file
1714
src/time/format.go
Normal file
File diff suppressed because it is too large
Load Diff
188
src/time/format_rfc3339.go
Normal file
188
src/time/format_rfc3339.go
Normal file
@@ -0,0 +1,188 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package time
|
||||
|
||||
import "errors"
|
||||
|
||||
// RFC 3339 is the most commonly used format.
|
||||
//
|
||||
// It is implicitly used by the Time.(Marshal|Unmarshal)(Text|JSON) methods.
|
||||
// Also, according to analysis on https://go.dev/issue/52746,
|
||||
// RFC 3339 accounts for 57% of all explicitly specified time formats,
|
||||
// with the second most popular format only being used 8% of the time.
|
||||
// The overwhelming use of RFC 3339 compared to all other formats justifies
|
||||
// the addition of logic to optimize formatting and parsing.
|
||||
|
||||
func (t Time) appendFormatRFC3339(b []byte, nanos bool) []byte {
|
||||
_, offset, abs := t.locabs()
|
||||
|
||||
// Format date.
|
||||
year, month, day, _ := absDate(abs, true)
|
||||
b = appendInt(b, year, 4)
|
||||
b = append(b, '-')
|
||||
b = appendInt(b, int(month), 2)
|
||||
b = append(b, '-')
|
||||
b = appendInt(b, day, 2)
|
||||
|
||||
b = append(b, 'T')
|
||||
|
||||
// Format time.
|
||||
hour, min, sec := absClock(abs)
|
||||
b = appendInt(b, hour, 2)
|
||||
b = append(b, ':')
|
||||
b = appendInt(b, min, 2)
|
||||
b = append(b, ':')
|
||||
b = appendInt(b, sec, 2)
|
||||
|
||||
if nanos {
|
||||
std := stdFracSecond(stdFracSecond9, 9, '.')
|
||||
b = appendNano(b, t.Nanosecond(), std)
|
||||
}
|
||||
|
||||
if offset == 0 {
|
||||
return append(b, 'Z')
|
||||
}
|
||||
|
||||
// Format zone.
|
||||
zone := offset / 60 // convert to minutes
|
||||
if zone < 0 {
|
||||
b = append(b, '-')
|
||||
zone = -zone
|
||||
} else {
|
||||
b = append(b, '+')
|
||||
}
|
||||
b = appendInt(b, zone/60, 2)
|
||||
b = append(b, ':')
|
||||
b = appendInt(b, zone%60, 2)
|
||||
return b
|
||||
}
|
||||
|
||||
func (t Time) appendStrictRFC3339(b []byte) ([]byte, error) {
|
||||
n0 := len(b)
|
||||
b = t.appendFormatRFC3339(b, true)
|
||||
|
||||
// Not all valid Go timestamps can be serialized as valid RFC 3339.
|
||||
// Explicitly check for these edge cases.
|
||||
// See https://go.dev/issue/4556 and https://go.dev/issue/54580.
|
||||
num2 := func(b []byte) byte { return 10*(b[0]-'0') + (b[1] - '0') }
|
||||
switch {
|
||||
case b[n0+len("9999")] != '-': // year must be exactly 4 digits wide
|
||||
return b, errors.New("year outside of range [0,9999]")
|
||||
case b[len(b)-1] != 'Z':
|
||||
c := b[len(b)-len("Z07:00")]
|
||||
if ('0' <= c && c <= '9') || num2(b[len(b)-len("07:00"):]) >= 24 {
|
||||
return b, errors.New("timezone hour outside of range [0,23]")
|
||||
}
|
||||
}
|
||||
return b, nil
|
||||
}
|
||||
|
||||
func parseRFC3339[bytes []byte | string](s bytes, local *Location) (Time, bool) {
|
||||
// parseUint parses s as an unsigned decimal integer and
|
||||
// verifies that it is within some range.
|
||||
// If it is invalid or out-of-range,
|
||||
// it sets ok to false and returns the min value.
|
||||
ok := true
|
||||
parseUint := func(s bytes, min, max int) (x int) {
|
||||
for _, c := range []byte(s) {
|
||||
if c < '0' || '9' < c {
|
||||
ok = false
|
||||
return min
|
||||
}
|
||||
x = x*10 + int(c) - '0'
|
||||
}
|
||||
if x < min || max < x {
|
||||
ok = false
|
||||
return min
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// Parse the date and time.
|
||||
if len(s) < len("2006-01-02T15:04:05") {
|
||||
return Time{}, false
|
||||
}
|
||||
year := parseUint(s[0:4], 0, 9999) // e.g., 2006
|
||||
month := parseUint(s[5:7], 1, 12) // e.g., 01
|
||||
day := parseUint(s[8:10], 1, daysIn(Month(month), year)) // e.g., 02
|
||||
hour := parseUint(s[11:13], 0, 23) // e.g., 15
|
||||
min := parseUint(s[14:16], 0, 59) // e.g., 04
|
||||
sec := parseUint(s[17:19], 0, 59) // e.g., 05
|
||||
if !ok || !(s[4] == '-' && s[7] == '-' && s[10] == 'T' && s[13] == ':' && s[16] == ':') {
|
||||
return Time{}, false
|
||||
}
|
||||
s = s[19:]
|
||||
|
||||
// Parse the fractional second.
|
||||
var nsec int
|
||||
if len(s) >= 2 && s[0] == '.' && isDigit(s, 1) {
|
||||
n := 2
|
||||
for ; n < len(s) && isDigit(s, n); n++ {
|
||||
}
|
||||
nsec, _, _ = parseNanoseconds(s, n)
|
||||
s = s[n:]
|
||||
}
|
||||
|
||||
// Parse the time zone.
|
||||
t := Date(year, Month(month), day, hour, min, sec, nsec, UTC)
|
||||
if len(s) != 1 || s[0] != 'Z' {
|
||||
if len(s) != len("-07:00") {
|
||||
return Time{}, false
|
||||
}
|
||||
hr := parseUint(s[1:3], 0, 23) // e.g., 07
|
||||
mm := parseUint(s[4:6], 0, 59) // e.g., 00
|
||||
if !ok || !((s[0] == '-' || s[0] == '+') && s[3] == ':') {
|
||||
return Time{}, false
|
||||
}
|
||||
zoneOffset := (hr*60 + mm) * 60
|
||||
if s[0] == '-' {
|
||||
zoneOffset *= -1
|
||||
}
|
||||
t.addSec(-int64(zoneOffset))
|
||||
|
||||
// Use local zone with the given offset if possible.
|
||||
if _, offset, _, _, _ := local.lookup(t.unixSec()); offset == zoneOffset {
|
||||
t.setLoc(local)
|
||||
} else {
|
||||
t.setLoc(FixedZone("", zoneOffset))
|
||||
}
|
||||
}
|
||||
return t, true
|
||||
}
|
||||
|
||||
func parseStrictRFC3339(b []byte) (Time, error) {
|
||||
t, ok := parseRFC3339(b, Local)
|
||||
if !ok {
|
||||
t, err := Parse(RFC3339, string(b))
|
||||
if err != nil {
|
||||
return Time{}, err
|
||||
}
|
||||
|
||||
// The parse template syntax cannot correctly validate RFC 3339.
|
||||
// Explicitly check for cases that Parse is unable to validate for.
|
||||
// See https://go.dev/issue/54580.
|
||||
num2 := func(b []byte) byte { return 10*(b[0]-'0') + (b[1] - '0') }
|
||||
switch {
|
||||
// TODO(https://go.dev/issue/54580): Strict parsing is disabled for now.
|
||||
// Enable this again with a GODEBUG opt-out.
|
||||
case true:
|
||||
return t, nil
|
||||
case b[len("2006-01-02T")+1] == ':': // hour must be two digits
|
||||
return Time{}, &ParseError{RFC3339, string(b), "15", string(b[len("2006-01-02T"):][:1]), ""}
|
||||
case b[len("2006-01-02T15:04:05")] == ',': // sub-second separator must be a period
|
||||
return Time{}, &ParseError{RFC3339, string(b), ".", ",", ""}
|
||||
case b[len(b)-1] != 'Z':
|
||||
switch {
|
||||
case num2(b[len(b)-len("07:00"):]) >= 24: // timezone hour must be in range
|
||||
return Time{}, &ParseError{RFC3339, string(b), "Z07:00", string(b[len(b)-len("Z07:00"):]), ": timezone hour out of range"}
|
||||
case num2(b[len(b)-len("00"):]) >= 60: // timezone minute must be in range
|
||||
return Time{}, &ParseError{RFC3339, string(b), "Z07:00", string(b[len(b)-len("Z07:00"):]), ": timezone minute out of range"}
|
||||
}
|
||||
default: // unknown error; should not occur
|
||||
return Time{}, &ParseError{RFC3339, string(b), RFC3339, string(b), ""}
|
||||
}
|
||||
}
|
||||
return t, nil
|
||||
}
|
||||
1093
src/time/format_test.go
Normal file
1093
src/time/format_test.go
Normal file
File diff suppressed because it is too large
Load Diff
157
src/time/genzabbrs.go
Normal file
157
src/time/genzabbrs.go
Normal file
@@ -0,0 +1,157 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ignore
|
||||
|
||||
//
|
||||
// usage:
|
||||
//
|
||||
// go run genzabbrs.go -output zoneinfo_abbrs_windows.go
|
||||
//
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/xml"
|
||||
"flag"
|
||||
"go/format"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
"text/template"
|
||||
"time"
|
||||
)
|
||||
|
||||
var filename = flag.String("output", "zoneinfo_abbrs_windows.go", "output file name")
|
||||
|
||||
// getAbbrs finds timezone abbreviations (standard and daylight saving time)
|
||||
// for location l.
|
||||
func getAbbrs(l *time.Location) (st, dt string) {
|
||||
t := time.Date(time.Now().Year(), 0, 1, 0, 0, 0, 0, l)
|
||||
abbr1, off1 := t.Zone()
|
||||
for i := 0; i < 12; i++ {
|
||||
t = t.AddDate(0, 1, 0)
|
||||
abbr2, off2 := t.Zone()
|
||||
if abbr1 != abbr2 {
|
||||
if off2-off1 < 0 { // southern hemisphere
|
||||
abbr1, abbr2 = abbr2, abbr1
|
||||
}
|
||||
return abbr1, abbr2
|
||||
}
|
||||
}
|
||||
return abbr1, abbr1
|
||||
}
|
||||
|
||||
type zone struct {
|
||||
WinName string
|
||||
UnixName string
|
||||
StTime string
|
||||
DSTime string
|
||||
}
|
||||
|
||||
const wzURL = "https://raw.githubusercontent.com/unicode-org/cldr/main/common/supplemental/windowsZones.xml"
|
||||
|
||||
type MapZone struct {
|
||||
Other string `xml:"other,attr"`
|
||||
Territory string `xml:"territory,attr"`
|
||||
Type string `xml:"type,attr"`
|
||||
}
|
||||
|
||||
type SupplementalData struct {
|
||||
Zones []MapZone `xml:"windowsZones>mapTimezones>mapZone"`
|
||||
}
|
||||
|
||||
func readWindowsZones() ([]*zone, error) {
|
||||
r, err := http.Get(wzURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer r.Body.Close()
|
||||
|
||||
data, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var sd SupplementalData
|
||||
err = xml.Unmarshal(data, &sd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
zs := make([]*zone, 0)
|
||||
for _, z := range sd.Zones {
|
||||
if z.Territory != "001" {
|
||||
// to avoid dups. I don't know why.
|
||||
continue
|
||||
}
|
||||
l, err := time.LoadLocation(z.Type)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
st, dt := getAbbrs(l)
|
||||
zs = append(zs, &zone{
|
||||
WinName: z.Other,
|
||||
UnixName: z.Type,
|
||||
StTime: st,
|
||||
DSTime: dt,
|
||||
})
|
||||
}
|
||||
return zs, nil
|
||||
}
|
||||
|
||||
func main() {
|
||||
flag.Parse()
|
||||
zs, err := readWindowsZones()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
slices.SortFunc(zs, func(a, b *zone) int {
|
||||
return strings.Compare(a.UnixName, b.UnixName)
|
||||
})
|
||||
var v = struct {
|
||||
URL string
|
||||
Zs []*zone
|
||||
}{
|
||||
wzURL,
|
||||
zs,
|
||||
}
|
||||
var buf bytes.Buffer
|
||||
err = template.Must(template.New("prog").Parse(prog)).Execute(&buf, v)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
data, err := format.Source(buf.Bytes())
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = os.WriteFile(*filename, data, 0644)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
const prog = `
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Code generated by genzabbrs.go; DO NOT EDIT.
|
||||
// Based on information from {{.URL}}
|
||||
|
||||
package time
|
||||
|
||||
type abbr struct {
|
||||
std string
|
||||
dst string
|
||||
}
|
||||
|
||||
var abbrs = map[string]abbr{
|
||||
{{range .Zs}} "{{.WinName}}": {"{{.StTime}}", "{{.DSTime}}"}, // {{.UnixName}}
|
||||
{{end}}}
|
||||
|
||||
`
|
||||
66
src/time/internal_test.go
Normal file
66
src/time/internal_test.go
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package time
|
||||
|
||||
func init() {
|
||||
// Force US/Pacific for time zone tests.
|
||||
ForceUSPacificForTesting()
|
||||
}
|
||||
|
||||
func initTestingZone() {
|
||||
// For hermeticity, use only tzinfo source from the test's GOROOT,
|
||||
// not the system sources and not whatever GOROOT may happen to be
|
||||
// set in the process's environment (if any).
|
||||
// This test runs in GOROOT/src/time, so GOROOT is "../..",
|
||||
// but it is theoretically possible
|
||||
sources := []string{"../../lib/time/zoneinfo.zip"}
|
||||
z, err := loadLocation("America/Los_Angeles", sources)
|
||||
if err != nil {
|
||||
panic("cannot load America/Los_Angeles for testing: " + err.Error() + "; you may want to use -tags=timetzdata")
|
||||
}
|
||||
z.name = "Local"
|
||||
localLoc = *z
|
||||
}
|
||||
|
||||
var origPlatformZoneSources []string = platformZoneSources
|
||||
|
||||
func disablePlatformSources() (undo func()) {
|
||||
platformZoneSources = nil
|
||||
return func() {
|
||||
platformZoneSources = origPlatformZoneSources
|
||||
}
|
||||
}
|
||||
|
||||
var Interrupt = interrupt
|
||||
var DaysIn = daysIn
|
||||
|
||||
func empty(arg any, seq uintptr, delta int64) {}
|
||||
|
||||
// Test that a runtimeTimer with a period that would overflow when on
|
||||
// expiration does not throw or cause other timers to hang.
|
||||
//
|
||||
// This test has to be in internal_test.go since it fiddles with
|
||||
// unexported data structures.
|
||||
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 := newTimer(runtimeNano(), 1<<63-1, empty, nil, nil)
|
||||
defer t.Stop()
|
||||
|
||||
// If this test fails, we will either throw (when siftdownTimer detects
|
||||
// bad when on update), or other timers will hang (if the timer in a
|
||||
// heap is in a bad state). There is no reliable way to test this, but
|
||||
// we wait on a short timer here as a smoke test (alternatively, timers
|
||||
// in later tests may hang).
|
||||
<-After(25 * Millisecond)
|
||||
}
|
||||
|
||||
var (
|
||||
MinMonoTime = Time{wall: 1 << 63, ext: -1 << 63, loc: UTC}
|
||||
MaxMonoTime = Time{wall: 1 << 63, ext: 1<<63 - 1, loc: UTC}
|
||||
|
||||
NotMonoNegativeTime = Time{wall: 0, ext: -1<<63 + 50}
|
||||
)
|
||||
278
src/time/mono_test.go
Normal file
278
src/time/mono_test.go
Normal file
@@ -0,0 +1,278 @@
|
||||
// Copyright 2017 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package time_test
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
. "time"
|
||||
)
|
||||
|
||||
func TestHasMonotonicClock(t *testing.T) {
|
||||
yes := func(expr string, tt Time) {
|
||||
if GetMono(&tt) == 0 {
|
||||
t.Errorf("%s: missing monotonic clock reading", expr)
|
||||
}
|
||||
}
|
||||
no := func(expr string, tt Time) {
|
||||
if GetMono(&tt) != 0 {
|
||||
t.Errorf("%s: unexpected monotonic clock reading", expr)
|
||||
}
|
||||
}
|
||||
|
||||
yes("<-After(1)", <-After(1))
|
||||
ticker := NewTicker(1)
|
||||
yes("<-Tick(1)", <-ticker.C)
|
||||
ticker.Stop()
|
||||
no("Date(2009, 11, 23, 0, 0, 0, 0, UTC)", Date(2009, 11, 23, 0, 0, 0, 0, UTC))
|
||||
tp, _ := Parse(UnixDate, "Sat Mar 7 11:06:39 PST 2015")
|
||||
no(`Parse(UnixDate, "Sat Mar 7 11:06:39 PST 2015")`, tp)
|
||||
no("Unix(1486057371, 0)", Unix(1486057371, 0))
|
||||
|
||||
yes("Now()", Now())
|
||||
|
||||
tu := Unix(1486057371, 0)
|
||||
tm := tu
|
||||
SetMono(&tm, 123456)
|
||||
no("tu", tu)
|
||||
yes("tm", tm)
|
||||
|
||||
no("tu.Add(1)", tu.Add(1))
|
||||
no("tu.In(UTC)", tu.In(UTC))
|
||||
no("tu.AddDate(1, 1, 1)", tu.AddDate(1, 1, 1))
|
||||
no("tu.AddDate(0, 0, 0)", tu.AddDate(0, 0, 0))
|
||||
no("tu.Local()", tu.Local())
|
||||
no("tu.UTC()", tu.UTC())
|
||||
no("tu.Round(2)", tu.Round(2))
|
||||
no("tu.Truncate(2)", tu.Truncate(2))
|
||||
|
||||
yes("tm.Add(1)", tm.Add(1))
|
||||
no("tm.AddDate(1, 1, 1)", tm.AddDate(1, 1, 1))
|
||||
no("tm.AddDate(0, 0, 0)", tm.AddDate(0, 0, 0))
|
||||
no("tm.In(UTC)", tm.In(UTC))
|
||||
no("tm.Local()", tm.Local())
|
||||
no("tm.UTC()", tm.UTC())
|
||||
no("tm.Round(2)", tm.Round(2))
|
||||
no("tm.Truncate(2)", tm.Truncate(2))
|
||||
}
|
||||
|
||||
func TestMonotonicAdd(t *testing.T) {
|
||||
tm := Unix(1486057371, 123456)
|
||||
SetMono(&tm, 123456789012345)
|
||||
|
||||
t2 := tm.Add(1e8)
|
||||
if t2.Nanosecond() != 100123456 {
|
||||
t.Errorf("t2.Nanosecond() = %d, want 100123456", t2.Nanosecond())
|
||||
}
|
||||
if GetMono(&t2) != 123456889012345 {
|
||||
t.Errorf("t2.mono = %d, want 123456889012345", GetMono(&t2))
|
||||
}
|
||||
|
||||
t3 := tm.Add(-9e18) // wall now out of range
|
||||
if t3.Nanosecond() != 123456 {
|
||||
t.Errorf("t3.Nanosecond() = %d, want 123456", t3.Nanosecond())
|
||||
}
|
||||
if GetMono(&t3) != 0 {
|
||||
t.Errorf("t3.mono = %d, want 0 (wall time out of range for monotonic reading)", GetMono(&t3))
|
||||
}
|
||||
|
||||
t4 := tm.Add(+9e18) // wall now out of range
|
||||
if t4.Nanosecond() != 123456 {
|
||||
t.Errorf("t4.Nanosecond() = %d, want 123456", t4.Nanosecond())
|
||||
}
|
||||
if GetMono(&t4) != 0 {
|
||||
t.Errorf("t4.mono = %d, want 0 (wall time out of range for monotonic reading)", GetMono(&t4))
|
||||
}
|
||||
|
||||
tn := Now()
|
||||
tn1 := tn.Add(1 * Hour)
|
||||
Sleep(100 * Millisecond)
|
||||
d := Until(tn1)
|
||||
if d < 59*Minute {
|
||||
t.Errorf("Until(Now().Add(1*Hour)) = %v, wanted at least 59m", d)
|
||||
}
|
||||
now := Now()
|
||||
if now.After(tn1) {
|
||||
t.Errorf("Now().After(Now().Add(1*Hour)) = true, want false")
|
||||
}
|
||||
if !tn1.After(now) {
|
||||
t.Errorf("Now().Add(1*Hour).After(now) = false, want true")
|
||||
}
|
||||
if tn1.Before(now) {
|
||||
t.Errorf("Now().Add(1*Hour).Before(Now()) = true, want false")
|
||||
}
|
||||
if !now.Before(tn1) {
|
||||
t.Errorf("Now().Before(Now().Add(1*Hour)) = false, want true")
|
||||
}
|
||||
if got, want := now.Compare(tn1), -1; got != want {
|
||||
t.Errorf("Now().Compare(Now().Add(1*Hour)) = %d, want %d", got, want)
|
||||
}
|
||||
if got, want := tn1.Compare(now), 1; got != want {
|
||||
t.Errorf("Now().Add(1*Hour).Compare(Now()) = %d, want %d", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMonotonicSub(t *testing.T) {
|
||||
t1 := Unix(1483228799, 995e6)
|
||||
SetMono(&t1, 123456789012345)
|
||||
|
||||
t2 := Unix(1483228799, 5e6)
|
||||
SetMono(&t2, 123456789012345+10e6)
|
||||
|
||||
t3 := Unix(1483228799, 995e6)
|
||||
SetMono(&t3, 123456789012345+1e9)
|
||||
|
||||
t1w := t1.AddDate(0, 0, 0)
|
||||
if GetMono(&t1w) != 0 {
|
||||
t.Fatalf("AddDate didn't strip monotonic clock reading")
|
||||
}
|
||||
t2w := t2.AddDate(0, 0, 0)
|
||||
if GetMono(&t2w) != 0 {
|
||||
t.Fatalf("AddDate didn't strip monotonic clock reading")
|
||||
}
|
||||
t3w := t3.AddDate(0, 0, 0)
|
||||
if GetMono(&t3w) != 0 {
|
||||
t.Fatalf("AddDate didn't strip monotonic clock reading")
|
||||
}
|
||||
|
||||
sub := func(txs, tys string, tx, txw, ty, tyw Time, d, dw Duration) {
|
||||
check := func(expr string, d, want Duration) {
|
||||
if d != want {
|
||||
t.Errorf("%s = %v, want %v", expr, d, want)
|
||||
}
|
||||
}
|
||||
check(txs+".Sub("+tys+")", tx.Sub(ty), d)
|
||||
check(txs+"w.Sub("+tys+")", txw.Sub(ty), dw)
|
||||
check(txs+".Sub("+tys+"w)", tx.Sub(tyw), dw)
|
||||
check(txs+"w.Sub("+tys+"w)", txw.Sub(tyw), dw)
|
||||
}
|
||||
sub("t1", "t1", t1, t1w, t1, t1w, 0, 0)
|
||||
sub("t1", "t2", t1, t1w, t2, t2w, -10*Millisecond, 990*Millisecond)
|
||||
sub("t1", "t3", t1, t1w, t3, t3w, -1000*Millisecond, 0)
|
||||
|
||||
sub("t2", "t1", t2, t2w, t1, t1w, 10*Millisecond, -990*Millisecond)
|
||||
sub("t2", "t2", t2, t2w, t2, t2w, 0, 0)
|
||||
sub("t2", "t3", t2, t2w, t3, t3w, -990*Millisecond, -990*Millisecond)
|
||||
|
||||
sub("t3", "t1", t3, t3w, t1, t1w, 1000*Millisecond, 0)
|
||||
sub("t3", "t2", t3, t3w, t2, t2w, 990*Millisecond, 990*Millisecond)
|
||||
sub("t3", "t3", t3, t3w, t3, t3w, 0, 0)
|
||||
|
||||
cmp := func(txs, tys string, tx, txw, ty, tyw Time, c, cw int) {
|
||||
check := func(expr string, b, want any) {
|
||||
if b != want {
|
||||
t.Errorf("%s = %v, want %v", expr, b, want)
|
||||
}
|
||||
}
|
||||
check(txs+".After("+tys+")", tx.After(ty), c > 0)
|
||||
check(txs+"w.After("+tys+")", txw.After(ty), cw > 0)
|
||||
check(txs+".After("+tys+"w)", tx.After(tyw), cw > 0)
|
||||
check(txs+"w.After("+tys+"w)", txw.After(tyw), cw > 0)
|
||||
|
||||
check(txs+".Before("+tys+")", tx.Before(ty), c < 0)
|
||||
check(txs+"w.Before("+tys+")", txw.Before(ty), cw < 0)
|
||||
check(txs+".Before("+tys+"w)", tx.Before(tyw), cw < 0)
|
||||
check(txs+"w.Before("+tys+"w)", txw.Before(tyw), cw < 0)
|
||||
|
||||
check(txs+".Equal("+tys+")", tx.Equal(ty), c == 0)
|
||||
check(txs+"w.Equal("+tys+")", txw.Equal(ty), cw == 0)
|
||||
check(txs+".Equal("+tys+"w)", tx.Equal(tyw), cw == 0)
|
||||
check(txs+"w.Equal("+tys+"w)", txw.Equal(tyw), cw == 0)
|
||||
|
||||
check(txs+".Compare("+tys+")", tx.Compare(ty), c)
|
||||
check(txs+"w.Compare("+tys+")", txw.Compare(ty), cw)
|
||||
check(txs+".Compare("+tys+"w)", tx.Compare(tyw), cw)
|
||||
check(txs+"w.Compare("+tys+"w)", txw.Compare(tyw), cw)
|
||||
}
|
||||
|
||||
cmp("t1", "t1", t1, t1w, t1, t1w, 0, 0)
|
||||
cmp("t1", "t2", t1, t1w, t2, t2w, -1, +1)
|
||||
cmp("t1", "t3", t1, t1w, t3, t3w, -1, 0)
|
||||
|
||||
cmp("t2", "t1", t2, t2w, t1, t1w, +1, -1)
|
||||
cmp("t2", "t2", t2, t2w, t2, t2w, 0, 0)
|
||||
cmp("t2", "t3", t2, t2w, t3, t3w, -1, -1)
|
||||
|
||||
cmp("t3", "t1", t3, t3w, t1, t1w, +1, 0)
|
||||
cmp("t3", "t2", t3, t3w, t2, t2w, +1, +1)
|
||||
cmp("t3", "t3", t3, t3w, t3, t3w, 0, 0)
|
||||
}
|
||||
|
||||
func TestMonotonicOverflow(t *testing.T) {
|
||||
t1 := Now().Add(-30 * Second)
|
||||
d := Until(t1)
|
||||
if d < -35*Second || -30*Second < d {
|
||||
t.Errorf("Until(Now().Add(-30s)) = %v, want roughly -30s (-35s to -30s)", d)
|
||||
}
|
||||
|
||||
t1 = Now().Add(30 * Second)
|
||||
d = Until(t1)
|
||||
if d < 25*Second || 30*Second < d {
|
||||
t.Errorf("Until(Now().Add(-30s)) = %v, want roughly 30s (25s to 30s)", d)
|
||||
}
|
||||
|
||||
t0 := Now()
|
||||
t1 = t0.Add(Duration(1<<63 - 1))
|
||||
if GetMono(&t1) != 0 {
|
||||
t.Errorf("Now().Add(maxDuration) has monotonic clock reading (%v => %v %d %d)", t0.String(), t1.String(), t0.Unix(), t1.Unix())
|
||||
}
|
||||
t2 := t1.Add(-Duration(1<<63 - 1))
|
||||
d = Since(t2)
|
||||
if d < -10*Second || 10*Second < d {
|
||||
t.Errorf("Since(Now().Add(max).Add(-max)) = %v, want [-10s, 10s]", d)
|
||||
}
|
||||
|
||||
t0 = Now()
|
||||
t1 = t0.Add(1 * Hour)
|
||||
Sleep(100 * Millisecond)
|
||||
t2 = Now().Add(-5 * Second)
|
||||
if !t1.After(t2) {
|
||||
t.Errorf("Now().Add(1*Hour).After(Now().Add(-5*Second)) = false, want true\nt1=%v\nt2=%v", t1, t2)
|
||||
}
|
||||
if t2.After(t1) {
|
||||
t.Errorf("Now().Add(-5*Second).After(Now().Add(1*Hour)) = true, want false\nt1=%v\nt2=%v", t1, t2)
|
||||
}
|
||||
if t1.Before(t2) {
|
||||
t.Errorf("Now().Add(1*Hour).Before(Now().Add(-5*Second)) = true, want false\nt1=%v\nt2=%v", t1, t2)
|
||||
}
|
||||
if !t2.Before(t1) {
|
||||
t.Errorf("Now().Add(-5*Second).Before(Now().Add(1*Hour)) = false, want true\nt1=%v\nt2=%v", t1, t2)
|
||||
}
|
||||
if got, want := t1.Compare(t2), 1; got != want {
|
||||
t.Errorf("Now().Add(1*Hour).Compare(Now().Add(-5*Second)) = %d, want %d\nt1=%v\nt2=%v", got, want, t1, t2)
|
||||
}
|
||||
if got, want := t2.Compare(t1), -1; got != want {
|
||||
t.Errorf("Now().Add(-5*Second).Before(Now().Add(1*Hour)) = %d, want %d\nt1=%v\nt2=%v", got, want, t1, t2)
|
||||
}
|
||||
}
|
||||
|
||||
var monotonicStringTests = []struct {
|
||||
mono int64
|
||||
want string
|
||||
}{
|
||||
{0, "m=+0.000000000"},
|
||||
{123456789, "m=+0.123456789"},
|
||||
{-123456789, "m=-0.123456789"},
|
||||
{123456789000, "m=+123.456789000"},
|
||||
{-123456789000, "m=-123.456789000"},
|
||||
{9e18, "m=+9000000000.000000000"},
|
||||
{-9e18, "m=-9000000000.000000000"},
|
||||
{-1 << 63, "m=-9223372036.854775808"},
|
||||
}
|
||||
|
||||
func TestMonotonicString(t *testing.T) {
|
||||
t1 := Now()
|
||||
t.Logf("Now() = %v", t1)
|
||||
|
||||
for _, tt := range monotonicStringTests {
|
||||
t1 := Now()
|
||||
SetMono(&t1, tt.mono)
|
||||
s := t1.String()
|
||||
got := s[strings.LastIndex(s, " ")+1:]
|
||||
if got != tt.want {
|
||||
t.Errorf("with mono=%d: got %q; want %q", tt.mono, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
216
src/time/sleep.go
Normal file
216
src/time/sleep.go
Normal file
@@ -0,0 +1,216 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package time
|
||||
|
||||
import (
|
||||
"internal/godebug"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
// Sleep pauses the current goroutine for at least the duration d.
|
||||
// A negative or zero duration causes Sleep to return immediately.
|
||||
func Sleep(d Duration)
|
||||
|
||||
var asynctimerchan = godebug.New("asynctimerchan")
|
||||
|
||||
// syncTimer returns c as an unsafe.Pointer, for passing to newTimer.
|
||||
// If the GODEBUG asynctimerchan has disabled the async timer chan
|
||||
// code, then syncTimer always returns nil, to disable the special
|
||||
// channel code paths in the runtime.
|
||||
func syncTimer(c chan Time) unsafe.Pointer {
|
||||
// If asynctimerchan=1, we don't even tell the runtime
|
||||
// about channel timers, so that we get the pre-Go 1.23 code paths.
|
||||
if asynctimerchan.Value() == "1" {
|
||||
asynctimerchan.IncNonDefault()
|
||||
return nil
|
||||
}
|
||||
|
||||
// Otherwise pass to runtime.
|
||||
// This handles asynctimerchan=0, which is the default Go 1.23 behavior,
|
||||
// as well as asynctimerchan=2, which is like asynctimerchan=1
|
||||
// but implemented entirely by the runtime.
|
||||
// The only reason to use asynctimerchan=2 is for debugging
|
||||
// a problem fixed by asynctimerchan=1: it enables the new
|
||||
// GC-able timer channels (#61542) but not the sync channels (#37196).
|
||||
//
|
||||
// If we decide to roll back the sync channels, we will still have
|
||||
// a fully tested async runtime implementation (asynctimerchan=2)
|
||||
// and can make this function always return c.
|
||||
//
|
||||
// If we decide to keep the sync channels, we can delete all the
|
||||
// handling of asynctimerchan in the runtime and keep just this
|
||||
// function to handle asynctimerchan=1.
|
||||
return *(*unsafe.Pointer)(unsafe.Pointer(&c))
|
||||
}
|
||||
|
||||
// when is a helper function for setting the 'when' field of a runtimeTimer.
|
||||
// It returns what the time will be, in nanoseconds, Duration d in the future.
|
||||
// If d is negative, it is ignored. If the returned value would be less than
|
||||
// zero because of an overflow, MaxInt64 is returned.
|
||||
func when(d Duration) int64 {
|
||||
if d <= 0 {
|
||||
return runtimeNano()
|
||||
}
|
||||
t := runtimeNano() + int64(d)
|
||||
if t < 0 {
|
||||
// N.B. runtimeNano() and d are always positive, so addition
|
||||
// (including overflow) will never result in t == 0.
|
||||
t = 1<<63 - 1 // math.MaxInt64
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
// These functions are pushed to package time from package runtime.
|
||||
|
||||
// The arg cp is a chan Time, but the declaration in runtime uses a pointer,
|
||||
// so we use a pointer here too. This keeps some tools that aggressively
|
||||
// compare linknamed symbol definitions happier.
|
||||
//
|
||||
//go:linkname newTimer
|
||||
func newTimer(when, period int64, f func(any, uintptr, int64), arg any, cp unsafe.Pointer) *Timer
|
||||
|
||||
//go:linkname stopTimer
|
||||
func stopTimer(*Timer) bool
|
||||
|
||||
//go:linkname resetTimer
|
||||
func resetTimer(t *Timer, when, period int64) bool
|
||||
|
||||
// Note: The runtime knows the layout of struct Timer, since newTimer allocates it.
|
||||
// The runtime also knows that Ticker and Timer have the same layout.
|
||||
// There are extra fields after the channel, reserved for the runtime
|
||||
// and inaccessible to users.
|
||||
|
||||
// The Timer type represents a single event.
|
||||
// When the Timer expires, the current time will be sent on C,
|
||||
// unless the Timer was created by [AfterFunc].
|
||||
// A Timer must be created with [NewTimer] or AfterFunc.
|
||||
type Timer struct {
|
||||
C <-chan Time
|
||||
initTimer bool
|
||||
}
|
||||
|
||||
// Stop prevents the [Timer] from firing.
|
||||
// It returns true if the call stops the timer, false if the timer has already
|
||||
// expired or been stopped.
|
||||
//
|
||||
// For a func-based timer created with [AfterFunc](d, f),
|
||||
// if t.Stop returns false, then the timer has already expired
|
||||
// and the function f has been started in its own goroutine;
|
||||
// Stop does not wait for f to complete before returning.
|
||||
// If the caller needs to know whether f is completed,
|
||||
// it must coordinate with f explicitly.
|
||||
//
|
||||
// For a chan-based timer created with NewTimer(d), as of Go 1.23,
|
||||
// any receive from t.C after Stop has returned is guaranteed to block
|
||||
// rather than receive a stale time value from before the Stop;
|
||||
// if the program has not received from t.C already and the timer is
|
||||
// running, Stop is guaranteed to return true.
|
||||
// Before Go 1.23, the only safe way to use Stop was insert an extra
|
||||
// <-t.C if Stop returned false to drain a potential stale value.
|
||||
// See the [NewTimer] documentation for more details.
|
||||
func (t *Timer) Stop() bool {
|
||||
if !t.initTimer {
|
||||
panic("time: Stop called on uninitialized Timer")
|
||||
}
|
||||
return stopTimer(t)
|
||||
}
|
||||
|
||||
// NewTimer creates a new Timer that will send
|
||||
// the current time on its channel after at least duration d.
|
||||
//
|
||||
// Before Go 1.23, the garbage collector did not recover
|
||||
// timers that had not yet expired or been stopped, so code often
|
||||
// immediately deferred t.Stop after calling NewTimer, to make
|
||||
// the timer recoverable when it was no longer needed.
|
||||
// As of Go 1.23, the garbage collector can recover unreferenced
|
||||
// timers, even if they haven't expired or been stopped.
|
||||
// The Stop method is no longer necessary to help the garbage collector.
|
||||
// (Code may of course still want to call Stop to stop the timer for other reasons.)
|
||||
//
|
||||
// Before Go 1.23, the channel associated with a Timer was
|
||||
// asynchronous (buffered, capacity 1), which meant that
|
||||
// stale time values could be received even after [Timer.Stop]
|
||||
// or [Timer.Reset] returned.
|
||||
// As of Go 1.23, the channel is synchronous (unbuffered, capacity 0),
|
||||
// eliminating the possibility of those stale values.
|
||||
//
|
||||
// The GODEBUG setting asynctimerchan=1 restores both pre-Go 1.23
|
||||
// behaviors: when set, unexpired timers won't be garbage collected, and
|
||||
// channels will have buffered capacity. This setting may be removed
|
||||
// in Go 1.27 or later.
|
||||
func NewTimer(d Duration) *Timer {
|
||||
c := make(chan Time, 1)
|
||||
t := (*Timer)(newTimer(when(d), 0, sendTime, c, syncTimer(c)))
|
||||
t.C = c
|
||||
return t
|
||||
}
|
||||
|
||||
// Reset changes the timer to expire after duration d.
|
||||
// It returns true if the timer had been active, false if the timer had
|
||||
// expired or been stopped.
|
||||
//
|
||||
// For a func-based timer created with [AfterFunc](d, f), Reset either reschedules
|
||||
// when f will run, in which case Reset returns true, or schedules f
|
||||
// to run again, in which case it returns false.
|
||||
// When Reset returns false, Reset neither waits for the prior f to
|
||||
// complete before returning nor does it guarantee that the subsequent
|
||||
// goroutine running f does not run concurrently with the prior
|
||||
// one. If the caller needs to know whether the prior execution of
|
||||
// f is completed, it must coordinate with f explicitly.
|
||||
//
|
||||
// For a chan-based timer created with NewTimer, as of Go 1.23,
|
||||
// any receive from t.C after Reset has returned is guaranteed not
|
||||
// to receive a time value corresponding to the previous timer settings;
|
||||
// if the program has not received from t.C already and the timer is
|
||||
// running, Reset is guaranteed to return true.
|
||||
// Before Go 1.23, the only safe way to use Reset was to [Stop] and
|
||||
// explicitly drain the timer first.
|
||||
// See the [NewTimer] documentation for more details.
|
||||
func (t *Timer) Reset(d Duration) bool {
|
||||
if !t.initTimer {
|
||||
panic("time: Reset called on uninitialized Timer")
|
||||
}
|
||||
w := when(d)
|
||||
return resetTimer(t, w, 0)
|
||||
}
|
||||
|
||||
// sendTime does a non-blocking send of the current time on c.
|
||||
func sendTime(c any, seq uintptr, delta int64) {
|
||||
// delta is how long ago the channel send was supposed to happen.
|
||||
// The current time can be arbitrarily far into the future, because the runtime
|
||||
// can delay a sendTime call until a goroutines tries to receive from
|
||||
// the channel. Subtract delta to go back to the old time that we
|
||||
// used to send.
|
||||
select {
|
||||
case c.(chan Time) <- Now().Add(Duration(-delta)):
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// After waits for the duration to elapse and then sends the current time
|
||||
// on the returned channel.
|
||||
// It is equivalent to [NewTimer](d).C.
|
||||
//
|
||||
// Before Go 1.23, this documentation warned that the underlying
|
||||
// [Timer] would not be recovered by the garbage collector until the
|
||||
// timer fired, and that if efficiency was a concern, code should use
|
||||
// NewTimer instead and call [Timer.Stop] if the timer is no longer needed.
|
||||
// As of Go 1.23, the garbage collector can recover unreferenced,
|
||||
// unstopped timers. There is no reason to prefer NewTimer when After will do.
|
||||
func After(d Duration) <-chan Time {
|
||||
return NewTimer(d).C
|
||||
}
|
||||
|
||||
// AfterFunc waits for the duration to elapse and then calls f
|
||||
// in its own goroutine. It returns a [Timer] that can
|
||||
// be used to cancel the call using its Stop method.
|
||||
// The returned Timer's C field is not used and will be nil.
|
||||
func AfterFunc(d Duration, f func()) *Timer {
|
||||
return (*Timer)(newTimer(when(d), 0, goFunc, f, nil))
|
||||
}
|
||||
|
||||
func goFunc(arg any, seq uintptr, delta int64) {
|
||||
go arg.(func())()
|
||||
}
|
||||
991
src/time/sleep_test.go
Normal file
991
src/time/sleep_test.go
Normal file
@@ -0,0 +1,991 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package time_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"internal/testenv"
|
||||
"math/rand"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
. "time"
|
||||
_ "unsafe" // for go:linkname
|
||||
)
|
||||
|
||||
// newTimerFunc simulates NewTimer using AfterFunc,
|
||||
// but this version will not hit the special cases for channels
|
||||
// that are used when calling NewTimer.
|
||||
// This makes it easy to test both paths.
|
||||
func newTimerFunc(d Duration) *Timer {
|
||||
c := make(chan Time, 1)
|
||||
t := AfterFunc(d, func() { c <- Now() })
|
||||
t.C = c
|
||||
return t
|
||||
}
|
||||
|
||||
// haveHighResSleep is true if the system supports at least ~1ms sleeps.
|
||||
//
|
||||
//go:linkname haveHighResSleep runtime.haveHighResSleep
|
||||
var haveHighResSleep bool
|
||||
|
||||
// adjustDelay returns an adjusted delay based on the system sleep resolution.
|
||||
// Go runtime uses different Windows timers for time.Now and sleeping.
|
||||
// These can tick at different frequencies and can arrive out of sync.
|
||||
// The effect can be seen, for example, as time.Sleep(100ms) is actually
|
||||
// shorter then 100ms when measured as difference between time.Now before and
|
||||
// after time.Sleep call. This was observed on Windows XP SP3 (windows/386).
|
||||
func adjustDelay(t *testing.T, delay Duration) Duration {
|
||||
if haveHighResSleep {
|
||||
return delay
|
||||
}
|
||||
t.Log("adjusting delay for low resolution sleep")
|
||||
switch runtime.GOOS {
|
||||
case "windows":
|
||||
return delay - 17*Millisecond
|
||||
default:
|
||||
t.Fatal("adjustDelay unimplemented on " + runtime.GOOS)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func TestSleep(t *testing.T) {
|
||||
const delay = 100 * Millisecond
|
||||
go func() {
|
||||
Sleep(delay / 2)
|
||||
Interrupt()
|
||||
}()
|
||||
start := Now()
|
||||
Sleep(delay)
|
||||
delayadj := adjustDelay(t, delay)
|
||||
duration := Since(start)
|
||||
if duration < delayadj {
|
||||
t.Fatalf("Sleep(%s) slept for only %s", delay, duration)
|
||||
}
|
||||
}
|
||||
|
||||
// Test the basic function calling behavior. Correct queuing
|
||||
// behavior is tested elsewhere, since After and AfterFunc share
|
||||
// the same code.
|
||||
func TestAfterFunc(t *testing.T) {
|
||||
i := 10
|
||||
c := make(chan bool)
|
||||
var f func()
|
||||
f = func() {
|
||||
i--
|
||||
if i >= 0 {
|
||||
AfterFunc(0, f)
|
||||
Sleep(1 * Second)
|
||||
} else {
|
||||
c <- true
|
||||
}
|
||||
}
|
||||
|
||||
AfterFunc(0, f)
|
||||
<-c
|
||||
}
|
||||
|
||||
func TestTickerStress(t *testing.T) {
|
||||
var stop atomic.Bool
|
||||
go func() {
|
||||
for !stop.Load() {
|
||||
runtime.GC()
|
||||
// Yield so that the OS can wake up the timer thread,
|
||||
// so that it can generate channel sends for the main goroutine,
|
||||
// which will eventually set stop = 1 for us.
|
||||
Sleep(Nanosecond)
|
||||
}
|
||||
}()
|
||||
ticker := NewTicker(1)
|
||||
for i := 0; i < 100; i++ {
|
||||
<-ticker.C
|
||||
}
|
||||
ticker.Stop()
|
||||
stop.Store(true)
|
||||
}
|
||||
|
||||
func TestTickerConcurrentStress(t *testing.T) {
|
||||
var stop atomic.Bool
|
||||
go func() {
|
||||
for !stop.Load() {
|
||||
runtime.GC()
|
||||
// Yield so that the OS can wake up the timer thread,
|
||||
// so that it can generate channel sends for the main goroutine,
|
||||
// which will eventually set stop = 1 for us.
|
||||
Sleep(Nanosecond)
|
||||
}
|
||||
}()
|
||||
ticker := NewTicker(1)
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 100; i++ {
|
||||
<-ticker.C
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
ticker.Stop()
|
||||
stop.Store(true)
|
||||
}
|
||||
|
||||
func TestAfterFuncStarvation(t *testing.T) {
|
||||
// Start two goroutines ping-ponging on a channel send.
|
||||
// At any given time, at least one of these goroutines is runnable:
|
||||
// if the channel buffer is full, the receiver is runnable,
|
||||
// and if it is not full, the sender is runnable.
|
||||
//
|
||||
// In addition, the AfterFunc callback should become runnable after
|
||||
// the indicated delay.
|
||||
//
|
||||
// Even if GOMAXPROCS=1, we expect the runtime to eventually schedule
|
||||
// the AfterFunc goroutine instead of the runnable channel goroutine.
|
||||
// However, in https://go.dev/issue/65178 this was observed to live-lock
|
||||
// on wasip1/wasm and js/wasm after <10000 runs.
|
||||
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1))
|
||||
|
||||
var (
|
||||
wg sync.WaitGroup
|
||||
stop atomic.Bool
|
||||
c = make(chan bool, 1)
|
||||
)
|
||||
|
||||
wg.Add(2)
|
||||
go func() {
|
||||
for !stop.Load() {
|
||||
c <- true
|
||||
}
|
||||
close(c)
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
for range c {
|
||||
}
|
||||
wg.Done()
|
||||
}()
|
||||
|
||||
AfterFunc(1*Microsecond, func() { stop.Store(true) })
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func benchmark(b *testing.B, bench func(*testing.PB)) {
|
||||
// Create equal number of garbage timers on each P before starting
|
||||
// the benchmark.
|
||||
var wg sync.WaitGroup
|
||||
garbageAll := make([][]*Timer, runtime.GOMAXPROCS(0))
|
||||
for i := range garbageAll {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
garbage := make([]*Timer, 1<<15)
|
||||
for j := range garbage {
|
||||
garbage[j] = AfterFunc(Hour, nil)
|
||||
}
|
||||
garbageAll[i] = garbage
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
b.ResetTimer()
|
||||
b.RunParallel(bench)
|
||||
b.StopTimer()
|
||||
|
||||
for _, garbage := range garbageAll {
|
||||
for _, t := range garbage {
|
||||
t.Stop()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAfterFunc1000(b *testing.B) {
|
||||
benchmark(b, func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
n := 1000
|
||||
c := make(chan bool)
|
||||
var f func()
|
||||
f = func() {
|
||||
n--
|
||||
if n >= 0 {
|
||||
AfterFunc(0, f)
|
||||
} else {
|
||||
c <- true
|
||||
}
|
||||
}
|
||||
AfterFunc(0, f)
|
||||
<-c
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkAfter(b *testing.B) {
|
||||
benchmark(b, func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
<-After(1)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkStop(b *testing.B) {
|
||||
b.Run("impl=chan", func(b *testing.B) {
|
||||
benchmark(b, func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
NewTimer(1 * Second).Stop()
|
||||
}
|
||||
})
|
||||
})
|
||||
b.Run("impl=func", func(b *testing.B) {
|
||||
benchmark(b, func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
newTimerFunc(1 * Second).Stop()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkSimultaneousAfterFunc1000(b *testing.B) {
|
||||
benchmark(b, func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
n := 1000
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(n)
|
||||
for range n {
|
||||
AfterFunc(0, wg.Done)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkStartStop1000(b *testing.B) {
|
||||
benchmark(b, func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
const N = 1000
|
||||
timers := make([]*Timer, N)
|
||||
for i := range timers {
|
||||
timers[i] = AfterFunc(Hour, nil)
|
||||
}
|
||||
|
||||
for i := range timers {
|
||||
timers[i].Stop()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkReset(b *testing.B) {
|
||||
b.Run("impl=chan", func(b *testing.B) {
|
||||
benchmark(b, func(pb *testing.PB) {
|
||||
t := NewTimer(Hour)
|
||||
for pb.Next() {
|
||||
t.Reset(Hour)
|
||||
}
|
||||
t.Stop()
|
||||
})
|
||||
})
|
||||
b.Run("impl=func", func(b *testing.B) {
|
||||
benchmark(b, func(pb *testing.PB) {
|
||||
t := newTimerFunc(Hour)
|
||||
for pb.Next() {
|
||||
t.Reset(Hour)
|
||||
}
|
||||
t.Stop()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkSleep1000(b *testing.B) {
|
||||
benchmark(b, func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
const N = 1000
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(N)
|
||||
for range N {
|
||||
go func() {
|
||||
Sleep(Nanosecond)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestAfter(t *testing.T) {
|
||||
const delay = 100 * Millisecond
|
||||
start := Now()
|
||||
end := <-After(delay)
|
||||
delayadj := adjustDelay(t, delay)
|
||||
if duration := Since(start); duration < delayadj {
|
||||
t.Fatalf("After(%s) slept for only %d ns", delay, duration)
|
||||
}
|
||||
if min := start.Add(delayadj); end.Before(min) {
|
||||
t.Fatalf("After(%s) expect >= %s, got %s", delay, min, end)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAfterTick(t *testing.T) {
|
||||
t.Parallel()
|
||||
const Count = 10
|
||||
Delta := 100 * Millisecond
|
||||
if testing.Short() {
|
||||
Delta = 10 * Millisecond
|
||||
}
|
||||
t0 := Now()
|
||||
for i := 0; i < Count; i++ {
|
||||
<-After(Delta)
|
||||
}
|
||||
t1 := Now()
|
||||
d := t1.Sub(t0)
|
||||
target := Delta * Count
|
||||
if d < target*9/10 {
|
||||
t.Fatalf("%d ticks of %s too fast: took %s, expected %s", Count, Delta, d, target)
|
||||
}
|
||||
if !testing.Short() && d > target*30/10 {
|
||||
t.Fatalf("%d ticks of %s too slow: took %s, expected %s", Count, Delta, d, target)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAfterStop(t *testing.T) {
|
||||
t.Run("impl=chan", func(t *testing.T) {
|
||||
testAfterStop(t, NewTimer)
|
||||
})
|
||||
t.Run("impl=func", func(t *testing.T) {
|
||||
testAfterStop(t, newTimerFunc)
|
||||
})
|
||||
}
|
||||
|
||||
func testAfterStop(t *testing.T, newTimer func(Duration) *Timer) {
|
||||
// We want to test that we stop a timer before it runs.
|
||||
// We also want to test that it didn't run after a longer timer.
|
||||
// Since we don't want the test to run for too long, we don't
|
||||
// want to use lengthy times. That makes the test inherently flaky.
|
||||
// So only report an error if it fails five times in a row.
|
||||
|
||||
var errs []string
|
||||
logErrs := func() {
|
||||
for _, e := range errs {
|
||||
t.Log(e)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < 5; i++ {
|
||||
AfterFunc(100*Millisecond, func() {})
|
||||
t0 := newTimer(50 * Millisecond)
|
||||
c1 := make(chan bool, 1)
|
||||
t1 := AfterFunc(150*Millisecond, func() { c1 <- true })
|
||||
c2 := After(200 * Millisecond)
|
||||
if !t0.Stop() {
|
||||
errs = append(errs, "failed to stop event 0")
|
||||
continue
|
||||
}
|
||||
if !t1.Stop() {
|
||||
errs = append(errs, "failed to stop event 1")
|
||||
continue
|
||||
}
|
||||
<-c2
|
||||
select {
|
||||
case <-t0.C:
|
||||
errs = append(errs, "event 0 was not stopped")
|
||||
continue
|
||||
case <-c1:
|
||||
errs = append(errs, "event 1 was not stopped")
|
||||
continue
|
||||
default:
|
||||
}
|
||||
if t1.Stop() {
|
||||
errs = append(errs, "Stop returned true twice")
|
||||
continue
|
||||
}
|
||||
|
||||
// Test passed, so all done.
|
||||
if len(errs) > 0 {
|
||||
t.Logf("saw %d errors, ignoring to avoid flakiness", len(errs))
|
||||
logErrs()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
t.Errorf("saw %d errors", len(errs))
|
||||
logErrs()
|
||||
}
|
||||
|
||||
func TestAfterQueuing(t *testing.T) {
|
||||
t.Run("impl=chan", func(t *testing.T) {
|
||||
testAfterQueuing(t, After)
|
||||
})
|
||||
t.Run("impl=func", func(t *testing.T) {
|
||||
testAfterQueuing(t, func(d Duration) <-chan Time { return newTimerFunc(d).C })
|
||||
})
|
||||
}
|
||||
|
||||
func testAfterQueuing(t *testing.T, after func(Duration) <-chan Time) {
|
||||
// This test flakes out on some systems,
|
||||
// so we'll try it a few times before declaring it a failure.
|
||||
const attempts = 5
|
||||
err := errors.New("!=nil")
|
||||
for i := 0; i < attempts && err != nil; i++ {
|
||||
delta := Duration(20+i*50) * Millisecond
|
||||
if err = testAfterQueuing1(delta, after); err != nil {
|
||||
t.Logf("attempt %v failed: %v", i, err)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
var slots = []int{5, 3, 6, 6, 6, 1, 1, 2, 7, 9, 4, 8, 0}
|
||||
|
||||
type afterResult struct {
|
||||
slot int
|
||||
t Time
|
||||
}
|
||||
|
||||
func await(slot int, result chan<- afterResult, ac <-chan Time) {
|
||||
result <- afterResult{slot, <-ac}
|
||||
}
|
||||
|
||||
func testAfterQueuing1(delta Duration, after func(Duration) <-chan Time) error {
|
||||
// make the result channel buffered because we don't want
|
||||
// to depend on channel queuing semantics that might
|
||||
// possibly change in the future.
|
||||
result := make(chan afterResult, len(slots))
|
||||
|
||||
t0 := Now()
|
||||
for _, slot := range slots {
|
||||
go await(slot, result, After(Duration(slot)*delta))
|
||||
}
|
||||
var order []int
|
||||
var times []Time
|
||||
for range slots {
|
||||
r := <-result
|
||||
order = append(order, r.slot)
|
||||
times = append(times, r.t)
|
||||
}
|
||||
for i := range order {
|
||||
if i > 0 && order[i] < order[i-1] {
|
||||
return fmt.Errorf("After calls returned out of order: %v", order)
|
||||
}
|
||||
}
|
||||
for i, t := range times {
|
||||
dt := t.Sub(t0)
|
||||
target := Duration(order[i]) * delta
|
||||
if dt < target-delta/2 || dt > target+delta*10 {
|
||||
return fmt.Errorf("After(%s) arrived at %s, expected [%s,%s]", target, dt, target-delta/2, target+delta*10)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestTimerStopStress(t *testing.T) {
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
t.Parallel()
|
||||
for i := 0; i < 100; i++ {
|
||||
go func(i int) {
|
||||
timer := AfterFunc(2*Second, func() {
|
||||
t.Errorf("timer %d was not stopped", i)
|
||||
})
|
||||
Sleep(1 * Second)
|
||||
timer.Stop()
|
||||
}(i)
|
||||
}
|
||||
Sleep(3 * Second)
|
||||
}
|
||||
|
||||
func TestSleepZeroDeadlock(t *testing.T) {
|
||||
// Sleep(0) used to hang, the sequence of events was as follows.
|
||||
// Sleep(0) sets G's status to Gwaiting, but then immediately returns leaving the status.
|
||||
// Then the goroutine calls e.g. new and falls down into the scheduler due to pending GC.
|
||||
// After the GC nobody wakes up the goroutine from Gwaiting status.
|
||||
defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(4))
|
||||
c := make(chan bool)
|
||||
go func() {
|
||||
for i := 0; i < 100; i++ {
|
||||
runtime.GC()
|
||||
}
|
||||
c <- true
|
||||
}()
|
||||
for i := 0; i < 100; i++ {
|
||||
Sleep(0)
|
||||
tmp := make(chan bool, 1)
|
||||
tmp <- true
|
||||
<-tmp
|
||||
}
|
||||
<-c
|
||||
}
|
||||
|
||||
func testReset(d Duration) error {
|
||||
t0 := NewTimer(2 * d)
|
||||
Sleep(d)
|
||||
if !t0.Reset(3 * d) {
|
||||
return errors.New("resetting unfired timer returned false")
|
||||
}
|
||||
Sleep(2 * d)
|
||||
select {
|
||||
case <-t0.C:
|
||||
return errors.New("timer fired early")
|
||||
default:
|
||||
}
|
||||
Sleep(2 * d)
|
||||
select {
|
||||
case <-t0.C:
|
||||
default:
|
||||
return errors.New("reset timer did not fire")
|
||||
}
|
||||
|
||||
if t0.Reset(50 * Millisecond) {
|
||||
return errors.New("resetting expired timer returned true")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestReset(t *testing.T) {
|
||||
// We try to run this test with increasingly larger multiples
|
||||
// until one works so slow, loaded hardware isn't as flaky,
|
||||
// but without slowing down fast machines unnecessarily.
|
||||
//
|
||||
// (maxDuration is several orders of magnitude longer than we
|
||||
// expect this test to actually take on a fast, unloaded machine.)
|
||||
d := 1 * Millisecond
|
||||
const maxDuration = 10 * Second
|
||||
for {
|
||||
err := testReset(d)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
d *= 2
|
||||
if d > maxDuration {
|
||||
t.Error(err)
|
||||
}
|
||||
t.Logf("%v; trying duration %v", err, d)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that sleeping (via Sleep or Timer) for an interval so large it
|
||||
// overflows does not result in a short sleep duration. Nor does it interfere
|
||||
// with execution of other timers. If it does, timers in this or subsequent
|
||||
// tests may not fire.
|
||||
func TestOverflowSleep(t *testing.T) {
|
||||
const big = Duration(int64(1<<63 - 1))
|
||||
|
||||
go func() {
|
||||
Sleep(big)
|
||||
// On failure, this may return after the test has completed, so
|
||||
// we need to panic instead.
|
||||
panic("big sleep returned")
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-After(big):
|
||||
t.Fatalf("big timeout fired")
|
||||
case <-After(25 * Millisecond):
|
||||
// OK
|
||||
}
|
||||
|
||||
const neg = Duration(-1 << 63)
|
||||
Sleep(neg) // Returns immediately.
|
||||
select {
|
||||
case <-After(neg):
|
||||
// OK
|
||||
case <-After(1 * Second):
|
||||
t.Fatalf("negative timeout didn't fire")
|
||||
}
|
||||
}
|
||||
|
||||
// Test that a panic while deleting a timer does not leave
|
||||
// the timers mutex held, deadlocking a ticker.Stop in a defer.
|
||||
func TestIssue5745(t *testing.T) {
|
||||
ticker := NewTicker(Hour)
|
||||
defer func() {
|
||||
// would deadlock here before the fix due to
|
||||
// lock taken before the segfault.
|
||||
ticker.Stop()
|
||||
|
||||
if r := recover(); r == nil {
|
||||
t.Error("Expected panic, but none happened.")
|
||||
}
|
||||
}()
|
||||
|
||||
// cause a panic due to a segfault
|
||||
var timer *Timer
|
||||
timer.Stop()
|
||||
t.Error("Should be unreachable.")
|
||||
}
|
||||
|
||||
func TestOverflowPeriodRuntimeTimer(t *testing.T) {
|
||||
// This may hang forever if timers are broken. See comment near
|
||||
// the end of CheckRuntimeTimerOverflow in internal_test.go.
|
||||
CheckRuntimeTimerPeriodOverflow()
|
||||
}
|
||||
|
||||
func checkZeroPanicString(t *testing.T) {
|
||||
e := recover()
|
||||
s, _ := e.(string)
|
||||
if want := "called on uninitialized Timer"; !strings.Contains(s, want) {
|
||||
t.Errorf("panic = %v; want substring %q", e, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestZeroTimerResetPanics(t *testing.T) {
|
||||
defer checkZeroPanicString(t)
|
||||
var tr Timer
|
||||
tr.Reset(1)
|
||||
}
|
||||
|
||||
func TestZeroTimerStopPanics(t *testing.T) {
|
||||
defer checkZeroPanicString(t)
|
||||
var tr Timer
|
||||
tr.Stop()
|
||||
}
|
||||
|
||||
// Test that zero duration timers aren't missed by the scheduler. Regression test for issue 44868.
|
||||
func TestZeroTimer(t *testing.T) {
|
||||
t.Run("impl=chan", func(t *testing.T) {
|
||||
testZeroTimer(t, NewTimer)
|
||||
})
|
||||
t.Run("impl=func", func(t *testing.T) {
|
||||
testZeroTimer(t, newTimerFunc)
|
||||
})
|
||||
t.Run("impl=cache", func(t *testing.T) {
|
||||
timer := newTimerFunc(Hour)
|
||||
testZeroTimer(t, func(d Duration) *Timer {
|
||||
timer.Reset(d)
|
||||
return timer
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func testZeroTimer(t *testing.T, newTimer func(Duration) *Timer) {
|
||||
if testing.Short() {
|
||||
t.Skip("-short")
|
||||
}
|
||||
|
||||
for i := 0; i < 1000000; i++ {
|
||||
s := Now()
|
||||
ti := newTimer(0)
|
||||
<-ti.C
|
||||
if diff := Since(s); diff > 2*Second {
|
||||
t.Errorf("Expected time to get value from Timer channel in less than 2 sec, took %v", diff)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test that rapidly moving a timer earlier doesn't cause it to get dropped.
|
||||
// Issue 47329.
|
||||
func TestTimerModifiedEarlier(t *testing.T) {
|
||||
if runtime.GOOS == "plan9" && runtime.GOARCH == "arm" {
|
||||
testenv.SkipFlaky(t, 50470)
|
||||
}
|
||||
|
||||
past := Until(Unix(0, 0))
|
||||
count := 1000
|
||||
fail := 0
|
||||
for i := 0; i < count; i++ {
|
||||
timer := newTimerFunc(Hour)
|
||||
for j := 0; j < 10; j++ {
|
||||
if !timer.Stop() {
|
||||
<-timer.C
|
||||
}
|
||||
timer.Reset(past)
|
||||
}
|
||||
|
||||
deadline := NewTimer(10 * Second)
|
||||
defer deadline.Stop()
|
||||
now := Now()
|
||||
select {
|
||||
case <-timer.C:
|
||||
if since := Since(now); since > 8*Second {
|
||||
t.Errorf("timer took too long (%v)", since)
|
||||
fail++
|
||||
}
|
||||
case <-deadline.C:
|
||||
t.Error("deadline expired")
|
||||
}
|
||||
}
|
||||
|
||||
if fail > 0 {
|
||||
t.Errorf("%d failures", fail)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that rapidly moving timers earlier and later doesn't cause
|
||||
// some of the sleep times to be lost.
|
||||
// Issue 47762
|
||||
func TestAdjustTimers(t *testing.T) {
|
||||
var rnd = rand.New(rand.NewSource(Now().UnixNano()))
|
||||
|
||||
timers := make([]*Timer, 100)
|
||||
states := make([]int, len(timers))
|
||||
indices := rnd.Perm(len(timers))
|
||||
|
||||
for len(indices) != 0 {
|
||||
var ii = rnd.Intn(len(indices))
|
||||
var i = indices[ii]
|
||||
|
||||
var timer = timers[i]
|
||||
var state = states[i]
|
||||
states[i]++
|
||||
|
||||
switch state {
|
||||
case 0:
|
||||
timers[i] = newTimerFunc(0)
|
||||
|
||||
case 1:
|
||||
<-timer.C // Timer is now idle.
|
||||
|
||||
// Reset to various long durations, which we'll cancel.
|
||||
case 2:
|
||||
if timer.Reset(1 * Minute) {
|
||||
panic("shouldn't be active (1)")
|
||||
}
|
||||
case 4:
|
||||
if timer.Reset(3 * Minute) {
|
||||
panic("shouldn't be active (3)")
|
||||
}
|
||||
case 6:
|
||||
if timer.Reset(2 * Minute) {
|
||||
panic("shouldn't be active (2)")
|
||||
}
|
||||
|
||||
// Stop and drain a long-duration timer.
|
||||
case 3, 5, 7:
|
||||
if !timer.Stop() {
|
||||
t.Logf("timer %d state %d Stop returned false", i, state)
|
||||
<-timer.C
|
||||
}
|
||||
|
||||
// Start a short-duration timer we expect to select without blocking.
|
||||
case 8:
|
||||
if timer.Reset(0) {
|
||||
t.Fatal("timer.Reset returned true")
|
||||
}
|
||||
case 9:
|
||||
now := Now()
|
||||
<-timer.C
|
||||
dur := Since(now)
|
||||
if dur > 750*Millisecond {
|
||||
t.Errorf("timer %d took %v to complete", i, dur)
|
||||
}
|
||||
|
||||
// Timer is done. Swap with tail and remove.
|
||||
case 10:
|
||||
indices[ii] = indices[len(indices)-1]
|
||||
indices = indices[:len(indices)-1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmark timer latency when the thread that creates the timer is busy with
|
||||
// other work and the timers must be serviced by other threads.
|
||||
// https://golang.org/issue/38860
|
||||
func BenchmarkParallelTimerLatency(b *testing.B) {
|
||||
gmp := runtime.GOMAXPROCS(0)
|
||||
if gmp < 2 || runtime.NumCPU() < gmp {
|
||||
b.Skip("skipping with GOMAXPROCS < 2 or NumCPU < GOMAXPROCS")
|
||||
}
|
||||
|
||||
// allocate memory now to avoid GC interference later.
|
||||
timerCount := gmp - 1
|
||||
stats := make([]struct {
|
||||
sum float64
|
||||
max Duration
|
||||
count int64
|
||||
_ [5]int64 // cache line padding
|
||||
}, timerCount)
|
||||
|
||||
// Ensure the time to start new threads to service timers will not pollute
|
||||
// the results.
|
||||
warmupScheduler(gmp)
|
||||
|
||||
// Note that other than the AfterFunc calls this benchmark is measuring it
|
||||
// avoids using any other timers. In particular, the main goroutine uses
|
||||
// doWork to spin for some durations because up through Go 1.15 if all
|
||||
// threads are idle sysmon could leave deep sleep when we wake.
|
||||
|
||||
// Ensure sysmon is in deep sleep.
|
||||
doWork(30 * Millisecond)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
const delay = Millisecond
|
||||
var wg sync.WaitGroup
|
||||
var count int32
|
||||
for i := 0; i < b.N; i++ {
|
||||
wg.Add(timerCount)
|
||||
atomic.StoreInt32(&count, 0)
|
||||
for j := 0; j < timerCount; j++ {
|
||||
j := j
|
||||
expectedWakeup := Now().Add(delay)
|
||||
AfterFunc(delay, func() {
|
||||
late := Since(expectedWakeup)
|
||||
if late < 0 {
|
||||
late = 0
|
||||
}
|
||||
stats[j].count++
|
||||
stats[j].sum += float64(late.Nanoseconds())
|
||||
if late > stats[j].max {
|
||||
stats[j].max = late
|
||||
}
|
||||
atomic.AddInt32(&count, 1)
|
||||
for atomic.LoadInt32(&count) < int32(timerCount) {
|
||||
// spin until all timers fired
|
||||
}
|
||||
wg.Done()
|
||||
})
|
||||
}
|
||||
|
||||
for atomic.LoadInt32(&count) < int32(timerCount) {
|
||||
// spin until all timers fired
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
// Spin for a bit to let the other scheduler threads go idle before the
|
||||
// next round.
|
||||
doWork(Millisecond)
|
||||
}
|
||||
var total float64
|
||||
var samples float64
|
||||
max := Duration(0)
|
||||
for _, s := range stats {
|
||||
if s.max > max {
|
||||
max = s.max
|
||||
}
|
||||
total += s.sum
|
||||
samples += float64(s.count)
|
||||
}
|
||||
b.ReportMetric(0, "ns/op")
|
||||
b.ReportMetric(total/samples, "avg-late-ns")
|
||||
b.ReportMetric(float64(max.Nanoseconds()), "max-late-ns")
|
||||
}
|
||||
|
||||
// Benchmark timer latency with staggered wakeup times and varying CPU bound
|
||||
// workloads. https://golang.org/issue/38860
|
||||
func BenchmarkStaggeredTickerLatency(b *testing.B) {
|
||||
gmp := runtime.GOMAXPROCS(0)
|
||||
if gmp < 2 || runtime.NumCPU() < gmp {
|
||||
b.Skip("skipping with GOMAXPROCS < 2 or NumCPU < GOMAXPROCS")
|
||||
}
|
||||
|
||||
const delay = 3 * Millisecond
|
||||
|
||||
for _, dur := range []Duration{300 * Microsecond, 2 * Millisecond} {
|
||||
b.Run(fmt.Sprintf("work-dur=%s", dur), func(b *testing.B) {
|
||||
for tickersPerP := 1; tickersPerP < int(delay/dur)+1; tickersPerP++ {
|
||||
tickerCount := gmp * tickersPerP
|
||||
b.Run(fmt.Sprintf("tickers-per-P=%d", tickersPerP), func(b *testing.B) {
|
||||
// allocate memory now to avoid GC interference later.
|
||||
stats := make([]struct {
|
||||
sum float64
|
||||
max Duration
|
||||
count int64
|
||||
_ [5]int64 // cache line padding
|
||||
}, tickerCount)
|
||||
|
||||
// Ensure the time to start new threads to service timers
|
||||
// will not pollute the results.
|
||||
warmupScheduler(gmp)
|
||||
|
||||
b.ResetTimer()
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(tickerCount)
|
||||
for j := 0; j < tickerCount; j++ {
|
||||
j := j
|
||||
doWork(delay / Duration(gmp))
|
||||
expectedWakeup := Now().Add(delay)
|
||||
ticker := NewTicker(delay)
|
||||
go func(c int, ticker *Ticker, firstWake Time) {
|
||||
defer ticker.Stop()
|
||||
|
||||
for ; c > 0; c-- {
|
||||
<-ticker.C
|
||||
late := Since(expectedWakeup)
|
||||
if late < 0 {
|
||||
late = 0
|
||||
}
|
||||
stats[j].count++
|
||||
stats[j].sum += float64(late.Nanoseconds())
|
||||
if late > stats[j].max {
|
||||
stats[j].max = late
|
||||
}
|
||||
expectedWakeup = expectedWakeup.Add(delay)
|
||||
doWork(dur)
|
||||
}
|
||||
wg.Done()
|
||||
}(b.N, ticker, expectedWakeup)
|
||||
}
|
||||
wg.Wait()
|
||||
|
||||
var total float64
|
||||
var samples float64
|
||||
max := Duration(0)
|
||||
for _, s := range stats {
|
||||
if s.max > max {
|
||||
max = s.max
|
||||
}
|
||||
total += s.sum
|
||||
samples += float64(s.count)
|
||||
}
|
||||
b.ReportMetric(0, "ns/op")
|
||||
b.ReportMetric(total/samples, "avg-late-ns")
|
||||
b.ReportMetric(float64(max.Nanoseconds()), "max-late-ns")
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// warmupScheduler ensures the scheduler has at least targetThreadCount threads
|
||||
// in its thread pool.
|
||||
func warmupScheduler(targetThreadCount int) {
|
||||
var wg sync.WaitGroup
|
||||
var count int32
|
||||
for i := 0; i < targetThreadCount; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
atomic.AddInt32(&count, 1)
|
||||
for atomic.LoadInt32(&count) < int32(targetThreadCount) {
|
||||
// spin until all threads started
|
||||
}
|
||||
|
||||
// spin a bit more to ensure they are all running on separate CPUs.
|
||||
doWork(Millisecond)
|
||||
wg.Done()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func doWork(dur Duration) {
|
||||
start := Now()
|
||||
for Since(start) < dur {
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkAdjustTimers10000(b *testing.B) {
|
||||
benchmark(b, func(pb *testing.PB) {
|
||||
for pb.Next() {
|
||||
const n = 10000
|
||||
timers := make([]*Timer, 0, n)
|
||||
for range n {
|
||||
t := AfterFunc(Hour, func() {})
|
||||
timers = append(timers, t)
|
||||
}
|
||||
timers[n-1].Reset(Nanosecond)
|
||||
Sleep(Microsecond)
|
||||
for _, t := range timers {
|
||||
t.Stop()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
54
src/time/sys_plan9.go
Normal file
54
src/time/sys_plan9.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build plan9
|
||||
|
||||
package time
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// for testing: whatever interrupts a sleep
|
||||
func interrupt() {
|
||||
// cannot predict pid, don't want to kill group
|
||||
}
|
||||
|
||||
func open(name string) (uintptr, error) {
|
||||
fd, err := syscall.Open(name, syscall.O_RDONLY)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uintptr(fd), nil
|
||||
}
|
||||
|
||||
func read(fd uintptr, buf []byte) (int, error) {
|
||||
return syscall.Read(int(fd), buf)
|
||||
}
|
||||
|
||||
func closefd(fd uintptr) {
|
||||
syscall.Close(int(fd))
|
||||
}
|
||||
|
||||
func preadn(fd uintptr, buf []byte, off int) error {
|
||||
whence := seekStart
|
||||
if off < 0 {
|
||||
whence = seekEnd
|
||||
}
|
||||
if _, err := syscall.Seek(int(fd), int64(off), whence); err != nil {
|
||||
return err
|
||||
}
|
||||
for len(buf) > 0 {
|
||||
m, err := syscall.Read(int(fd), buf)
|
||||
if m <= 0 {
|
||||
if err == nil {
|
||||
return errors.New("short read")
|
||||
}
|
||||
return err
|
||||
}
|
||||
buf = buf[m:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
62
src/time/sys_unix.go
Normal file
62
src/time/sys_unix.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build unix || (js && wasm) || wasip1
|
||||
|
||||
package time
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// for testing: whatever interrupts a sleep
|
||||
func interrupt() {
|
||||
// There is no mechanism in wasi to interrupt the call to poll_oneoff
|
||||
// used to implement runtime.usleep so this function does nothing, which
|
||||
// somewhat defeats the purpose of TestSleep but we are still better off
|
||||
// validating that time elapses when the process calls time.Sleep than
|
||||
// skipping the test altogether.
|
||||
if runtime.GOOS != "wasip1" {
|
||||
syscall.Kill(syscall.Getpid(), syscall.SIGCHLD)
|
||||
}
|
||||
}
|
||||
|
||||
func open(name string) (uintptr, error) {
|
||||
fd, err := syscall.Open(name, syscall.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return uintptr(fd), nil
|
||||
}
|
||||
|
||||
func read(fd uintptr, buf []byte) (int, error) {
|
||||
return syscall.Read(int(fd), buf)
|
||||
}
|
||||
|
||||
func closefd(fd uintptr) {
|
||||
syscall.Close(int(fd))
|
||||
}
|
||||
|
||||
func preadn(fd uintptr, buf []byte, off int) error {
|
||||
whence := seekStart
|
||||
if off < 0 {
|
||||
whence = seekEnd
|
||||
}
|
||||
if _, err := syscall.Seek(int(fd), int64(off), whence); err != nil {
|
||||
return err
|
||||
}
|
||||
for len(buf) > 0 {
|
||||
m, err := syscall.Read(int(fd), buf)
|
||||
if m <= 0 {
|
||||
if err == nil {
|
||||
return errors.New("short read")
|
||||
}
|
||||
return err
|
||||
}
|
||||
buf = buf[m:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
55
src/time/sys_windows.go
Normal file
55
src/time/sys_windows.go
Normal file
@@ -0,0 +1,55 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package time
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// for testing: whatever interrupts a sleep
|
||||
func interrupt() {
|
||||
}
|
||||
|
||||
func open(name string) (uintptr, error) {
|
||||
fd, err := syscall.Open(name, syscall.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
// This condition solves issue https://go.dev/issue/50248
|
||||
if err == syscall.ERROR_PATH_NOT_FOUND {
|
||||
err = syscall.ENOENT
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
return uintptr(fd), nil
|
||||
}
|
||||
|
||||
func read(fd uintptr, buf []byte) (int, error) {
|
||||
return syscall.Read(syscall.Handle(fd), buf)
|
||||
}
|
||||
|
||||
func closefd(fd uintptr) {
|
||||
syscall.Close(syscall.Handle(fd))
|
||||
}
|
||||
|
||||
func preadn(fd uintptr, buf []byte, off int) error {
|
||||
whence := seekStart
|
||||
if off < 0 {
|
||||
whence = seekEnd
|
||||
}
|
||||
if _, err := syscall.Seek(syscall.Handle(fd), int64(off), whence); err != nil {
|
||||
return err
|
||||
}
|
||||
for len(buf) > 0 {
|
||||
m, err := syscall.Read(syscall.Handle(fd), buf)
|
||||
if m <= 0 {
|
||||
if err == nil {
|
||||
return errors.New("short read")
|
||||
}
|
||||
return err
|
||||
}
|
||||
buf = buf[m:]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
BIN
src/time/testdata/2020b_Europe_Berlin
vendored
Normal file
BIN
src/time/testdata/2020b_Europe_Berlin
vendored
Normal file
Binary file not shown.
BIN
src/time/testdata/2021a_America_Nuuk
vendored
Normal file
BIN
src/time/testdata/2021a_America_Nuuk
vendored
Normal file
Binary file not shown.
BIN
src/time/testdata/2021a_Asia_Gaza
vendored
Normal file
BIN
src/time/testdata/2021a_Asia_Gaza
vendored
Normal file
Binary file not shown.
BIN
src/time/testdata/2021a_Europe_Dublin
vendored
Normal file
BIN
src/time/testdata/2021a_Europe_Dublin
vendored
Normal file
Binary file not shown.
91
src/time/tick.go
Normal file
91
src/time/tick.go
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package time
|
||||
|
||||
import "unsafe"
|
||||
|
||||
// Note: The runtime knows the layout of struct Ticker, since newTimer allocates it.
|
||||
// Note also that Ticker and Timer have the same layout, so that newTimer can handle both.
|
||||
// The initTimer and initTicker fields are named differently so that
|
||||
// users cannot convert between the two without unsafe.
|
||||
|
||||
// A Ticker holds a channel that delivers “ticks” of a clock
|
||||
// at intervals.
|
||||
type Ticker struct {
|
||||
C <-chan Time // The channel on which the ticks are delivered.
|
||||
initTicker bool
|
||||
}
|
||||
|
||||
// NewTicker returns a new [Ticker] containing a channel that will send
|
||||
// the current time on the channel after each tick. The period of the
|
||||
// ticks is specified by the duration argument. The ticker will adjust
|
||||
// the time interval or drop ticks to make up for slow receivers.
|
||||
// The duration d must be greater than zero; if not, NewTicker will
|
||||
// panic.
|
||||
//
|
||||
// Before Go 1.23, the garbage collector did not recover
|
||||
// tickers that had not yet expired or been stopped, so code often
|
||||
// immediately deferred t.Stop after calling NewTicker, to make
|
||||
// the ticker recoverable when it was no longer needed.
|
||||
// As of Go 1.23, the garbage collector can recover unreferenced
|
||||
// tickers, even if they haven't been stopped.
|
||||
// The Stop method is no longer necessary to help the garbage collector.
|
||||
// (Code may of course still want to call Stop to stop the ticker for other reasons.)
|
||||
func NewTicker(d Duration) *Ticker {
|
||||
if d <= 0 {
|
||||
panic("non-positive interval for NewTicker")
|
||||
}
|
||||
// Give the channel a 1-element time buffer.
|
||||
// If the client falls behind while reading, we drop ticks
|
||||
// on the floor until the client catches up.
|
||||
c := make(chan Time, 1)
|
||||
t := (*Ticker)(unsafe.Pointer(newTimer(when(d), int64(d), sendTime, c, syncTimer(c))))
|
||||
t.C = c
|
||||
return t
|
||||
}
|
||||
|
||||
// Stop turns off a ticker. After Stop, no more ticks will be sent.
|
||||
// Stop does not close the channel, to prevent a concurrent goroutine
|
||||
// reading from the channel from seeing an erroneous "tick".
|
||||
func (t *Ticker) Stop() {
|
||||
if !t.initTicker {
|
||||
// This is misuse, and the same for time.Timer would panic,
|
||||
// but this didn't always panic, and we keep it not panicking
|
||||
// to avoid breaking old programs. See issue 21874.
|
||||
return
|
||||
}
|
||||
stopTimer((*Timer)(unsafe.Pointer(t)))
|
||||
}
|
||||
|
||||
// Reset stops a ticker and resets its period to the specified duration.
|
||||
// The next tick will arrive after the new period elapses. The duration d
|
||||
// must be greater than zero; if not, Reset will panic.
|
||||
func (t *Ticker) Reset(d Duration) {
|
||||
if d <= 0 {
|
||||
panic("non-positive interval for Ticker.Reset")
|
||||
}
|
||||
if !t.initTicker {
|
||||
panic("time: Reset called on uninitialized Ticker")
|
||||
}
|
||||
resetTimer((*Timer)(unsafe.Pointer(t)), when(d), int64(d))
|
||||
}
|
||||
|
||||
// Tick is a convenience wrapper for [NewTicker] providing access to the ticking
|
||||
// channel only. Unlike NewTicker, Tick will return nil if d <= 0.
|
||||
//
|
||||
// Before Go 1.23, this documentation warned that the underlying
|
||||
// [Ticker] would never be recovered by the garbage collector, and that
|
||||
// if efficiency was a concern, code should use NewTicker instead and
|
||||
// call [Ticker.Stop] when the ticker is no longer needed.
|
||||
// As of Go 1.23, the garbage collector can recover unreferenced
|
||||
// tickers, even if they haven't been stopped.
|
||||
// The Stop method is no longer necessary to help the garbage collector.
|
||||
// There is no longer any reason to prefer NewTicker when Tick will do.
|
||||
func Tick(d Duration) <-chan Time {
|
||||
if d <= 0 {
|
||||
return nil
|
||||
}
|
||||
return NewTicker(d).C
|
||||
}
|
||||
694
src/time/tick_test.go
Normal file
694
src/time/tick_test.go
Normal file
@@ -0,0 +1,694 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package time_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"runtime"
|
||||
"sync"
|
||||
"testing"
|
||||
. "time"
|
||||
)
|
||||
|
||||
func TestTicker(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// We want to test that a ticker takes as much time as expected.
|
||||
// Since we don't want the test to run for too long, we don't
|
||||
// want to use lengthy times. This makes the test inherently flaky.
|
||||
// Start with a short time, but try again with a long one if the
|
||||
// first test fails.
|
||||
|
||||
baseCount := 10
|
||||
baseDelta := 20 * Millisecond
|
||||
|
||||
// On Darwin ARM64 the tick frequency seems limited. Issue 35692.
|
||||
if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && runtime.GOARCH == "arm64" {
|
||||
// The following test will run ticker count/2 times then reset
|
||||
// the ticker to double the duration for the rest of count/2.
|
||||
// Since tick frequency is limited on Darwin ARM64, use even
|
||||
// number to give the ticks more time to let the test pass.
|
||||
// See CL 220638.
|
||||
baseCount = 6
|
||||
baseDelta = 100 * Millisecond
|
||||
}
|
||||
|
||||
var errs []string
|
||||
logErrs := func() {
|
||||
for _, e := range errs {
|
||||
t.Log(e)
|
||||
}
|
||||
}
|
||||
|
||||
for _, test := range []struct {
|
||||
count int
|
||||
delta Duration
|
||||
}{{
|
||||
count: baseCount,
|
||||
delta: baseDelta,
|
||||
}, {
|
||||
count: 8,
|
||||
delta: 1 * Second,
|
||||
}} {
|
||||
count, delta := test.count, test.delta
|
||||
ticker := NewTicker(delta)
|
||||
t0 := Now()
|
||||
for range count / 2 {
|
||||
<-ticker.C
|
||||
}
|
||||
ticker.Reset(delta * 2)
|
||||
for range count - count/2 {
|
||||
<-ticker.C
|
||||
}
|
||||
ticker.Stop()
|
||||
t1 := Now()
|
||||
dt := t1.Sub(t0)
|
||||
target := 3 * delta * Duration(count/2)
|
||||
slop := target * 3 / 10
|
||||
if dt < target-slop || dt > target+slop {
|
||||
errs = append(errs, fmt.Sprintf("%d %s ticks then %d %s ticks took %s, expected [%s,%s]", count/2, delta, count/2, delta*2, dt, target-slop, target+slop))
|
||||
if dt > target+slop {
|
||||
// System may be overloaded; sleep a bit
|
||||
// in the hopes it will recover.
|
||||
Sleep(Second / 2)
|
||||
}
|
||||
continue
|
||||
}
|
||||
// Now test that the ticker stopped.
|
||||
Sleep(2 * delta)
|
||||
select {
|
||||
case <-ticker.C:
|
||||
errs = append(errs, "Ticker did not shut down")
|
||||
continue
|
||||
default:
|
||||
// ok
|
||||
}
|
||||
|
||||
// Test passed, so all done.
|
||||
if len(errs) > 0 {
|
||||
t.Logf("saw %d errors, ignoring to avoid flakiness", len(errs))
|
||||
logErrs()
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
t.Errorf("saw %d errors", len(errs))
|
||||
logErrs()
|
||||
}
|
||||
|
||||
// Issue 21874
|
||||
func TestTickerStopWithDirectInitialization(t *testing.T) {
|
||||
c := make(chan Time)
|
||||
tk := &Ticker{C: c}
|
||||
tk.Stop()
|
||||
}
|
||||
|
||||
// Test that a bug tearing down a ticker has been fixed. This routine should not deadlock.
|
||||
func TestTeardown(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
Delta := 100 * Millisecond
|
||||
if testing.Short() {
|
||||
Delta = 20 * Millisecond
|
||||
}
|
||||
for range 3 {
|
||||
ticker := NewTicker(Delta)
|
||||
<-ticker.C
|
||||
ticker.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
// Test the Tick convenience wrapper.
|
||||
func TestTick(t *testing.T) {
|
||||
// Test that giving a negative duration returns nil.
|
||||
if got := Tick(-1); got != nil {
|
||||
t.Errorf("Tick(-1) = %v; want nil", got)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that NewTicker panics when given a duration less than zero.
|
||||
func TestNewTickerLtZeroDuration(t *testing.T) {
|
||||
defer func() {
|
||||
if err := recover(); err == nil {
|
||||
t.Errorf("NewTicker(-1) should have panicked")
|
||||
}
|
||||
}()
|
||||
NewTicker(-1)
|
||||
}
|
||||
|
||||
// Test that Ticker.Reset panics when given a duration less than zero.
|
||||
func TestTickerResetLtZeroDuration(t *testing.T) {
|
||||
defer func() {
|
||||
if err := recover(); err == nil {
|
||||
t.Errorf("Ticker.Reset(0) should have panicked")
|
||||
}
|
||||
}()
|
||||
tk := NewTicker(Second)
|
||||
tk.Reset(0)
|
||||
}
|
||||
|
||||
func TestLongAdjustTimers(t *testing.T) {
|
||||
if runtime.GOOS == "android" || runtime.GOOS == "ios" {
|
||||
t.Skipf("skipping on %s - too slow", runtime.GOOS)
|
||||
}
|
||||
t.Parallel()
|
||||
var wg sync.WaitGroup
|
||||
defer wg.Wait()
|
||||
|
||||
// Build up the timer heap.
|
||||
const count = 5000
|
||||
wg.Add(count)
|
||||
for range count {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
Sleep(10 * Microsecond)
|
||||
}()
|
||||
}
|
||||
for range count {
|
||||
Sleep(1 * Microsecond)
|
||||
}
|
||||
|
||||
// Give ourselves 60 seconds to complete.
|
||||
// This used to reliably fail on a Mac M3 laptop,
|
||||
// which needed 77 seconds.
|
||||
// Trybots are slower, so it will fail even more reliably there.
|
||||
// With the fix, the code runs in under a second.
|
||||
done := make(chan bool)
|
||||
AfterFunc(60*Second, func() { close(done) })
|
||||
|
||||
// Set up a queuing goroutine to ping pong through the scheduler.
|
||||
inQ := make(chan func())
|
||||
outQ := make(chan func())
|
||||
|
||||
defer close(inQ)
|
||||
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer close(outQ)
|
||||
var q []func()
|
||||
for {
|
||||
var sendTo chan func()
|
||||
var send func()
|
||||
if len(q) > 0 {
|
||||
sendTo = outQ
|
||||
send = q[0]
|
||||
}
|
||||
select {
|
||||
case sendTo <- send:
|
||||
q = q[1:]
|
||||
case f, ok := <-inQ:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
q = append(q, f)
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for i := range 50000 {
|
||||
const try = 20
|
||||
for range try {
|
||||
inQ <- func() {}
|
||||
}
|
||||
for range try {
|
||||
select {
|
||||
case _, ok := <-outQ:
|
||||
if !ok {
|
||||
t.Fatal("output channel is closed")
|
||||
}
|
||||
case <-After(5 * Second):
|
||||
t.Fatalf("failed to read work, iteration %d", i)
|
||||
case <-done:
|
||||
t.Fatal("timer expired")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
func BenchmarkTicker(b *testing.B) {
|
||||
benchmark(b, func(pb *testing.PB) {
|
||||
ticker := NewTicker(Nanosecond)
|
||||
for pb.Next() {
|
||||
<-ticker.C
|
||||
}
|
||||
ticker.Stop()
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkTickerReset(b *testing.B) {
|
||||
benchmark(b, func(pb *testing.PB) {
|
||||
ticker := NewTicker(Nanosecond)
|
||||
for pb.Next() {
|
||||
ticker.Reset(Nanosecond * 2)
|
||||
}
|
||||
ticker.Stop()
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkTickerResetNaive(b *testing.B) {
|
||||
benchmark(b, func(pb *testing.PB) {
|
||||
ticker := NewTicker(Nanosecond)
|
||||
for pb.Next() {
|
||||
ticker.Stop()
|
||||
ticker = NewTicker(Nanosecond * 2)
|
||||
}
|
||||
ticker.Stop()
|
||||
})
|
||||
}
|
||||
|
||||
func TestTimerGC(t *testing.T) {
|
||||
run := func(t *testing.T, what string, f func()) {
|
||||
t.Helper()
|
||||
t.Run(what, func(t *testing.T) {
|
||||
t.Helper()
|
||||
const N = 1e4
|
||||
var stats runtime.MemStats
|
||||
runtime.GC()
|
||||
runtime.GC()
|
||||
runtime.GC()
|
||||
runtime.ReadMemStats(&stats)
|
||||
before := int64(stats.Mallocs - stats.Frees)
|
||||
|
||||
for j := 0; j < N; j++ {
|
||||
f()
|
||||
}
|
||||
|
||||
runtime.GC()
|
||||
runtime.GC()
|
||||
runtime.GC()
|
||||
runtime.ReadMemStats(&stats)
|
||||
after := int64(stats.Mallocs - stats.Frees)
|
||||
|
||||
// Allow some slack, but inuse >= N means at least 1 allocation per iteration.
|
||||
inuse := after - before
|
||||
if inuse >= N {
|
||||
t.Errorf("%s did not get GC'ed: %d allocations", what, inuse)
|
||||
|
||||
Sleep(1 * Second)
|
||||
runtime.ReadMemStats(&stats)
|
||||
after := int64(stats.Mallocs - stats.Frees)
|
||||
inuse = after - before
|
||||
t.Errorf("after a sleep: %d allocations", inuse)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
run(t, "After", func() { After(Hour) })
|
||||
run(t, "Tick", func() { Tick(Hour) })
|
||||
run(t, "NewTimer", func() { NewTimer(Hour) })
|
||||
run(t, "NewTicker", func() { NewTicker(Hour) })
|
||||
run(t, "NewTimerStop", func() { NewTimer(Hour).Stop() })
|
||||
run(t, "NewTickerStop", func() { NewTicker(Hour).Stop() })
|
||||
}
|
||||
|
||||
func TestChan(t *testing.T) {
|
||||
for _, name := range []string{"0", "1", "2"} {
|
||||
t.Run("asynctimerchan="+name, func(t *testing.T) {
|
||||
t.Setenv("GODEBUG", "asynctimerchan="+name)
|
||||
t.Run("Timer", func(t *testing.T) {
|
||||
tim := NewTimer(10000 * Second)
|
||||
testTimerChan(t, tim, tim.C, name == "0")
|
||||
})
|
||||
t.Run("Ticker", func(t *testing.T) {
|
||||
tim := &tickerTimer{Ticker: NewTicker(10000 * Second)}
|
||||
testTimerChan(t, tim, tim.C, name == "0")
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type timer interface {
|
||||
Stop() bool
|
||||
Reset(Duration) bool
|
||||
}
|
||||
|
||||
// tickerTimer is a Timer with Reset and Stop methods that return bools,
|
||||
// to have the same signatures as Timer.
|
||||
type tickerTimer struct {
|
||||
*Ticker
|
||||
stopped bool
|
||||
}
|
||||
|
||||
func (t *tickerTimer) Stop() bool {
|
||||
pending := !t.stopped
|
||||
t.stopped = true
|
||||
t.Ticker.Stop()
|
||||
return pending
|
||||
}
|
||||
|
||||
func (t *tickerTimer) Reset(d Duration) bool {
|
||||
pending := !t.stopped
|
||||
t.stopped = false
|
||||
t.Ticker.Reset(d)
|
||||
return pending
|
||||
}
|
||||
|
||||
func testTimerChan(t *testing.T, tim timer, C <-chan Time, synctimerchan bool) {
|
||||
_, isTimer := tim.(*Timer)
|
||||
isTicker := !isTimer
|
||||
|
||||
// Retry parameters. Enough to deflake even on slow machines.
|
||||
// Windows in particular has very coarse timers so we have to
|
||||
// wait 10ms just to make a timer go off.
|
||||
const (
|
||||
sched = 10 * Millisecond
|
||||
tries = 100
|
||||
drainTries = 5
|
||||
)
|
||||
|
||||
// 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:
|
||||
return
|
||||
default:
|
||||
}
|
||||
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 {
|
||||
default:
|
||||
case <-C:
|
||||
t.Errorf("extra tick")
|
||||
}
|
||||
}
|
||||
assertTick := func() {
|
||||
t.Helper()
|
||||
select {
|
||||
default:
|
||||
case <-C:
|
||||
return
|
||||
}
|
||||
for range tries {
|
||||
Sleep(sched)
|
||||
select {
|
||||
default:
|
||||
case <-C:
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Errorf("missing tick")
|
||||
}
|
||||
assertLen := func() {
|
||||
t.Helper()
|
||||
if synctimerchan {
|
||||
if n := len(C); n != 0 {
|
||||
t.Errorf("synctimer has len(C) = %d, want 0 (always)", n)
|
||||
}
|
||||
return
|
||||
}
|
||||
var n int
|
||||
if n = len(C); n == 1 {
|
||||
return
|
||||
}
|
||||
for range tries {
|
||||
Sleep(sched)
|
||||
if n = len(C); n == 1 {
|
||||
return
|
||||
}
|
||||
}
|
||||
t.Errorf("len(C) = %d, want 1", n)
|
||||
}
|
||||
|
||||
// Test simple stop; timer never in heap.
|
||||
tim.Stop()
|
||||
noTick()
|
||||
|
||||
// Test modify of timer not in heap.
|
||||
tim.Reset(10000 * Second)
|
||||
noTick()
|
||||
|
||||
if synctimerchan {
|
||||
// Test modify of timer in heap.
|
||||
tim.Reset(1)
|
||||
Sleep(sched)
|
||||
if l, c := len(C), cap(C); l != 0 || c != 0 {
|
||||
//t.Fatalf("len(C), cap(C) = %d, %d, want 0, 0", l, c)
|
||||
}
|
||||
assertTick()
|
||||
} else {
|
||||
// Test modify of timer in heap.
|
||||
tim.Reset(1)
|
||||
assertTick()
|
||||
Sleep(sched)
|
||||
tim.Reset(10000 * Second)
|
||||
drainAsync()
|
||||
noTick()
|
||||
|
||||
// Test that len sees an immediate tick arrive
|
||||
// for Reset of timer in heap.
|
||||
tim.Reset(1)
|
||||
assertLen()
|
||||
assertTick()
|
||||
|
||||
// Test that len sees an immediate tick arrive
|
||||
// for Reset of timer NOT in heap.
|
||||
tim.Stop()
|
||||
drainAsync()
|
||||
tim.Reset(1)
|
||||
assertLen()
|
||||
assertTick()
|
||||
}
|
||||
|
||||
// Sleep long enough that a second tick must happen if this is a ticker.
|
||||
// Test that Reset does not lose the tick that should have happened.
|
||||
Sleep(sched)
|
||||
tim.Reset(10000 * Second)
|
||||
drainAsync()
|
||||
noTick()
|
||||
|
||||
notDone := func(done chan bool) {
|
||||
t.Helper()
|
||||
select {
|
||||
default:
|
||||
case <-done:
|
||||
t.Fatalf("early done")
|
||||
}
|
||||
}
|
||||
|
||||
waitDone := func(done chan bool) {
|
||||
t.Helper()
|
||||
for range tries {
|
||||
Sleep(sched)
|
||||
select {
|
||||
case <-done:
|
||||
return
|
||||
default:
|
||||
}
|
||||
}
|
||||
t.Fatalf("never got done")
|
||||
}
|
||||
|
||||
// Reset timer in heap (already reset above, but just in case).
|
||||
tim.Reset(10000 * Second)
|
||||
drainAsync()
|
||||
|
||||
// Test stop while timer in heap (because goroutine is blocked on <-C).
|
||||
done := make(chan bool)
|
||||
notDone(done)
|
||||
go func() {
|
||||
<-C
|
||||
close(done)
|
||||
}()
|
||||
Sleep(sched)
|
||||
notDone(done)
|
||||
|
||||
// Test reset far away while timer in heap.
|
||||
tim.Reset(20000 * Second)
|
||||
Sleep(sched)
|
||||
notDone(done)
|
||||
|
||||
// Test imminent reset while in heap.
|
||||
tim.Reset(1)
|
||||
waitDone(done)
|
||||
|
||||
// If this is a ticker, another tick should have come in already
|
||||
// (they are 1ns apart). If a timer, it should have stopped.
|
||||
if isTicker {
|
||||
assertTick()
|
||||
} else {
|
||||
noTick()
|
||||
}
|
||||
|
||||
tim.Stop()
|
||||
drainAsync()
|
||||
noTick()
|
||||
|
||||
// Again using select and with two goroutines waiting.
|
||||
tim.Reset(10000 * Second)
|
||||
drainAsync()
|
||||
done = make(chan bool, 2)
|
||||
done1 := make(chan bool)
|
||||
done2 := make(chan bool)
|
||||
stop := make(chan bool)
|
||||
go func() {
|
||||
select {
|
||||
case <-C:
|
||||
done <- true
|
||||
case <-stop:
|
||||
}
|
||||
close(done1)
|
||||
}()
|
||||
go func() {
|
||||
select {
|
||||
case <-C:
|
||||
done <- true
|
||||
case <-stop:
|
||||
}
|
||||
close(done2)
|
||||
}()
|
||||
Sleep(sched)
|
||||
notDone(done)
|
||||
tim.Reset(sched / 2)
|
||||
Sleep(sched)
|
||||
waitDone(done)
|
||||
tim.Stop()
|
||||
close(stop)
|
||||
waitDone(done1)
|
||||
waitDone(done2)
|
||||
if isTicker {
|
||||
// extra send might have sent done again
|
||||
// (handled by buffering done above).
|
||||
select {
|
||||
default:
|
||||
case <-done:
|
||||
}
|
||||
// extra send after that might have filled C.
|
||||
select {
|
||||
default:
|
||||
case <-C:
|
||||
}
|
||||
}
|
||||
notDone(done)
|
||||
|
||||
// Test enqueueTimerChan when timer is stopped.
|
||||
stop = make(chan bool)
|
||||
done = make(chan bool, 2)
|
||||
for range 2 {
|
||||
go func() {
|
||||
select {
|
||||
case <-C:
|
||||
panic("unexpected data")
|
||||
case <-stop:
|
||||
}
|
||||
done <- true
|
||||
}()
|
||||
}
|
||||
Sleep(sched)
|
||||
close(stop)
|
||||
waitDone(done)
|
||||
waitDone(done)
|
||||
|
||||
// Test that Stop and Reset block old values from being received.
|
||||
// (Proposal go.dev/issue/37196.)
|
||||
if synctimerchan {
|
||||
tim.Reset(1)
|
||||
Sleep(10 * Millisecond)
|
||||
if pending := tim.Stop(); pending != true {
|
||||
t.Errorf("tim.Stop() = %v, want true", pending)
|
||||
}
|
||||
noTick()
|
||||
|
||||
tim.Reset(Hour)
|
||||
noTick()
|
||||
if pending := tim.Reset(1); pending != true {
|
||||
t.Errorf("tim.Stop() = %v, want true", pending)
|
||||
}
|
||||
assertTick()
|
||||
Sleep(10 * Millisecond)
|
||||
if isTicker {
|
||||
assertTick()
|
||||
Sleep(10 * Millisecond)
|
||||
} else {
|
||||
noTick()
|
||||
}
|
||||
if pending, want := tim.Reset(Hour), isTicker; pending != want {
|
||||
t.Errorf("tim.Stop() = %v, want %v", pending, want)
|
||||
}
|
||||
noTick()
|
||||
}
|
||||
}
|
||||
|
||||
func TestManualTicker(t *testing.T) {
|
||||
// Code should not do this, but some old code dating to Go 1.9 does.
|
||||
// Make sure this doesn't crash.
|
||||
// See go.dev/issue/21874.
|
||||
c := make(chan Time)
|
||||
tick := &Ticker{C: c}
|
||||
tick.Stop()
|
||||
}
|
||||
|
||||
func TestAfterTimes(t *testing.T) {
|
||||
t.Parallel()
|
||||
// Using After(10ms) but waiting for 500ms to read the channel
|
||||
// should produce a time from start+10ms, not start+500ms.
|
||||
// Make sure it does.
|
||||
// To avoid flakes due to very long scheduling delays,
|
||||
// require 10 failures in a row before deciding something is wrong.
|
||||
for range 10 {
|
||||
start := Now()
|
||||
c := After(10 * Millisecond)
|
||||
Sleep(500 * Millisecond)
|
||||
dt := (<-c).Sub(start)
|
||||
if dt < 400*Millisecond {
|
||||
return
|
||||
}
|
||||
t.Logf("After(10ms) time is +%v, want <400ms", dt)
|
||||
}
|
||||
t.Errorf("not working")
|
||||
}
|
||||
|
||||
func TestTickTimes(t *testing.T) {
|
||||
t.Parallel()
|
||||
// See comment in TestAfterTimes
|
||||
for range 10 {
|
||||
start := Now()
|
||||
c := Tick(10 * Millisecond)
|
||||
Sleep(500 * Millisecond)
|
||||
dt := (<-c).Sub(start)
|
||||
if dt < 400*Millisecond {
|
||||
return
|
||||
}
|
||||
t.Logf("Tick(10ms) time is +%v, want <400ms", dt)
|
||||
}
|
||||
t.Errorf("not working")
|
||||
}
|
||||
1695
src/time/time.go
Normal file
1695
src/time/time.go
Normal file
File diff suppressed because it is too large
Load Diff
1920
src/time/time_test.go
Normal file
1920
src/time/time_test.go
Normal file
File diff suppressed because it is too large
Load Diff
110
src/time/tzdata/tzdata.go
Normal file
110
src/time/tzdata/tzdata.go
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package tzdata provides an embedded copy of the timezone database.
|
||||
// If this package is imported anywhere in the program, then if
|
||||
// the time package cannot find tzdata files on the system,
|
||||
// it will use this embedded information.
|
||||
//
|
||||
// Importing this package will increase the size of a program by about
|
||||
// 450 KB.
|
||||
//
|
||||
// This package should normally be imported by a program's main package,
|
||||
// not by a library. Libraries normally shouldn't decide whether to
|
||||
// include the timezone database in a program.
|
||||
//
|
||||
// This package will be automatically imported if you build with
|
||||
// -tags timetzdata.
|
||||
package tzdata
|
||||
|
||||
// The test for this package is time/tzdata_test.go.
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
_ "unsafe" // for go:linkname
|
||||
)
|
||||
|
||||
// registerLoadFromEmbeddedTZData is defined in package time.
|
||||
//
|
||||
//go:linkname registerLoadFromEmbeddedTZData time.registerLoadFromEmbeddedTZData
|
||||
func registerLoadFromEmbeddedTZData(func(string) (string, error))
|
||||
|
||||
func init() {
|
||||
registerLoadFromEmbeddedTZData(loadFromEmbeddedTZData)
|
||||
}
|
||||
|
||||
// get4s returns the little-endian 32-bit value at the start of s.
|
||||
func get4s(s string) int {
|
||||
if len(s) < 4 {
|
||||
return 0
|
||||
}
|
||||
return int(s[0]) | int(s[1])<<8 | int(s[2])<<16 | int(s[3])<<24
|
||||
}
|
||||
|
||||
// get2s returns the little-endian 16-bit value at the start of s.
|
||||
func get2s(s string) int {
|
||||
if len(s) < 2 {
|
||||
return 0
|
||||
}
|
||||
return int(s[0]) | int(s[1])<<8
|
||||
}
|
||||
|
||||
// loadFromEmbeddedTZData returns the contents of the file with the given
|
||||
// name in an uncompressed zip file, where the contents of the file can
|
||||
// be found in embeddedTzdata.
|
||||
// This is similar to time.loadTzinfoFromZip.
|
||||
func loadFromEmbeddedTZData(name string) (string, error) {
|
||||
const (
|
||||
zecheader = 0x06054b50
|
||||
zcheader = 0x02014b50
|
||||
ztailsize = 22
|
||||
|
||||
zheadersize = 30
|
||||
zheader = 0x04034b50
|
||||
)
|
||||
|
||||
// zipdata is provided by zzipdata.go,
|
||||
// which is generated by cmd/dist during make.bash.
|
||||
z := zipdata
|
||||
|
||||
idx := len(z) - ztailsize
|
||||
n := get2s(z[idx+10:])
|
||||
idx = get4s(z[idx+16:])
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
// See time.loadTzinfoFromZip for zip entry layout.
|
||||
if get4s(z[idx:]) != zcheader {
|
||||
break
|
||||
}
|
||||
meth := get2s(z[idx+10:])
|
||||
size := get4s(z[idx+24:])
|
||||
namelen := get2s(z[idx+28:])
|
||||
xlen := get2s(z[idx+30:])
|
||||
fclen := get2s(z[idx+32:])
|
||||
off := get4s(z[idx+42:])
|
||||
zname := z[idx+46 : idx+46+namelen]
|
||||
idx += 46 + namelen + xlen + fclen
|
||||
if zname != name {
|
||||
continue
|
||||
}
|
||||
if meth != 0 {
|
||||
return "", errors.New("unsupported compression for " + name + " in embedded tzdata")
|
||||
}
|
||||
|
||||
// See time.loadTzinfoFromZip for zip per-file header layout.
|
||||
idx = off
|
||||
if get4s(z[idx:]) != zheader ||
|
||||
get2s(z[idx+8:]) != meth ||
|
||||
get2s(z[idx+26:]) != namelen ||
|
||||
z[idx+30:idx+30+namelen] != name {
|
||||
return "", errors.New("corrupt embedded tzdata")
|
||||
}
|
||||
xlen = get2s(z[idx+28:])
|
||||
idx += 30 + namelen + xlen
|
||||
return z[idx : idx+size], nil
|
||||
}
|
||||
|
||||
return "", syscall.ENOENT
|
||||
}
|
||||
99
src/time/tzdata_test.go
Normal file
99
src/time/tzdata_test.go
Normal file
@@ -0,0 +1,99 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package time_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
_ "time/tzdata"
|
||||
)
|
||||
|
||||
var zones = []string{
|
||||
"Asia/Jerusalem",
|
||||
"America/Los_Angeles",
|
||||
}
|
||||
|
||||
func TestEmbeddedTZData(t *testing.T) {
|
||||
undo := time.DisablePlatformSources()
|
||||
defer undo()
|
||||
|
||||
for _, zone := range zones {
|
||||
ref, err := time.LoadLocation(zone)
|
||||
if err != nil {
|
||||
t.Errorf("LoadLocation(%q): %v", zone, err)
|
||||
continue
|
||||
}
|
||||
|
||||
embedded, err := time.LoadFromEmbeddedTZData(zone)
|
||||
if err != nil {
|
||||
t.Errorf("LoadFromEmbeddedTZData(%q): %v", zone, err)
|
||||
continue
|
||||
}
|
||||
sample, err := time.LoadLocationFromTZData(zone, []byte(embedded))
|
||||
if err != nil {
|
||||
t.Errorf("LoadLocationFromTZData failed for %q: %v", zone, err)
|
||||
continue
|
||||
}
|
||||
|
||||
// Compare the name and zone fields of ref and sample.
|
||||
// The tx field changes faster as tzdata is updated.
|
||||
// The cache fields are expected to differ.
|
||||
v1 := reflect.ValueOf(ref).Elem()
|
||||
v2 := reflect.ValueOf(sample).Elem()
|
||||
typ := v1.Type()
|
||||
nf := typ.NumField()
|
||||
found := 0
|
||||
for i := 0; i < nf; i++ {
|
||||
ft := typ.Field(i)
|
||||
if ft.Name != "name" && ft.Name != "zone" {
|
||||
continue
|
||||
}
|
||||
found++
|
||||
if !equal(t, v1.Field(i), v2.Field(i)) {
|
||||
t.Errorf("zone %s: system and embedded tzdata field %s differs", zone, ft.Name)
|
||||
}
|
||||
}
|
||||
if found != 2 {
|
||||
t.Errorf("test must be updated for change to time.Location struct")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// equal is a small version of reflect.DeepEqual that we use to
|
||||
// compare the values of zoneinfo unexported fields.
|
||||
func equal(t *testing.T, f1, f2 reflect.Value) bool {
|
||||
switch f1.Type().Kind() {
|
||||
case reflect.Slice:
|
||||
if f1.Len() != f2.Len() {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < f1.Len(); i++ {
|
||||
if !equal(t, f1.Index(i), f2.Index(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.Struct:
|
||||
nf := f1.Type().NumField()
|
||||
for i := 0; i < nf; i++ {
|
||||
if !equal(t, f1.Field(i), f2.Field(i)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case reflect.String:
|
||||
return f1.String() == f2.String()
|
||||
case reflect.Bool:
|
||||
return f1.Bool() == f2.Bool()
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
return f1.Int() == f2.Int()
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
return f1.Uint() == f2.Uint()
|
||||
default:
|
||||
t.Errorf("test internal error: unsupported kind %v", f1.Type().Kind())
|
||||
return true
|
||||
}
|
||||
}
|
||||
712
src/time/zoneinfo.go
Normal file
712
src/time/zoneinfo.go
Normal file
@@ -0,0 +1,712 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package time
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
//go:generate env ZONEINFO=$GOROOT/lib/time/zoneinfo.zip go run genzabbrs.go -output zoneinfo_abbrs_windows.go
|
||||
|
||||
// A Location maps time instants to the zone in use at that time.
|
||||
// Typically, the Location represents the collection of time offsets
|
||||
// in use in a geographical area. For many Locations the time offset varies
|
||||
// depending on whether daylight savings time is in use at the time instant.
|
||||
//
|
||||
// Location is used to provide a time zone in a printed Time value and for
|
||||
// calculations involving intervals that may cross daylight savings time
|
||||
// boundaries.
|
||||
type Location struct {
|
||||
name string
|
||||
zone []zone
|
||||
tx []zoneTrans
|
||||
|
||||
// The tzdata information can be followed by a string that describes
|
||||
// how to handle DST transitions not recorded in zoneTrans.
|
||||
// The format is the TZ environment variable without a colon; see
|
||||
// https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap08.html.
|
||||
// Example string, for America/Los_Angeles: PST8PDT,M3.2.0,M11.1.0
|
||||
extend string
|
||||
|
||||
// Most lookups will be for the current time.
|
||||
// To avoid the binary search through tx, keep a
|
||||
// static one-element cache that gives the correct
|
||||
// zone for the time when the Location was created.
|
||||
// if cacheStart <= t < cacheEnd,
|
||||
// lookup can return cacheZone.
|
||||
// The units for cacheStart and cacheEnd are seconds
|
||||
// since January 1, 1970 UTC, to match the argument
|
||||
// to lookup.
|
||||
cacheStart int64
|
||||
cacheEnd int64
|
||||
cacheZone *zone
|
||||
}
|
||||
|
||||
// A zone represents a single time zone such as CET.
|
||||
type zone struct {
|
||||
name string // abbreviated name, "CET"
|
||||
offset int // seconds east of UTC
|
||||
isDST bool // is this zone Daylight Savings Time?
|
||||
}
|
||||
|
||||
// A zoneTrans represents a single time zone transition.
|
||||
type zoneTrans struct {
|
||||
when int64 // transition time, in seconds since 1970 GMT
|
||||
index uint8 // the index of the zone that goes into effect at that time
|
||||
isstd, isutc bool // ignored - no idea what these mean
|
||||
}
|
||||
|
||||
// alpha and omega are the beginning and end of time for zone
|
||||
// transitions.
|
||||
const (
|
||||
alpha = -1 << 63 // math.MinInt64
|
||||
omega = 1<<63 - 1 // math.MaxInt64
|
||||
)
|
||||
|
||||
// UTC represents Universal Coordinated Time (UTC).
|
||||
var UTC *Location = &utcLoc
|
||||
|
||||
// utcLoc is separate so that get can refer to &utcLoc
|
||||
// and ensure that it never returns a nil *Location,
|
||||
// even if a badly behaved client has changed UTC.
|
||||
var utcLoc = Location{name: "UTC"}
|
||||
|
||||
// Local represents the system's local time zone.
|
||||
// On Unix systems, Local consults the TZ environment
|
||||
// variable to find the time zone to use. No TZ means
|
||||
// use the system default /etc/localtime.
|
||||
// TZ="" means use UTC.
|
||||
// TZ="foo" means use file foo in the system timezone directory.
|
||||
var Local *Location = &localLoc
|
||||
|
||||
// localLoc is separate so that initLocal can initialize
|
||||
// it even if a client has changed Local.
|
||||
var localLoc Location
|
||||
var localOnce sync.Once
|
||||
|
||||
func (l *Location) get() *Location {
|
||||
if l == nil {
|
||||
return &utcLoc
|
||||
}
|
||||
if l == &localLoc {
|
||||
localOnce.Do(initLocal)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
// String returns a descriptive name for the time zone information,
|
||||
// corresponding to the name argument to [LoadLocation] or [FixedZone].
|
||||
func (l *Location) String() string {
|
||||
return l.get().name
|
||||
}
|
||||
|
||||
var unnamedFixedZones []*Location
|
||||
var unnamedFixedZonesOnce sync.Once
|
||||
|
||||
// FixedZone returns a [Location] that always uses
|
||||
// the given zone name and offset (seconds east of UTC).
|
||||
func FixedZone(name string, offset int) *Location {
|
||||
// Most calls to FixedZone have an unnamed zone with an offset by the hour.
|
||||
// Optimize for that case by returning the same *Location for a given hour.
|
||||
const hoursBeforeUTC = 12
|
||||
const hoursAfterUTC = 14
|
||||
hour := offset / 60 / 60
|
||||
if name == "" && -hoursBeforeUTC <= hour && hour <= +hoursAfterUTC && hour*60*60 == offset {
|
||||
unnamedFixedZonesOnce.Do(func() {
|
||||
unnamedFixedZones = make([]*Location, hoursBeforeUTC+1+hoursAfterUTC)
|
||||
for hr := -hoursBeforeUTC; hr <= +hoursAfterUTC; hr++ {
|
||||
unnamedFixedZones[hr+hoursBeforeUTC] = fixedZone("", hr*60*60)
|
||||
}
|
||||
})
|
||||
return unnamedFixedZones[hour+hoursBeforeUTC]
|
||||
}
|
||||
return fixedZone(name, offset)
|
||||
}
|
||||
|
||||
func fixedZone(name string, offset int) *Location {
|
||||
l := &Location{
|
||||
name: name,
|
||||
zone: []zone{{name, offset, false}},
|
||||
tx: []zoneTrans{{alpha, 0, false, false}},
|
||||
cacheStart: alpha,
|
||||
cacheEnd: omega,
|
||||
}
|
||||
l.cacheZone = &l.zone[0]
|
||||
return l
|
||||
}
|
||||
|
||||
// lookup returns information about the time zone in use at an
|
||||
// instant in time expressed as seconds since January 1, 1970 00:00:00 UTC.
|
||||
//
|
||||
// The returned information gives the name of the zone (such as "CET"),
|
||||
// the start and end times bracketing sec when that zone is in effect,
|
||||
// the offset in seconds east of UTC (such as -5*60*60), and whether
|
||||
// the daylight savings is being observed at that time.
|
||||
func (l *Location) lookup(sec int64) (name string, offset int, start, end int64, isDST bool) {
|
||||
l = l.get()
|
||||
|
||||
if len(l.zone) == 0 {
|
||||
name = "UTC"
|
||||
offset = 0
|
||||
start = alpha
|
||||
end = omega
|
||||
isDST = false
|
||||
return
|
||||
}
|
||||
|
||||
if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
|
||||
name = zone.name
|
||||
offset = zone.offset
|
||||
start = l.cacheStart
|
||||
end = l.cacheEnd
|
||||
isDST = zone.isDST
|
||||
return
|
||||
}
|
||||
|
||||
if len(l.tx) == 0 || sec < l.tx[0].when {
|
||||
zone := &l.zone[l.lookupFirstZone()]
|
||||
name = zone.name
|
||||
offset = zone.offset
|
||||
start = alpha
|
||||
if len(l.tx) > 0 {
|
||||
end = l.tx[0].when
|
||||
} else {
|
||||
end = omega
|
||||
}
|
||||
isDST = zone.isDST
|
||||
return
|
||||
}
|
||||
|
||||
// Binary search for entry with largest time <= sec.
|
||||
// Not using sort.Search to avoid dependencies.
|
||||
tx := l.tx
|
||||
end = omega
|
||||
lo := 0
|
||||
hi := len(tx)
|
||||
for hi-lo > 1 {
|
||||
m := int(uint(lo+hi) >> 1)
|
||||
lim := tx[m].when
|
||||
if sec < lim {
|
||||
end = lim
|
||||
hi = m
|
||||
} else {
|
||||
lo = m
|
||||
}
|
||||
}
|
||||
zone := &l.zone[tx[lo].index]
|
||||
name = zone.name
|
||||
offset = zone.offset
|
||||
start = tx[lo].when
|
||||
// end = maintained during the search
|
||||
isDST = zone.isDST
|
||||
|
||||
// If we're at the end of the known zone transitions,
|
||||
// try the extend string.
|
||||
if lo == len(tx)-1 && l.extend != "" {
|
||||
if ename, eoffset, estart, eend, eisDST, ok := tzset(l.extend, start, sec); ok {
|
||||
return ename, eoffset, estart, eend, eisDST
|
||||
}
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
// lookupFirstZone returns the index of the time zone to use for times
|
||||
// before the first transition time, or when there are no transition
|
||||
// times.
|
||||
//
|
||||
// The reference implementation in localtime.c from
|
||||
// https://www.iana.org/time-zones/repository/releases/tzcode2013g.tar.gz
|
||||
// implements the following algorithm for these cases:
|
||||
// 1. If the first zone is unused by the transitions, use it.
|
||||
// 2. Otherwise, if there are transition times, and the first
|
||||
// transition is to a zone in daylight time, find the first
|
||||
// non-daylight-time zone before and closest to the first transition
|
||||
// zone.
|
||||
// 3. Otherwise, use the first zone that is not daylight time, if
|
||||
// there is one.
|
||||
// 4. Otherwise, use the first zone.
|
||||
func (l *Location) lookupFirstZone() int {
|
||||
// Case 1.
|
||||
if !l.firstZoneUsed() {
|
||||
return 0
|
||||
}
|
||||
|
||||
// Case 2.
|
||||
if len(l.tx) > 0 && l.zone[l.tx[0].index].isDST {
|
||||
for zi := int(l.tx[0].index) - 1; zi >= 0; zi-- {
|
||||
if !l.zone[zi].isDST {
|
||||
return zi
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Case 3.
|
||||
for zi := range l.zone {
|
||||
if !l.zone[zi].isDST {
|
||||
return zi
|
||||
}
|
||||
}
|
||||
|
||||
// Case 4.
|
||||
return 0
|
||||
}
|
||||
|
||||
// firstZoneUsed reports whether the first zone is used by some
|
||||
// transition.
|
||||
func (l *Location) firstZoneUsed() bool {
|
||||
for _, tx := range l.tx {
|
||||
if tx.index == 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// tzset takes a timezone string like the one found in the TZ environment
|
||||
// variable, the time of the last time zone transition expressed as seconds
|
||||
// since January 1, 1970 00:00:00 UTC, and a time expressed the same way.
|
||||
// We call this a tzset string since in C the function tzset reads TZ.
|
||||
// The return values are as for lookup, plus ok which reports whether the
|
||||
// parse succeeded.
|
||||
func tzset(s string, lastTxSec, sec int64) (name string, offset int, start, end int64, isDST, ok bool) {
|
||||
var (
|
||||
stdName, dstName string
|
||||
stdOffset, dstOffset int
|
||||
)
|
||||
|
||||
stdName, s, ok = tzsetName(s)
|
||||
if ok {
|
||||
stdOffset, s, ok = tzsetOffset(s)
|
||||
}
|
||||
if !ok {
|
||||
return "", 0, 0, 0, false, false
|
||||
}
|
||||
|
||||
// The numbers in the tzset string are added to local time to get UTC,
|
||||
// but our offsets are added to UTC to get local time,
|
||||
// so we negate the number we see here.
|
||||
stdOffset = -stdOffset
|
||||
|
||||
if len(s) == 0 || s[0] == ',' {
|
||||
// No daylight savings time.
|
||||
return stdName, stdOffset, lastTxSec, omega, false, true
|
||||
}
|
||||
|
||||
dstName, s, ok = tzsetName(s)
|
||||
if ok {
|
||||
if len(s) == 0 || s[0] == ',' {
|
||||
dstOffset = stdOffset + secondsPerHour
|
||||
} else {
|
||||
dstOffset, s, ok = tzsetOffset(s)
|
||||
dstOffset = -dstOffset // as with stdOffset, above
|
||||
}
|
||||
}
|
||||
if !ok {
|
||||
return "", 0, 0, 0, false, false
|
||||
}
|
||||
|
||||
if len(s) == 0 {
|
||||
// Default DST rules per tzcode.
|
||||
s = ",M3.2.0,M11.1.0"
|
||||
}
|
||||
// The TZ definition does not mention ';' here but tzcode accepts it.
|
||||
if s[0] != ',' && s[0] != ';' {
|
||||
return "", 0, 0, 0, false, false
|
||||
}
|
||||
s = s[1:]
|
||||
|
||||
var startRule, endRule rule
|
||||
startRule, s, ok = tzsetRule(s)
|
||||
if !ok || len(s) == 0 || s[0] != ',' {
|
||||
return "", 0, 0, 0, false, false
|
||||
}
|
||||
s = s[1:]
|
||||
endRule, s, ok = tzsetRule(s)
|
||||
if !ok || len(s) > 0 {
|
||||
return "", 0, 0, 0, false, false
|
||||
}
|
||||
|
||||
year, _, _, yday := absDate(uint64(sec+unixToInternal+internalToAbsolute), false)
|
||||
|
||||
ysec := int64(yday*secondsPerDay) + sec%secondsPerDay
|
||||
|
||||
// Compute start of year in seconds since Unix epoch.
|
||||
d := daysSinceEpoch(year)
|
||||
abs := int64(d * secondsPerDay)
|
||||
abs += absoluteToInternal + internalToUnix
|
||||
|
||||
startSec := int64(tzruleTime(year, startRule, stdOffset))
|
||||
endSec := int64(tzruleTime(year, endRule, dstOffset))
|
||||
dstIsDST, stdIsDST := true, false
|
||||
// Note: this is a flipping of "DST" and "STD" while retaining the labels
|
||||
// This happens in southern hemispheres. The labelling here thus is a little
|
||||
// inconsistent with the goal.
|
||||
if endSec < startSec {
|
||||
startSec, endSec = endSec, startSec
|
||||
stdName, dstName = dstName, stdName
|
||||
stdOffset, dstOffset = dstOffset, stdOffset
|
||||
stdIsDST, dstIsDST = dstIsDST, stdIsDST
|
||||
}
|
||||
|
||||
// The start and end values that we return are accurate
|
||||
// close to a daylight savings transition, but are otherwise
|
||||
// just the start and end of the year. That suffices for
|
||||
// the only caller that cares, which is Date.
|
||||
if ysec < startSec {
|
||||
return stdName, stdOffset, abs, startSec + abs, stdIsDST, true
|
||||
} else if ysec >= endSec {
|
||||
return stdName, stdOffset, endSec + abs, abs + 365*secondsPerDay, stdIsDST, true
|
||||
} else {
|
||||
return dstName, dstOffset, startSec + abs, endSec + abs, dstIsDST, true
|
||||
}
|
||||
}
|
||||
|
||||
// tzsetName returns the timezone name at the start of the tzset string s,
|
||||
// and the remainder of s, and reports whether the parsing is OK.
|
||||
func tzsetName(s string) (string, string, bool) {
|
||||
if len(s) == 0 {
|
||||
return "", "", false
|
||||
}
|
||||
if s[0] != '<' {
|
||||
for i, r := range s {
|
||||
switch r {
|
||||
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', ',', '-', '+':
|
||||
if i < 3 {
|
||||
return "", "", false
|
||||
}
|
||||
return s[:i], s[i:], true
|
||||
}
|
||||
}
|
||||
if len(s) < 3 {
|
||||
return "", "", false
|
||||
}
|
||||
return s, "", true
|
||||
} else {
|
||||
for i, r := range s {
|
||||
if r == '>' {
|
||||
return s[1:i], s[i+1:], true
|
||||
}
|
||||
}
|
||||
return "", "", false
|
||||
}
|
||||
}
|
||||
|
||||
// tzsetOffset returns the timezone offset at the start of the tzset string s,
|
||||
// and the remainder of s, and reports whether the parsing is OK.
|
||||
// The timezone offset is returned as a number of seconds.
|
||||
func tzsetOffset(s string) (offset int, rest string, ok bool) {
|
||||
if len(s) == 0 {
|
||||
return 0, "", false
|
||||
}
|
||||
neg := false
|
||||
if s[0] == '+' {
|
||||
s = s[1:]
|
||||
} else if s[0] == '-' {
|
||||
s = s[1:]
|
||||
neg = true
|
||||
}
|
||||
|
||||
// The tzdata code permits values up to 24 * 7 here,
|
||||
// although POSIX does not.
|
||||
var hours int
|
||||
hours, s, ok = tzsetNum(s, 0, 24*7)
|
||||
if !ok {
|
||||
return 0, "", false
|
||||
}
|
||||
off := hours * secondsPerHour
|
||||
if len(s) == 0 || s[0] != ':' {
|
||||
if neg {
|
||||
off = -off
|
||||
}
|
||||
return off, s, true
|
||||
}
|
||||
|
||||
var mins int
|
||||
mins, s, ok = tzsetNum(s[1:], 0, 59)
|
||||
if !ok {
|
||||
return 0, "", false
|
||||
}
|
||||
off += mins * secondsPerMinute
|
||||
if len(s) == 0 || s[0] != ':' {
|
||||
if neg {
|
||||
off = -off
|
||||
}
|
||||
return off, s, true
|
||||
}
|
||||
|
||||
var secs int
|
||||
secs, s, ok = tzsetNum(s[1:], 0, 59)
|
||||
if !ok {
|
||||
return 0, "", false
|
||||
}
|
||||
off += secs
|
||||
|
||||
if neg {
|
||||
off = -off
|
||||
}
|
||||
return off, s, true
|
||||
}
|
||||
|
||||
// ruleKind is the kinds of rules that can be seen in a tzset string.
|
||||
type ruleKind int
|
||||
|
||||
const (
|
||||
ruleJulian ruleKind = iota
|
||||
ruleDOY
|
||||
ruleMonthWeekDay
|
||||
)
|
||||
|
||||
// rule is a rule read from a tzset string.
|
||||
type rule struct {
|
||||
kind ruleKind
|
||||
day int
|
||||
week int
|
||||
mon int
|
||||
time int // transition time
|
||||
}
|
||||
|
||||
// tzsetRule parses a rule from a tzset string.
|
||||
// It returns the rule, and the remainder of the string, and reports success.
|
||||
func tzsetRule(s string) (rule, string, bool) {
|
||||
var r rule
|
||||
if len(s) == 0 {
|
||||
return rule{}, "", false
|
||||
}
|
||||
ok := false
|
||||
if s[0] == 'J' {
|
||||
var jday int
|
||||
jday, s, ok = tzsetNum(s[1:], 1, 365)
|
||||
if !ok {
|
||||
return rule{}, "", false
|
||||
}
|
||||
r.kind = ruleJulian
|
||||
r.day = jday
|
||||
} else if s[0] == 'M' {
|
||||
var mon int
|
||||
mon, s, ok = tzsetNum(s[1:], 1, 12)
|
||||
if !ok || len(s) == 0 || s[0] != '.' {
|
||||
return rule{}, "", false
|
||||
|
||||
}
|
||||
var week int
|
||||
week, s, ok = tzsetNum(s[1:], 1, 5)
|
||||
if !ok || len(s) == 0 || s[0] != '.' {
|
||||
return rule{}, "", false
|
||||
}
|
||||
var day int
|
||||
day, s, ok = tzsetNum(s[1:], 0, 6)
|
||||
if !ok {
|
||||
return rule{}, "", false
|
||||
}
|
||||
r.kind = ruleMonthWeekDay
|
||||
r.day = day
|
||||
r.week = week
|
||||
r.mon = mon
|
||||
} else {
|
||||
var day int
|
||||
day, s, ok = tzsetNum(s, 0, 365)
|
||||
if !ok {
|
||||
return rule{}, "", false
|
||||
}
|
||||
r.kind = ruleDOY
|
||||
r.day = day
|
||||
}
|
||||
|
||||
if len(s) == 0 || s[0] != '/' {
|
||||
r.time = 2 * secondsPerHour // 2am is the default
|
||||
return r, s, true
|
||||
}
|
||||
|
||||
offset, s, ok := tzsetOffset(s[1:])
|
||||
if !ok {
|
||||
return rule{}, "", false
|
||||
}
|
||||
r.time = offset
|
||||
|
||||
return r, s, true
|
||||
}
|
||||
|
||||
// tzsetNum parses a number from a tzset string.
|
||||
// It returns the number, and the remainder of the string, and reports success.
|
||||
// The number must be between min and max.
|
||||
func tzsetNum(s string, min, max int) (num int, rest string, ok bool) {
|
||||
if len(s) == 0 {
|
||||
return 0, "", false
|
||||
}
|
||||
num = 0
|
||||
for i, r := range s {
|
||||
if r < '0' || r > '9' {
|
||||
if i == 0 || num < min {
|
||||
return 0, "", false
|
||||
}
|
||||
return num, s[i:], true
|
||||
}
|
||||
num *= 10
|
||||
num += int(r) - '0'
|
||||
if num > max {
|
||||
return 0, "", false
|
||||
}
|
||||
}
|
||||
if num < min {
|
||||
return 0, "", false
|
||||
}
|
||||
return num, "", true
|
||||
}
|
||||
|
||||
// tzruleTime takes a year, a rule, and a timezone offset,
|
||||
// and returns the number of seconds since the start of the year
|
||||
// that the rule takes effect.
|
||||
func tzruleTime(year int, r rule, off int) int {
|
||||
var s int
|
||||
switch r.kind {
|
||||
case ruleJulian:
|
||||
s = (r.day - 1) * secondsPerDay
|
||||
if isLeap(year) && r.day >= 60 {
|
||||
s += secondsPerDay
|
||||
}
|
||||
case ruleDOY:
|
||||
s = r.day * secondsPerDay
|
||||
case ruleMonthWeekDay:
|
||||
// Zeller's Congruence.
|
||||
m1 := (r.mon+9)%12 + 1
|
||||
yy0 := year
|
||||
if r.mon <= 2 {
|
||||
yy0--
|
||||
}
|
||||
yy1 := yy0 / 100
|
||||
yy2 := yy0 % 100
|
||||
dow := ((26*m1-2)/10 + 1 + yy2 + yy2/4 + yy1/4 - 2*yy1) % 7
|
||||
if dow < 0 {
|
||||
dow += 7
|
||||
}
|
||||
// Now dow is the day-of-week of the first day of r.mon.
|
||||
// Get the day-of-month of the first "dow" day.
|
||||
d := r.day - dow
|
||||
if d < 0 {
|
||||
d += 7
|
||||
}
|
||||
for i := 1; i < r.week; i++ {
|
||||
if d+7 >= daysIn(Month(r.mon), year) {
|
||||
break
|
||||
}
|
||||
d += 7
|
||||
}
|
||||
d += int(daysBefore[r.mon-1])
|
||||
if isLeap(year) && r.mon > 2 {
|
||||
d++
|
||||
}
|
||||
s = d * secondsPerDay
|
||||
}
|
||||
|
||||
return s + r.time - off
|
||||
}
|
||||
|
||||
// lookupName returns information about the time zone with
|
||||
// the given name (such as "EST") at the given pseudo-Unix time
|
||||
// (what the given time of day would be in UTC).
|
||||
func (l *Location) lookupName(name string, unix int64) (offset int, ok bool) {
|
||||
l = l.get()
|
||||
|
||||
// First try for a zone with the right name that was actually
|
||||
// in effect at the given time. (In Sydney, Australia, both standard
|
||||
// and daylight-savings time are abbreviated "EST". Using the
|
||||
// offset helps us pick the right one for the given time.
|
||||
// It's not perfect: during the backward transition we might pick
|
||||
// either one.)
|
||||
for i := range l.zone {
|
||||
zone := &l.zone[i]
|
||||
if zone.name == name {
|
||||
nam, offset, _, _, _ := l.lookup(unix - int64(zone.offset))
|
||||
if nam == zone.name {
|
||||
return offset, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise fall back to an ordinary name match.
|
||||
for i := range l.zone {
|
||||
zone := &l.zone[i]
|
||||
if zone.name == name {
|
||||
return zone.offset, true
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, give up.
|
||||
return
|
||||
}
|
||||
|
||||
// NOTE(rsc): Eventually we will need to accept the POSIX TZ environment
|
||||
// syntax too, but I don't feel like implementing it today.
|
||||
|
||||
var errLocation = errors.New("time: invalid location name")
|
||||
|
||||
var zoneinfo *string
|
||||
var zoneinfoOnce sync.Once
|
||||
|
||||
// LoadLocation returns the Location with the given name.
|
||||
//
|
||||
// If the name is "" or "UTC", LoadLocation returns UTC.
|
||||
// If the name is "Local", LoadLocation returns Local.
|
||||
//
|
||||
// Otherwise, the name is taken to be a location name corresponding to a file
|
||||
// in the IANA Time Zone database, such as "America/New_York".
|
||||
//
|
||||
// LoadLocation looks for the IANA Time Zone database in the following
|
||||
// locations in order:
|
||||
//
|
||||
// - the directory or uncompressed zip file named by the ZONEINFO environment variable
|
||||
// - on a Unix system, the system standard installation location
|
||||
// - $GOROOT/lib/time/zoneinfo.zip
|
||||
// - the time/tzdata package, if it was imported
|
||||
func LoadLocation(name string) (*Location, error) {
|
||||
if name == "" || name == "UTC" {
|
||||
return UTC, nil
|
||||
}
|
||||
if name == "Local" {
|
||||
return Local, nil
|
||||
}
|
||||
if containsDotDot(name) || name[0] == '/' || name[0] == '\\' {
|
||||
// No valid IANA Time Zone name contains a single dot,
|
||||
// much less dot dot. Likewise, none begin with a slash.
|
||||
return nil, errLocation
|
||||
}
|
||||
zoneinfoOnce.Do(func() {
|
||||
env, _ := syscall.Getenv("ZONEINFO")
|
||||
zoneinfo = &env
|
||||
})
|
||||
var firstErr error
|
||||
if *zoneinfo != "" {
|
||||
if zoneData, err := loadTzinfoFromDirOrZip(*zoneinfo, name); err == nil {
|
||||
if z, err := LoadLocationFromTZData(name, zoneData); err == nil {
|
||||
return z, nil
|
||||
}
|
||||
firstErr = err
|
||||
} else if err != syscall.ENOENT {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
if z, err := loadLocation(name, platformZoneSources); err == nil {
|
||||
return z, nil
|
||||
} else if firstErr == nil {
|
||||
firstErr = err
|
||||
}
|
||||
return nil, firstErr
|
||||
}
|
||||
|
||||
// containsDotDot reports whether s contains "..".
|
||||
func containsDotDot(s string) bool {
|
||||
if len(s) < 2 {
|
||||
return false
|
||||
}
|
||||
for i := 0; i < len(s)-1; i++ {
|
||||
if s[i] == '.' && s[i+1] == '.' {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
155
src/time/zoneinfo_abbrs_windows.go
Normal file
155
src/time/zoneinfo_abbrs_windows.go
Normal file
@@ -0,0 +1,155 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Code generated by genzabbrs.go; DO NOT EDIT.
|
||||
// Based on information from https://raw.githubusercontent.com/unicode-org/cldr/main/common/supplemental/windowsZones.xml
|
||||
|
||||
package time
|
||||
|
||||
type abbr struct {
|
||||
std string
|
||||
dst string
|
||||
}
|
||||
|
||||
var abbrs = map[string]abbr{
|
||||
"Egypt Standard Time": {"EET", "EEST"}, // Africa/Cairo
|
||||
"Morocco Standard Time": {"+00", "+01"}, // Africa/Casablanca
|
||||
"South Africa Standard Time": {"SAST", "SAST"}, // Africa/Johannesburg
|
||||
"South Sudan Standard Time": {"CAT", "CAT"}, // Africa/Juba
|
||||
"Sudan Standard Time": {"CAT", "CAT"}, // Africa/Khartoum
|
||||
"W. Central Africa Standard Time": {"WAT", "WAT"}, // Africa/Lagos
|
||||
"E. Africa Standard Time": {"EAT", "EAT"}, // Africa/Nairobi
|
||||
"Sao Tome Standard Time": {"GMT", "GMT"}, // Africa/Sao_Tome
|
||||
"Libya Standard Time": {"EET", "EET"}, // Africa/Tripoli
|
||||
"Namibia Standard Time": {"CAT", "CAT"}, // Africa/Windhoek
|
||||
"Aleutian Standard Time": {"HST", "HDT"}, // America/Adak
|
||||
"Alaskan Standard Time": {"AKST", "AKDT"}, // America/Anchorage
|
||||
"Tocantins Standard Time": {"-03", "-03"}, // America/Araguaina
|
||||
"Paraguay Standard Time": {"-04", "-03"}, // America/Asuncion
|
||||
"Bahia Standard Time": {"-03", "-03"}, // America/Bahia
|
||||
"SA Pacific Standard Time": {"-05", "-05"}, // America/Bogota
|
||||
"Argentina Standard Time": {"-03", "-03"}, // America/Buenos_Aires
|
||||
"Eastern Standard Time (Mexico)": {"EST", "EST"}, // America/Cancun
|
||||
"Venezuela Standard Time": {"-04", "-04"}, // America/Caracas
|
||||
"SA Eastern Standard Time": {"-03", "-03"}, // America/Cayenne
|
||||
"Central Standard Time": {"CST", "CDT"}, // America/Chicago
|
||||
"Central Brazilian Standard Time": {"-04", "-04"}, // America/Cuiaba
|
||||
"Mountain Standard Time": {"MST", "MDT"}, // America/Denver
|
||||
"Greenland Standard Time": {"-03", "-02"}, // America/Godthab
|
||||
"Turks And Caicos Standard Time": {"EST", "EDT"}, // America/Grand_Turk
|
||||
"Central America Standard Time": {"CST", "CST"}, // America/Guatemala
|
||||
"Atlantic Standard Time": {"AST", "ADT"}, // America/Halifax
|
||||
"Cuba Standard Time": {"CST", "CDT"}, // America/Havana
|
||||
"US Eastern Standard Time": {"EST", "EDT"}, // America/Indianapolis
|
||||
"SA Western Standard Time": {"-04", "-04"}, // America/La_Paz
|
||||
"Pacific Standard Time": {"PST", "PDT"}, // America/Los_Angeles
|
||||
"Mountain Standard Time (Mexico)": {"MST", "MST"}, // America/Mazatlan
|
||||
"Central Standard Time (Mexico)": {"CST", "CST"}, // America/Mexico_City
|
||||
"Saint Pierre Standard Time": {"-03", "-02"}, // America/Miquelon
|
||||
"Montevideo Standard Time": {"-03", "-03"}, // America/Montevideo
|
||||
"Eastern Standard Time": {"EST", "EDT"}, // America/New_York
|
||||
"US Mountain Standard Time": {"MST", "MST"}, // America/Phoenix
|
||||
"Haiti Standard Time": {"EST", "EDT"}, // America/Port-au-Prince
|
||||
"Magallanes Standard Time": {"-03", "-03"}, // America/Punta_Arenas
|
||||
"Canada Central Standard Time": {"CST", "CST"}, // America/Regina
|
||||
"Pacific SA Standard Time": {"-04", "-03"}, // America/Santiago
|
||||
"E. South America Standard Time": {"-03", "-03"}, // America/Sao_Paulo
|
||||
"Newfoundland Standard Time": {"NST", "NDT"}, // America/St_Johns
|
||||
"Pacific Standard Time (Mexico)": {"PST", "PDT"}, // America/Tijuana
|
||||
"Yukon Standard Time": {"MST", "MST"}, // America/Whitehorse
|
||||
"Central Asia Standard Time": {"+06", "+06"}, // Asia/Almaty
|
||||
"Jordan Standard Time": {"+03", "+03"}, // Asia/Amman
|
||||
"Arabic Standard Time": {"+03", "+03"}, // Asia/Baghdad
|
||||
"Azerbaijan Standard Time": {"+04", "+04"}, // Asia/Baku
|
||||
"SE Asia Standard Time": {"+07", "+07"}, // Asia/Bangkok
|
||||
"Altai Standard Time": {"+07", "+07"}, // Asia/Barnaul
|
||||
"Middle East Standard Time": {"EET", "EEST"}, // Asia/Beirut
|
||||
"India Standard Time": {"IST", "IST"}, // Asia/Calcutta
|
||||
"Transbaikal Standard Time": {"+09", "+09"}, // Asia/Chita
|
||||
"Sri Lanka Standard Time": {"+0530", "+0530"}, // Asia/Colombo
|
||||
"Syria Standard Time": {"+03", "+03"}, // Asia/Damascus
|
||||
"Bangladesh Standard Time": {"+06", "+06"}, // Asia/Dhaka
|
||||
"Arabian Standard Time": {"+04", "+04"}, // Asia/Dubai
|
||||
"West Bank Standard Time": {"EET", "EEST"}, // Asia/Hebron
|
||||
"W. Mongolia Standard Time": {"+07", "+07"}, // Asia/Hovd
|
||||
"North Asia East Standard Time": {"+08", "+08"}, // Asia/Irkutsk
|
||||
"Israel Standard Time": {"IST", "IDT"}, // Asia/Jerusalem
|
||||
"Afghanistan Standard Time": {"+0430", "+0430"}, // Asia/Kabul
|
||||
"Russia Time Zone 11": {"+12", "+12"}, // Asia/Kamchatka
|
||||
"Pakistan Standard Time": {"PKT", "PKT"}, // Asia/Karachi
|
||||
"Nepal Standard Time": {"+0545", "+0545"}, // Asia/Katmandu
|
||||
"North Asia Standard Time": {"+07", "+07"}, // Asia/Krasnoyarsk
|
||||
"Magadan Standard Time": {"+11", "+11"}, // Asia/Magadan
|
||||
"N. Central Asia Standard Time": {"+07", "+07"}, // Asia/Novosibirsk
|
||||
"Omsk Standard Time": {"+06", "+06"}, // Asia/Omsk
|
||||
"North Korea Standard Time": {"KST", "KST"}, // Asia/Pyongyang
|
||||
"Qyzylorda Standard Time": {"+05", "+05"}, // Asia/Qyzylorda
|
||||
"Myanmar Standard Time": {"+0630", "+0630"}, // Asia/Rangoon
|
||||
"Arab Standard Time": {"+03", "+03"}, // Asia/Riyadh
|
||||
"Sakhalin Standard Time": {"+11", "+11"}, // Asia/Sakhalin
|
||||
"Korea Standard Time": {"KST", "KST"}, // Asia/Seoul
|
||||
"China Standard Time": {"CST", "CST"}, // Asia/Shanghai
|
||||
"Singapore Standard Time": {"+08", "+08"}, // Asia/Singapore
|
||||
"Russia Time Zone 10": {"+11", "+11"}, // Asia/Srednekolymsk
|
||||
"Taipei Standard Time": {"CST", "CST"}, // Asia/Taipei
|
||||
"West Asia Standard Time": {"+05", "+05"}, // Asia/Tashkent
|
||||
"Georgian Standard Time": {"+04", "+04"}, // Asia/Tbilisi
|
||||
"Iran Standard Time": {"+0330", "+0330"}, // Asia/Tehran
|
||||
"Tokyo Standard Time": {"JST", "JST"}, // Asia/Tokyo
|
||||
"Tomsk Standard Time": {"+07", "+07"}, // Asia/Tomsk
|
||||
"Ulaanbaatar Standard Time": {"+08", "+08"}, // Asia/Ulaanbaatar
|
||||
"Vladivostok Standard Time": {"+10", "+10"}, // Asia/Vladivostok
|
||||
"Yakutsk Standard Time": {"+09", "+09"}, // Asia/Yakutsk
|
||||
"Ekaterinburg Standard Time": {"+05", "+05"}, // Asia/Yekaterinburg
|
||||
"Caucasus Standard Time": {"+04", "+04"}, // Asia/Yerevan
|
||||
"Azores Standard Time": {"-01", "+00"}, // Atlantic/Azores
|
||||
"Cape Verde Standard Time": {"-01", "-01"}, // Atlantic/Cape_Verde
|
||||
"Greenwich Standard Time": {"GMT", "GMT"}, // Atlantic/Reykjavik
|
||||
"Cen. Australia Standard Time": {"ACST", "ACDT"}, // Australia/Adelaide
|
||||
"E. Australia Standard Time": {"AEST", "AEST"}, // Australia/Brisbane
|
||||
"AUS Central Standard Time": {"ACST", "ACST"}, // Australia/Darwin
|
||||
"Aus Central W. Standard Time": {"+0845", "+0845"}, // Australia/Eucla
|
||||
"Tasmania Standard Time": {"AEST", "AEDT"}, // Australia/Hobart
|
||||
"Lord Howe Standard Time": {"+1030", "+11"}, // Australia/Lord_Howe
|
||||
"W. Australia Standard Time": {"AWST", "AWST"}, // Australia/Perth
|
||||
"AUS Eastern Standard Time": {"AEST", "AEDT"}, // Australia/Sydney
|
||||
"UTC-11": {"-11", "-11"}, // Etc/GMT+11
|
||||
"Dateline Standard Time": {"-12", "-12"}, // Etc/GMT+12
|
||||
"UTC-02": {"-02", "-02"}, // Etc/GMT+2
|
||||
"UTC-08": {"-08", "-08"}, // Etc/GMT+8
|
||||
"UTC-09": {"-09", "-09"}, // Etc/GMT+9
|
||||
"UTC+12": {"+12", "+12"}, // Etc/GMT-12
|
||||
"UTC+13": {"+13", "+13"}, // Etc/GMT-13
|
||||
"UTC": {"UTC", "UTC"}, // Etc/UTC
|
||||
"Astrakhan Standard Time": {"+04", "+04"}, // Europe/Astrakhan
|
||||
"W. Europe Standard Time": {"CET", "CEST"}, // Europe/Berlin
|
||||
"GTB Standard Time": {"EET", "EEST"}, // Europe/Bucharest
|
||||
"Central Europe Standard Time": {"CET", "CEST"}, // Europe/Budapest
|
||||
"E. Europe Standard Time": {"EET", "EEST"}, // Europe/Chisinau
|
||||
"Turkey Standard Time": {"+03", "+03"}, // Europe/Istanbul
|
||||
"Kaliningrad Standard Time": {"EET", "EET"}, // Europe/Kaliningrad
|
||||
"FLE Standard Time": {"EET", "EEST"}, // Europe/Kiev
|
||||
"GMT Standard Time": {"GMT", "BST"}, // Europe/London
|
||||
"Belarus Standard Time": {"+03", "+03"}, // Europe/Minsk
|
||||
"Russian Standard Time": {"MSK", "MSK"}, // Europe/Moscow
|
||||
"Romance Standard Time": {"CET", "CEST"}, // Europe/Paris
|
||||
"Russia Time Zone 3": {"+04", "+04"}, // Europe/Samara
|
||||
"Saratov Standard Time": {"+04", "+04"}, // Europe/Saratov
|
||||
"Volgograd Standard Time": {"MSK", "MSK"}, // Europe/Volgograd
|
||||
"Central European Standard Time": {"CET", "CEST"}, // Europe/Warsaw
|
||||
"Mauritius Standard Time": {"+04", "+04"}, // Indian/Mauritius
|
||||
"Samoa Standard Time": {"+13", "+13"}, // Pacific/Apia
|
||||
"New Zealand Standard Time": {"NZST", "NZDT"}, // Pacific/Auckland
|
||||
"Bougainville Standard Time": {"+11", "+11"}, // Pacific/Bougainville
|
||||
"Chatham Islands Standard Time": {"+1245", "+1345"}, // Pacific/Chatham
|
||||
"Easter Island Standard Time": {"-06", "-05"}, // Pacific/Easter
|
||||
"Fiji Standard Time": {"+12", "+12"}, // Pacific/Fiji
|
||||
"Central Pacific Standard Time": {"+11", "+11"}, // Pacific/Guadalcanal
|
||||
"Hawaiian Standard Time": {"HST", "HST"}, // Pacific/Honolulu
|
||||
"Line Islands Standard Time": {"+14", "+14"}, // Pacific/Kiritimati
|
||||
"Marquesas Standard Time": {"-0930", "-0930"}, // Pacific/Marquesas
|
||||
"Norfolk Standard Time": {"+11", "+12"}, // Pacific/Norfolk
|
||||
"West Pacific Standard Time": {"+10", "+10"}, // Pacific/Port_Moresby
|
||||
"Tonga Standard Time": {"+13", "+13"}, // Pacific/Tongatapu
|
||||
}
|
||||
87
src/time/zoneinfo_android.go
Normal file
87
src/time/zoneinfo_android.go
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Parse the "tzdata" packed timezone file used on Android.
|
||||
// The format is lifted from ZoneInfoDB.java and ZoneInfo.java in
|
||||
// java/libcore/util in the AOSP.
|
||||
|
||||
package time
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var platformZoneSources = []string{
|
||||
"/system/usr/share/zoneinfo/tzdata",
|
||||
"/data/misc/zoneinfo/current/tzdata",
|
||||
}
|
||||
|
||||
func initLocal() {
|
||||
// TODO(elias.naur): getprop persist.sys.timezone
|
||||
localLoc = *UTC
|
||||
}
|
||||
|
||||
func init() {
|
||||
loadTzinfoFromTzdata = androidLoadTzinfoFromTzdata
|
||||
}
|
||||
|
||||
var allowGorootSource = true
|
||||
|
||||
func gorootZoneSource(goroot string) (string, bool) {
|
||||
if goroot == "" || !allowGorootSource {
|
||||
return "", false
|
||||
}
|
||||
return goroot + "/lib/time/zoneinfo.zip", true
|
||||
}
|
||||
|
||||
func androidLoadTzinfoFromTzdata(file, name string) ([]byte, error) {
|
||||
const (
|
||||
headersize = 12 + 3*4
|
||||
namesize = 40
|
||||
entrysize = namesize + 3*4
|
||||
)
|
||||
if len(name) > namesize {
|
||||
return nil, errors.New(name + " is longer than the maximum zone name length (40 bytes)")
|
||||
}
|
||||
fd, err := open(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer closefd(fd)
|
||||
|
||||
buf := make([]byte, headersize)
|
||||
if err := preadn(fd, buf, 0); err != nil {
|
||||
return nil, errors.New("corrupt tzdata file " + file)
|
||||
}
|
||||
d := dataIO{buf, false}
|
||||
if magic := d.read(6); string(magic) != "tzdata" {
|
||||
return nil, errors.New("corrupt tzdata file " + file)
|
||||
}
|
||||
d = dataIO{buf[12:], false}
|
||||
indexOff, _ := d.big4()
|
||||
dataOff, _ := d.big4()
|
||||
indexSize := dataOff - indexOff
|
||||
entrycount := indexSize / entrysize
|
||||
buf = make([]byte, indexSize)
|
||||
if err := preadn(fd, buf, int(indexOff)); err != nil {
|
||||
return nil, errors.New("corrupt tzdata file " + file)
|
||||
}
|
||||
for i := 0; i < int(entrycount); i++ {
|
||||
entry := buf[i*entrysize : (i+1)*entrysize]
|
||||
// len(name) <= namesize is checked at function entry
|
||||
if string(entry[:len(name)]) != name {
|
||||
continue
|
||||
}
|
||||
d := dataIO{entry[namesize:], false}
|
||||
off, _ := d.big4()
|
||||
size, _ := d.big4()
|
||||
buf := make([]byte, size)
|
||||
if err := preadn(fd, buf, int(off+dataOff)); err != nil {
|
||||
return nil, errors.New("corrupt tzdata file " + file)
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
return nil, syscall.ENOENT
|
||||
}
|
||||
18
src/time/zoneinfo_android_test.go
Normal file
18
src/time/zoneinfo_android_test.go
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2016 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package time_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
. "time"
|
||||
)
|
||||
|
||||
func TestAndroidTzdata(t *testing.T) {
|
||||
undo := ForceAndroidTzdataForTest()
|
||||
defer undo()
|
||||
if _, err := LoadLocation("America/Los_Angeles"); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
14
src/time/zoneinfo_goroot.go
Normal file
14
src/time/zoneinfo_goroot.go
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2022 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !ios && !android
|
||||
|
||||
package time
|
||||
|
||||
func gorootZoneSource(goroot string) (string, bool) {
|
||||
if goroot == "" {
|
||||
return "", false
|
||||
}
|
||||
return goroot + "/lib/time/zoneinfo.zip", true
|
||||
}
|
||||
45
src/time/zoneinfo_ios.go
Normal file
45
src/time/zoneinfo_ios.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2015 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build ios
|
||||
|
||||
package time
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var platformZoneSources []string // none on iOS
|
||||
|
||||
func gorootZoneSource(goroot string) (string, bool) {
|
||||
// The working directory at initialization is the root of the
|
||||
// app bundle: "/private/.../bundlename.app". That's where we
|
||||
// keep zoneinfo.zip for tethered iOS builds.
|
||||
// For self-hosted iOS builds, the zoneinfo.zip is in GOROOT.
|
||||
var roots []string
|
||||
if goroot != "" {
|
||||
roots = append(roots, goroot+"/lib/time")
|
||||
}
|
||||
wd, err := syscall.Getwd()
|
||||
if err == nil {
|
||||
roots = append(roots, wd)
|
||||
}
|
||||
for _, r := range roots {
|
||||
var st syscall.Stat_t
|
||||
fd, err := syscall.Open(r, syscall.O_RDONLY, 0)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
defer syscall.Close(fd)
|
||||
if err := syscall.Fstat(fd, &st); err == nil {
|
||||
return r + "/zoneinfo.zip", true
|
||||
}
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
func initLocal() {
|
||||
// TODO(crawshaw): [NSTimeZone localTimeZone]
|
||||
localLoc = *UTC
|
||||
}
|
||||
45
src/time/zoneinfo_js.go
Normal file
45
src/time/zoneinfo_js.go
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2018 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build js && wasm
|
||||
|
||||
package time
|
||||
|
||||
import (
|
||||
"internal/itoa"
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
var platformZoneSources = []string{
|
||||
"/usr/share/zoneinfo/",
|
||||
"/usr/share/lib/zoneinfo/",
|
||||
"/usr/lib/locale/TZ/",
|
||||
}
|
||||
|
||||
func initLocal() {
|
||||
localLoc.name = "Local"
|
||||
|
||||
z := zone{}
|
||||
d := js.Global().Get("Date").New()
|
||||
offset := d.Call("getTimezoneOffset").Int() * -1
|
||||
z.offset = offset * 60
|
||||
// According to https://tc39.github.io/ecma262/#sec-timezoneestring,
|
||||
// the timezone name from (new Date()).toTimeString() is an implementation-dependent
|
||||
// result, and in Google Chrome, it gives the fully expanded name rather than
|
||||
// the abbreviation.
|
||||
// Hence, we construct the name from the offset.
|
||||
z.name = "UTC"
|
||||
if offset < 0 {
|
||||
z.name += "-"
|
||||
offset *= -1
|
||||
} else {
|
||||
z.name += "+"
|
||||
}
|
||||
z.name += itoa.Itoa(offset / 60)
|
||||
min := offset % 60
|
||||
if min != 0 {
|
||||
z.name += ":" + itoa.Itoa(min)
|
||||
}
|
||||
localLoc.zone = []zone{z}
|
||||
}
|
||||
139
src/time/zoneinfo_plan9.go
Normal file
139
src/time/zoneinfo_plan9.go
Normal file
@@ -0,0 +1,139 @@
|
||||
// Copyright 2011 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Parse Plan 9 timezone(2) files.
|
||||
|
||||
package time
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var platformZoneSources []string // none on Plan 9
|
||||
|
||||
func isSpace(r rune) bool {
|
||||
return r == ' ' || r == '\t' || r == '\n'
|
||||
}
|
||||
|
||||
// Copied from strings to avoid a dependency.
|
||||
func fields(s string) []string {
|
||||
// First count the fields.
|
||||
n := 0
|
||||
inField := false
|
||||
for _, rune := range s {
|
||||
wasInField := inField
|
||||
inField = !isSpace(rune)
|
||||
if inField && !wasInField {
|
||||
n++
|
||||
}
|
||||
}
|
||||
|
||||
// Now create them.
|
||||
a := make([]string, n)
|
||||
na := 0
|
||||
fieldStart := -1 // Set to -1 when looking for start of field.
|
||||
for i, rune := range s {
|
||||
if isSpace(rune) {
|
||||
if fieldStart >= 0 {
|
||||
a[na] = s[fieldStart:i]
|
||||
na++
|
||||
fieldStart = -1
|
||||
}
|
||||
} else if fieldStart == -1 {
|
||||
fieldStart = i
|
||||
}
|
||||
}
|
||||
if fieldStart >= 0 { // Last field might end at EOF.
|
||||
a[na] = s[fieldStart:]
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func loadZoneDataPlan9(s string) (l *Location, err error) {
|
||||
f := fields(s)
|
||||
if len(f) < 4 {
|
||||
if len(f) == 2 && f[0] == "GMT" {
|
||||
return UTC, nil
|
||||
}
|
||||
return nil, errBadData
|
||||
}
|
||||
|
||||
var zones [2]zone
|
||||
|
||||
// standard timezone offset
|
||||
o, err := atoi(f[1])
|
||||
if err != nil {
|
||||
return nil, errBadData
|
||||
}
|
||||
zones[0] = zone{name: f[0], offset: o, isDST: false}
|
||||
|
||||
// alternate timezone offset
|
||||
o, err = atoi(f[3])
|
||||
if err != nil {
|
||||
return nil, errBadData
|
||||
}
|
||||
zones[1] = zone{name: f[2], offset: o, isDST: true}
|
||||
|
||||
// transition time pairs
|
||||
var tx []zoneTrans
|
||||
f = f[4:]
|
||||
for i := 0; i < len(f); i++ {
|
||||
zi := 0
|
||||
if i%2 == 0 {
|
||||
zi = 1
|
||||
}
|
||||
t, err := atoi(f[i])
|
||||
if err != nil {
|
||||
return nil, errBadData
|
||||
}
|
||||
t -= zones[0].offset
|
||||
tx = append(tx, zoneTrans{when: int64(t), index: uint8(zi)})
|
||||
}
|
||||
|
||||
// Committed to succeed.
|
||||
l = &Location{zone: zones[:], tx: tx}
|
||||
|
||||
// Fill in the cache with information about right now,
|
||||
// since that will be the most common lookup.
|
||||
sec, _, _ := now()
|
||||
for i := range tx {
|
||||
if tx[i].when <= sec && (i+1 == len(tx) || sec < tx[i+1].when) {
|
||||
l.cacheStart = tx[i].when
|
||||
l.cacheEnd = omega
|
||||
if i+1 < len(tx) {
|
||||
l.cacheEnd = tx[i+1].when
|
||||
}
|
||||
l.cacheZone = &l.zone[tx[i].index]
|
||||
}
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func loadZoneFilePlan9(name string) (*Location, error) {
|
||||
b, err := readFile(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return loadZoneDataPlan9(string(b))
|
||||
}
|
||||
|
||||
func initLocal() {
|
||||
t, ok := syscall.Getenv("timezone")
|
||||
if ok {
|
||||
if z, err := loadZoneDataPlan9(t); err == nil {
|
||||
localLoc = *z
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if z, err := loadZoneFilePlan9("/adm/timezone/local"); err == nil {
|
||||
localLoc = *z
|
||||
localLoc.name = "Local"
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to UTC.
|
||||
localLoc.name = "UTC"
|
||||
}
|
||||
599
src/time/zoneinfo_read.go
Normal file
599
src/time/zoneinfo_read.go
Normal file
@@ -0,0 +1,599 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Parse "zoneinfo" time zone file.
|
||||
// This is a fairly standard file format used on OS X, Linux, BSD, Sun, and others.
|
||||
// See tzfile(5), https://en.wikipedia.org/wiki/Zoneinfo,
|
||||
// and ftp://munnari.oz.au/pub/oldtz/
|
||||
|
||||
package time
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"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
|
||||
}
|
||||
|
||||
// loadFromEmbeddedTZData is used to load a specific tzdata file
|
||||
// from tzdata information embedded in the binary itself.
|
||||
// This is set when the time/tzdata package is imported,
|
||||
// via registerLoadFromEmbeddedTzdata.
|
||||
var loadFromEmbeddedTZData func(zipname string) (string, error)
|
||||
|
||||
// maxFileSize is the max permitted size of files read by readFile.
|
||||
// As reference, the zoneinfo.zip distributed by Go is ~350 KB,
|
||||
// so 10MB is overkill.
|
||||
const maxFileSize = 10 << 20
|
||||
|
||||
type fileSizeError string
|
||||
|
||||
func (f fileSizeError) Error() string {
|
||||
return "time: file " + string(f) + " is too large"
|
||||
}
|
||||
|
||||
// Copies of io.Seek* constants to avoid importing "io":
|
||||
const (
|
||||
seekStart = 0
|
||||
seekCurrent = 1
|
||||
seekEnd = 2
|
||||
)
|
||||
|
||||
// Simple I/O interface to binary blob of data.
|
||||
type dataIO struct {
|
||||
p []byte
|
||||
error bool
|
||||
}
|
||||
|
||||
func (d *dataIO) read(n int) []byte {
|
||||
if len(d.p) < n {
|
||||
d.p = nil
|
||||
d.error = true
|
||||
return nil
|
||||
}
|
||||
p := d.p[0:n]
|
||||
d.p = d.p[n:]
|
||||
return p
|
||||
}
|
||||
|
||||
func (d *dataIO) big4() (n uint32, ok bool) {
|
||||
p := d.read(4)
|
||||
if len(p) < 4 {
|
||||
d.error = true
|
||||
return 0, false
|
||||
}
|
||||
return uint32(p[3]) | uint32(p[2])<<8 | uint32(p[1])<<16 | uint32(p[0])<<24, true
|
||||
}
|
||||
|
||||
func (d *dataIO) big8() (n uint64, ok bool) {
|
||||
n1, ok1 := d.big4()
|
||||
n2, ok2 := d.big4()
|
||||
if !ok1 || !ok2 {
|
||||
d.error = true
|
||||
return 0, false
|
||||
}
|
||||
return (uint64(n1) << 32) | uint64(n2), true
|
||||
}
|
||||
|
||||
func (d *dataIO) byte() (n byte, ok bool) {
|
||||
p := d.read(1)
|
||||
if len(p) < 1 {
|
||||
d.error = true
|
||||
return 0, false
|
||||
}
|
||||
return p[0], true
|
||||
}
|
||||
|
||||
// rest returns the rest of the data in the buffer.
|
||||
func (d *dataIO) rest() []byte {
|
||||
r := d.p
|
||||
d.p = nil
|
||||
return r
|
||||
}
|
||||
|
||||
// Make a string by stopping at the first NUL
|
||||
func byteString(p []byte) string {
|
||||
if i := bytealg.IndexByte(p, 0); i != -1 {
|
||||
p = p[:i]
|
||||
}
|
||||
return string(p)
|
||||
}
|
||||
|
||||
var errBadData = errors.New("malformed time zone information")
|
||||
|
||||
// LoadLocationFromTZData returns a Location with the given name
|
||||
// initialized from the IANA Time Zone database-formatted data.
|
||||
// The data should be in the format of a standard IANA time zone file
|
||||
// (for example, the content of /etc/localtime on Unix systems).
|
||||
func LoadLocationFromTZData(name string, data []byte) (*Location, error) {
|
||||
d := dataIO{data, false}
|
||||
|
||||
// 4-byte magic "TZif"
|
||||
if magic := d.read(4); string(magic) != "TZif" {
|
||||
return nil, errBadData
|
||||
}
|
||||
|
||||
// 1-byte version, then 15 bytes of padding
|
||||
var version int
|
||||
var p []byte
|
||||
if p = d.read(16); len(p) != 16 {
|
||||
return nil, errBadData
|
||||
} else {
|
||||
switch p[0] {
|
||||
case 0:
|
||||
version = 1
|
||||
case '2':
|
||||
version = 2
|
||||
case '3':
|
||||
version = 3
|
||||
default:
|
||||
return nil, errBadData
|
||||
}
|
||||
}
|
||||
|
||||
// six big-endian 32-bit integers:
|
||||
// number of UTC/local indicators
|
||||
// number of standard/wall indicators
|
||||
// number of leap seconds
|
||||
// number of transition times
|
||||
// number of local time zones
|
||||
// number of characters of time zone abbrev strings
|
||||
const (
|
||||
NUTCLocal = iota
|
||||
NStdWall
|
||||
NLeap
|
||||
NTime
|
||||
NZone
|
||||
NChar
|
||||
)
|
||||
var n [6]int
|
||||
for i := 0; i < 6; i++ {
|
||||
nn, ok := d.big4()
|
||||
if !ok {
|
||||
return nil, errBadData
|
||||
}
|
||||
if uint32(int(nn)) != nn {
|
||||
return nil, errBadData
|
||||
}
|
||||
n[i] = int(nn)
|
||||
}
|
||||
|
||||
// If we have version 2 or 3, then the data is first written out
|
||||
// in a 32-bit format, then written out again in a 64-bit format.
|
||||
// Skip the 32-bit format and read the 64-bit one, as it can
|
||||
// describe a broader range of dates.
|
||||
|
||||
is64 := false
|
||||
if version > 1 {
|
||||
// Skip the 32-bit data.
|
||||
skip := n[NTime]*4 +
|
||||
n[NTime] +
|
||||
n[NZone]*6 +
|
||||
n[NChar] +
|
||||
n[NLeap]*8 +
|
||||
n[NStdWall] +
|
||||
n[NUTCLocal]
|
||||
// Skip the version 2 header that we just read.
|
||||
skip += 4 + 16
|
||||
d.read(skip)
|
||||
|
||||
is64 = true
|
||||
|
||||
// Read the counts again, they can differ.
|
||||
for i := 0; i < 6; i++ {
|
||||
nn, ok := d.big4()
|
||||
if !ok {
|
||||
return nil, errBadData
|
||||
}
|
||||
if uint32(int(nn)) != nn {
|
||||
return nil, errBadData
|
||||
}
|
||||
n[i] = int(nn)
|
||||
}
|
||||
}
|
||||
|
||||
size := 4
|
||||
if is64 {
|
||||
size = 8
|
||||
}
|
||||
|
||||
// Transition times.
|
||||
txtimes := dataIO{d.read(n[NTime] * size), false}
|
||||
|
||||
// Time zone indices for transition times.
|
||||
txzones := d.read(n[NTime])
|
||||
|
||||
// Zone info structures
|
||||
zonedata := dataIO{d.read(n[NZone] * 6), false}
|
||||
|
||||
// Time zone abbreviations.
|
||||
abbrev := d.read(n[NChar])
|
||||
|
||||
// Leap-second time pairs
|
||||
d.read(n[NLeap] * (size + 4))
|
||||
|
||||
// Whether tx times associated with local time types
|
||||
// are specified as standard time or wall time.
|
||||
isstd := d.read(n[NStdWall])
|
||||
|
||||
// Whether tx times associated with local time types
|
||||
// are specified as UTC or local time.
|
||||
isutc := d.read(n[NUTCLocal])
|
||||
|
||||
if d.error { // ran out of data
|
||||
return nil, errBadData
|
||||
}
|
||||
|
||||
var extend string
|
||||
rest := d.rest()
|
||||
if len(rest) > 2 && rest[0] == '\n' && rest[len(rest)-1] == '\n' {
|
||||
extend = string(rest[1 : len(rest)-1])
|
||||
}
|
||||
|
||||
// Now we can build up a useful data structure.
|
||||
// First the zone information.
|
||||
// utcoff[4] isdst[1] nameindex[1]
|
||||
nzone := n[NZone]
|
||||
if nzone == 0 {
|
||||
// Reject tzdata files with no zones. There's nothing useful in them.
|
||||
// This also avoids a panic later when we add and then use a fake transition (golang.org/issue/29437).
|
||||
return nil, errBadData
|
||||
}
|
||||
zones := make([]zone, nzone)
|
||||
for i := range zones {
|
||||
var ok bool
|
||||
var n uint32
|
||||
if n, ok = zonedata.big4(); !ok {
|
||||
return nil, errBadData
|
||||
}
|
||||
if uint32(int(n)) != n {
|
||||
return nil, errBadData
|
||||
}
|
||||
zones[i].offset = int(int32(n))
|
||||
var b byte
|
||||
if b, ok = zonedata.byte(); !ok {
|
||||
return nil, errBadData
|
||||
}
|
||||
zones[i].isDST = b != 0
|
||||
if b, ok = zonedata.byte(); !ok || int(b) >= len(abbrev) {
|
||||
return nil, errBadData
|
||||
}
|
||||
zones[i].name = byteString(abbrev[b:])
|
||||
if runtime.GOOS == "aix" && len(name) > 8 && (name[:8] == "Etc/GMT+" || name[:8] == "Etc/GMT-") {
|
||||
// There is a bug with AIX 7.2 TL 0 with files in Etc,
|
||||
// GMT+1 will return GMT-1 instead of GMT+1 or -01.
|
||||
if name != "Etc/GMT+0" {
|
||||
// GMT+0 is OK
|
||||
zones[i].name = name[4:]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now the transition time info.
|
||||
tx := make([]zoneTrans, n[NTime])
|
||||
for i := range tx {
|
||||
var n int64
|
||||
if !is64 {
|
||||
if n4, ok := txtimes.big4(); !ok {
|
||||
return nil, errBadData
|
||||
} else {
|
||||
n = int64(int32(n4))
|
||||
}
|
||||
} else {
|
||||
if n8, ok := txtimes.big8(); !ok {
|
||||
return nil, errBadData
|
||||
} else {
|
||||
n = int64(n8)
|
||||
}
|
||||
}
|
||||
tx[i].when = n
|
||||
if int(txzones[i]) >= len(zones) {
|
||||
return nil, errBadData
|
||||
}
|
||||
tx[i].index = txzones[i]
|
||||
if i < len(isstd) {
|
||||
tx[i].isstd = isstd[i] != 0
|
||||
}
|
||||
if i < len(isutc) {
|
||||
tx[i].isutc = isutc[i] != 0
|
||||
}
|
||||
}
|
||||
|
||||
if len(tx) == 0 {
|
||||
// Build fake transition to cover all time.
|
||||
// This happens in fixed locations like "Etc/GMT0".
|
||||
tx = append(tx, zoneTrans{when: alpha, index: 0})
|
||||
}
|
||||
|
||||
// Committed to succeed.
|
||||
l := &Location{zone: zones, tx: tx, name: name, extend: extend}
|
||||
|
||||
// Fill in the cache with information about right now,
|
||||
// since that will be the most common lookup.
|
||||
sec, _, _ := now()
|
||||
for i := range tx {
|
||||
if tx[i].when <= sec && (i+1 == len(tx) || sec < tx[i+1].when) {
|
||||
l.cacheStart = tx[i].when
|
||||
l.cacheEnd = omega
|
||||
l.cacheZone = &l.zone[tx[i].index]
|
||||
if i+1 < len(tx) {
|
||||
l.cacheEnd = tx[i+1].when
|
||||
} else if l.extend != "" {
|
||||
// If we're at the end of the known zone transitions,
|
||||
// try the extend string.
|
||||
if name, offset, estart, eend, isDST, ok := tzset(l.extend, l.cacheStart, sec); ok {
|
||||
l.cacheStart = estart
|
||||
l.cacheEnd = eend
|
||||
// Find the zone that is returned by tzset to avoid allocation if possible.
|
||||
if zoneIdx := findZone(l.zone, name, offset, isDST); zoneIdx != -1 {
|
||||
l.cacheZone = &l.zone[zoneIdx]
|
||||
} else {
|
||||
l.cacheZone = &zone{
|
||||
name: name,
|
||||
offset: offset,
|
||||
isDST: isDST,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func findZone(zones []zone, name string, offset int, isDST bool) int {
|
||||
for i, z := range zones {
|
||||
if z.name == name && z.offset == offset && z.isDST == isDST {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
// loadTzinfoFromDirOrZip returns the contents of the file with the given name
|
||||
// in dir. dir can either be an uncompressed zip file, or a directory.
|
||||
func loadTzinfoFromDirOrZip(dir, name string) ([]byte, error) {
|
||||
if len(dir) > 4 && dir[len(dir)-4:] == ".zip" {
|
||||
return loadTzinfoFromZip(dir, name)
|
||||
}
|
||||
if dir != "" {
|
||||
name = dir + "/" + name
|
||||
}
|
||||
return readFile(name)
|
||||
}
|
||||
|
||||
// There are 500+ zoneinfo files. Rather than distribute them all
|
||||
// individually, we ship them in an uncompressed zip file.
|
||||
// Used this way, the zip file format serves as a commonly readable
|
||||
// container for the individual small files. We choose zip over tar
|
||||
// because zip files have a contiguous table of contents, making
|
||||
// individual file lookups faster, and because the per-file overhead
|
||||
// in a zip file is considerably less than tar's 512 bytes.
|
||||
|
||||
// get4 returns the little-endian 32-bit value in b.
|
||||
func get4(b []byte) int {
|
||||
if len(b) < 4 {
|
||||
return 0
|
||||
}
|
||||
return int(b[0]) | int(b[1])<<8 | int(b[2])<<16 | int(b[3])<<24
|
||||
}
|
||||
|
||||
// get2 returns the little-endian 16-bit value in b.
|
||||
func get2(b []byte) int {
|
||||
if len(b) < 2 {
|
||||
return 0
|
||||
}
|
||||
return int(b[0]) | int(b[1])<<8
|
||||
}
|
||||
|
||||
// loadTzinfoFromZip returns the contents of the file with the given name
|
||||
// in the given uncompressed zip file.
|
||||
func loadTzinfoFromZip(zipfile, name string) ([]byte, error) {
|
||||
fd, err := open(zipfile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer closefd(fd)
|
||||
|
||||
const (
|
||||
zecheader = 0x06054b50
|
||||
zcheader = 0x02014b50
|
||||
ztailsize = 22
|
||||
|
||||
zheadersize = 30
|
||||
zheader = 0x04034b50
|
||||
)
|
||||
|
||||
buf := make([]byte, ztailsize)
|
||||
if err := preadn(fd, buf, -ztailsize); err != nil || get4(buf) != zecheader {
|
||||
return nil, errors.New("corrupt zip file " + zipfile)
|
||||
}
|
||||
n := get2(buf[10:])
|
||||
size := get4(buf[12:])
|
||||
off := get4(buf[16:])
|
||||
|
||||
buf = make([]byte, size)
|
||||
if err := preadn(fd, buf, off); err != nil {
|
||||
return nil, errors.New("corrupt zip file " + zipfile)
|
||||
}
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
// zip entry layout:
|
||||
// 0 magic[4]
|
||||
// 4 madevers[1]
|
||||
// 5 madeos[1]
|
||||
// 6 extvers[1]
|
||||
// 7 extos[1]
|
||||
// 8 flags[2]
|
||||
// 10 meth[2]
|
||||
// 12 modtime[2]
|
||||
// 14 moddate[2]
|
||||
// 16 crc[4]
|
||||
// 20 csize[4]
|
||||
// 24 uncsize[4]
|
||||
// 28 namelen[2]
|
||||
// 30 xlen[2]
|
||||
// 32 fclen[2]
|
||||
// 34 disknum[2]
|
||||
// 36 iattr[2]
|
||||
// 38 eattr[4]
|
||||
// 42 off[4]
|
||||
// 46 name[namelen]
|
||||
// 46+namelen+xlen+fclen - next header
|
||||
//
|
||||
if get4(buf) != zcheader {
|
||||
break
|
||||
}
|
||||
meth := get2(buf[10:])
|
||||
size := get4(buf[24:])
|
||||
namelen := get2(buf[28:])
|
||||
xlen := get2(buf[30:])
|
||||
fclen := get2(buf[32:])
|
||||
off := get4(buf[42:])
|
||||
zname := buf[46 : 46+namelen]
|
||||
buf = buf[46+namelen+xlen+fclen:]
|
||||
if string(zname) != name {
|
||||
continue
|
||||
}
|
||||
if meth != 0 {
|
||||
return nil, errors.New("unsupported compression for " + name + " in " + zipfile)
|
||||
}
|
||||
|
||||
// zip per-file header layout:
|
||||
// 0 magic[4]
|
||||
// 4 extvers[1]
|
||||
// 5 extos[1]
|
||||
// 6 flags[2]
|
||||
// 8 meth[2]
|
||||
// 10 modtime[2]
|
||||
// 12 moddate[2]
|
||||
// 14 crc[4]
|
||||
// 18 csize[4]
|
||||
// 22 uncsize[4]
|
||||
// 26 namelen[2]
|
||||
// 28 xlen[2]
|
||||
// 30 name[namelen]
|
||||
// 30+namelen+xlen - file data
|
||||
//
|
||||
buf = make([]byte, zheadersize+namelen)
|
||||
if err := preadn(fd, buf, off); err != nil ||
|
||||
get4(buf) != zheader ||
|
||||
get2(buf[8:]) != meth ||
|
||||
get2(buf[26:]) != namelen ||
|
||||
string(buf[30:30+namelen]) != name {
|
||||
return nil, errors.New("corrupt zip file " + zipfile)
|
||||
}
|
||||
xlen = get2(buf[28:])
|
||||
|
||||
buf = make([]byte, size)
|
||||
if err := preadn(fd, buf, off+30+namelen+xlen); err != nil {
|
||||
return nil, errors.New("corrupt zip file " + zipfile)
|
||||
}
|
||||
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
return nil, syscall.ENOENT
|
||||
}
|
||||
|
||||
// loadTzinfoFromTzdata returns the time zone information of the time zone
|
||||
// with the given name, from a tzdata database file as they are typically
|
||||
// found on android.
|
||||
var loadTzinfoFromTzdata func(file, name string) ([]byte, error)
|
||||
|
||||
// loadTzinfo returns the time zone information of the time zone
|
||||
// with the given name, from a given source. A source may be a
|
||||
// timezone database directory, tzdata database file or an uncompressed
|
||||
// zip file, containing the contents of such a directory.
|
||||
func loadTzinfo(name string, source string) ([]byte, error) {
|
||||
if len(source) >= 6 && source[len(source)-6:] == "tzdata" {
|
||||
return loadTzinfoFromTzdata(source, name)
|
||||
}
|
||||
return loadTzinfoFromDirOrZip(source, name)
|
||||
}
|
||||
|
||||
// loadLocation returns the Location with the given name from one of
|
||||
// the specified sources. See loadTzinfo for a list of supported sources.
|
||||
// The first timezone data matching the given name that is successfully loaded
|
||||
// and parsed is returned as a Location.
|
||||
func loadLocation(name string, sources []string) (z *Location, firstErr error) {
|
||||
for _, source := range sources {
|
||||
zoneData, err := loadTzinfo(name, source)
|
||||
if err == nil {
|
||||
if z, err = LoadLocationFromTZData(name, zoneData); err == nil {
|
||||
return z, nil
|
||||
}
|
||||
}
|
||||
if firstErr == nil && err != syscall.ENOENT {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
if loadFromEmbeddedTZData != nil {
|
||||
zoneData, err := loadFromEmbeddedTZData(name)
|
||||
if err == nil {
|
||||
if z, err = LoadLocationFromTZData(name, []byte(zoneData)); err == nil {
|
||||
return z, nil
|
||||
}
|
||||
}
|
||||
if firstErr == nil && err != syscall.ENOENT {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
if source, ok := gorootZoneSource(runtime.GOROOT()); ok {
|
||||
zoneData, err := loadTzinfo(name, source)
|
||||
if err == nil {
|
||||
if z, err = LoadLocationFromTZData(name, zoneData); err == nil {
|
||||
return z, nil
|
||||
}
|
||||
}
|
||||
if firstErr == nil && err != syscall.ENOENT {
|
||||
firstErr = err
|
||||
}
|
||||
}
|
||||
if firstErr != nil {
|
||||
return nil, firstErr
|
||||
}
|
||||
return nil, errors.New("unknown time zone " + name)
|
||||
}
|
||||
|
||||
// readFile reads and returns the content of the named file.
|
||||
// It is a trivial implementation of os.ReadFile, reimplemented
|
||||
// here to avoid depending on io/ioutil or os.
|
||||
// It returns an error if name exceeds maxFileSize bytes.
|
||||
func readFile(name string) ([]byte, error) {
|
||||
f, err := open(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer closefd(f)
|
||||
var (
|
||||
buf [4096]byte
|
||||
ret []byte
|
||||
n int
|
||||
)
|
||||
for {
|
||||
n, err = read(f, buf[:])
|
||||
if n > 0 {
|
||||
ret = append(ret, buf[:n]...)
|
||||
}
|
||||
if n == 0 || err != nil {
|
||||
break
|
||||
}
|
||||
if len(ret) > maxFileSize {
|
||||
return nil, fileSizeError(name)
|
||||
}
|
||||
}
|
||||
return ret, err
|
||||
}
|
||||
350
src/time/zoneinfo_test.go
Normal file
350
src/time/zoneinfo_test.go
Normal file
@@ -0,0 +1,350 @@
|
||||
// Copyright 2014 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package time_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"internal/testenv"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if time.ZoneinfoForTesting() != nil {
|
||||
panic(fmt.Errorf("zoneinfo initialized before first LoadLocation"))
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvVarUsage(t *testing.T) {
|
||||
time.ResetZoneinfoForTesting()
|
||||
|
||||
const testZoneinfo = "foo.zip"
|
||||
const env = "ZONEINFO"
|
||||
|
||||
t.Setenv(env, testZoneinfo)
|
||||
|
||||
// Result isn't important, we're testing the side effect of this command
|
||||
time.LoadLocation("Asia/Jerusalem")
|
||||
defer time.ResetZoneinfoForTesting()
|
||||
|
||||
if zoneinfo := time.ZoneinfoForTesting(); testZoneinfo != *zoneinfo {
|
||||
t.Errorf("zoneinfo does not match env variable: got %q want %q", *zoneinfo, testZoneinfo)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBadLocationErrMsg(t *testing.T) {
|
||||
time.ResetZoneinfoForTesting()
|
||||
loc := "Asia/SomethingNotExist"
|
||||
want := errors.New("unknown time zone " + loc)
|
||||
_, err := time.LoadLocation(loc)
|
||||
if err.Error() != want.Error() {
|
||||
t.Errorf("LoadLocation(%q) error = %v; want %v", loc, err, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadLocationValidatesNames(t *testing.T) {
|
||||
time.ResetZoneinfoForTesting()
|
||||
const env = "ZONEINFO"
|
||||
t.Setenv(env, "")
|
||||
|
||||
bad := []string{
|
||||
"/usr/foo/Foo",
|
||||
"\\UNC\foo",
|
||||
"..",
|
||||
"a..",
|
||||
}
|
||||
for _, v := range bad {
|
||||
_, err := time.LoadLocation(v)
|
||||
if err != time.ErrLocation {
|
||||
t.Errorf("LoadLocation(%q) error = %v; want ErrLocation", v, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVersion3(t *testing.T) {
|
||||
undo := time.DisablePlatformSources()
|
||||
defer undo()
|
||||
_, err := time.LoadLocation("Asia/Jerusalem")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that we get the correct results for times before the first
|
||||
// transition time. To do this we explicitly check early dates in a
|
||||
// couple of specific timezones.
|
||||
func TestFirstZone(t *testing.T) {
|
||||
undo := time.DisablePlatformSources()
|
||||
defer undo()
|
||||
|
||||
const format = "Mon, 02 Jan 2006 15:04:05 -0700 (MST)"
|
||||
var tests = []struct {
|
||||
zone string
|
||||
unix int64
|
||||
want1 string
|
||||
want2 string
|
||||
}{
|
||||
{
|
||||
"PST8PDT",
|
||||
-1633269601,
|
||||
"Sun, 31 Mar 1918 01:59:59 -0800 (PST)",
|
||||
"Sun, 31 Mar 1918 03:00:00 -0700 (PDT)",
|
||||
},
|
||||
{
|
||||
"Pacific/Fakaofo",
|
||||
1325242799,
|
||||
"Thu, 29 Dec 2011 23:59:59 -1100 (-11)",
|
||||
"Sat, 31 Dec 2011 00:00:00 +1300 (+13)",
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
z, err := time.LoadLocation(test.zone)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s := time.Unix(test.unix, 0).In(z).Format(format)
|
||||
if s != test.want1 {
|
||||
t.Errorf("for %s %d got %q want %q", test.zone, test.unix, s, test.want1)
|
||||
}
|
||||
s = time.Unix(test.unix+1, 0).In(z).Format(format)
|
||||
if s != test.want2 {
|
||||
t.Errorf("for %s %d got %q want %q", test.zone, test.unix, s, test.want2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestLocationNames(t *testing.T) {
|
||||
if time.Local.String() != "Local" {
|
||||
t.Errorf(`invalid Local location name: got %q want "Local"`, time.Local)
|
||||
}
|
||||
if time.UTC.String() != "UTC" {
|
||||
t.Errorf(`invalid UTC location name: got %q want "UTC"`, time.UTC)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoadLocationFromTZData(t *testing.T) {
|
||||
undo := time.DisablePlatformSources()
|
||||
defer undo()
|
||||
|
||||
const locationName = "Asia/Jerusalem"
|
||||
reference, err := time.LoadLocation(locationName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
gorootSource, ok := time.GorootZoneSource(testenv.GOROOT(t))
|
||||
if !ok {
|
||||
t.Fatal("Failed to locate tzinfo source in GOROOT.")
|
||||
}
|
||||
tzinfo, err := time.LoadTzinfo(locationName, gorootSource)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
sample, err := time.LoadLocationFromTZData(locationName, tzinfo)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(reference, sample) {
|
||||
t.Errorf("return values of LoadLocationFromTZData and LoadLocation don't match")
|
||||
}
|
||||
}
|
||||
|
||||
// Issue 30099.
|
||||
func TestEarlyLocation(t *testing.T) {
|
||||
undo := time.DisablePlatformSources()
|
||||
defer undo()
|
||||
|
||||
const locName = "America/New_York"
|
||||
loc, err := time.LoadLocation(locName)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
d := time.Date(1900, time.January, 1, 0, 0, 0, 0, loc)
|
||||
tzName, tzOffset := d.Zone()
|
||||
if want := "EST"; tzName != want {
|
||||
t.Errorf("Zone name == %s, want %s", tzName, want)
|
||||
}
|
||||
if want := -18000; tzOffset != want {
|
||||
t.Errorf("Zone offset == %d, want %d", tzOffset, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMalformedTZData(t *testing.T) {
|
||||
// The goal here is just that malformed tzdata results in an error, not a panic.
|
||||
issue29437 := "TZif\x00000000000000000\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x0000"
|
||||
_, err := time.LoadLocationFromTZData("abc", []byte(issue29437))
|
||||
if err == nil {
|
||||
t.Error("expected error, got none")
|
||||
}
|
||||
}
|
||||
|
||||
var slimTests = []struct {
|
||||
zoneName string
|
||||
fileName string
|
||||
date func(*time.Location) time.Time
|
||||
wantName string
|
||||
wantOffset int
|
||||
}{
|
||||
{
|
||||
// 2020b slim tzdata for Europe/Berlin.
|
||||
zoneName: "Europe/Berlin",
|
||||
fileName: "2020b_Europe_Berlin",
|
||||
date: func(loc *time.Location) time.Time { return time.Date(2020, time.October, 29, 15, 30, 0, 0, loc) },
|
||||
wantName: "CET",
|
||||
wantOffset: 3600,
|
||||
},
|
||||
{
|
||||
// 2021a slim tzdata for America/Nuuk.
|
||||
zoneName: "America/Nuuk",
|
||||
fileName: "2021a_America_Nuuk",
|
||||
date: func(loc *time.Location) time.Time { return time.Date(2020, time.October, 29, 15, 30, 0, 0, loc) },
|
||||
wantName: "-03",
|
||||
wantOffset: -10800,
|
||||
},
|
||||
{
|
||||
// 2021a slim tzdata for Asia/Gaza.
|
||||
zoneName: "Asia/Gaza",
|
||||
fileName: "2021a_Asia_Gaza",
|
||||
date: func(loc *time.Location) time.Time { return time.Date(2020, time.October, 29, 15, 30, 0, 0, loc) },
|
||||
wantName: "EET",
|
||||
wantOffset: 7200,
|
||||
},
|
||||
{
|
||||
// 2021a slim tzdata for Europe/Dublin.
|
||||
zoneName: "Europe/Dublin",
|
||||
fileName: "2021a_Europe_Dublin",
|
||||
date: func(loc *time.Location) time.Time { return time.Date(2021, time.April, 2, 11, 12, 13, 0, loc) },
|
||||
wantName: "IST",
|
||||
wantOffset: 3600,
|
||||
},
|
||||
}
|
||||
|
||||
func TestLoadLocationFromTZDataSlim(t *testing.T) {
|
||||
for _, test := range slimTests {
|
||||
tzData, err := os.ReadFile("testdata/" + test.fileName)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
reference, err := time.LoadLocationFromTZData(test.zoneName, tzData)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
continue
|
||||
}
|
||||
|
||||
d := test.date(reference)
|
||||
tzName, tzOffset := d.Zone()
|
||||
if tzName != test.wantName {
|
||||
t.Errorf("Zone name == %s, want %s", tzName, test.wantName)
|
||||
}
|
||||
if tzOffset != test.wantOffset {
|
||||
t.Errorf("Zone offset == %d, want %d", tzOffset, test.wantOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTzset(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
inStr string
|
||||
inEnd int64
|
||||
inSec int64
|
||||
name string
|
||||
off int
|
||||
start int64
|
||||
end int64
|
||||
isDST bool
|
||||
ok bool
|
||||
}{
|
||||
{"", 0, 0, "", 0, 0, 0, false, false},
|
||||
{"PST8PDT,M3.2.0,M11.1.0", 0, 2159200800, "PDT", -7 * 60 * 60, 2152173600, 2172733200, true, true},
|
||||
{"PST8PDT,M3.2.0,M11.1.0", 0, 2152173599, "PST", -8 * 60 * 60, 2145916800, 2152173600, false, true},
|
||||
{"PST8PDT,M3.2.0,M11.1.0", 0, 2152173600, "PDT", -7 * 60 * 60, 2152173600, 2172733200, true, true},
|
||||
{"PST8PDT,M3.2.0,M11.1.0", 0, 2152173601, "PDT", -7 * 60 * 60, 2152173600, 2172733200, true, true},
|
||||
{"PST8PDT,M3.2.0,M11.1.0", 0, 2172733199, "PDT", -7 * 60 * 60, 2152173600, 2172733200, true, true},
|
||||
{"PST8PDT,M3.2.0,M11.1.0", 0, 2172733200, "PST", -8 * 60 * 60, 2172733200, 2177452800, false, true},
|
||||
{"PST8PDT,M3.2.0,M11.1.0", 0, 2172733201, "PST", -8 * 60 * 60, 2172733200, 2177452800, false, true},
|
||||
{"KST-9", 592333200, 1677246697, "KST", 9 * 60 * 60, 592333200, 1<<63 - 1, false, true},
|
||||
} {
|
||||
name, off, start, end, isDST, ok := time.Tzset(test.inStr, test.inEnd, test.inSec)
|
||||
if name != test.name || off != test.off || start != test.start || end != test.end || isDST != test.isDST || ok != test.ok {
|
||||
t.Errorf("tzset(%q, %d, %d) = %q, %d, %d, %d, %t, %t, want %q, %d, %d, %d, %t, %t", test.inStr, test.inEnd, test.inSec, name, off, start, end, isDST, ok, test.name, test.off, test.start, test.end, test.isDST, test.ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTzsetName(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in string
|
||||
name string
|
||||
out string
|
||||
ok bool
|
||||
}{
|
||||
{"", "", "", false},
|
||||
{"X", "", "", false},
|
||||
{"PST", "PST", "", true},
|
||||
{"PST8PDT", "PST", "8PDT", true},
|
||||
{"PST-08", "PST", "-08", true},
|
||||
{"<A+B>+08", "A+B", "+08", true},
|
||||
} {
|
||||
name, out, ok := time.TzsetName(test.in)
|
||||
if name != test.name || out != test.out || ok != test.ok {
|
||||
t.Errorf("tzsetName(%q) = %q, %q, %t, want %q, %q, %t", test.in, name, out, ok, test.name, test.out, test.ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTzsetOffset(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in string
|
||||
off int
|
||||
out string
|
||||
ok bool
|
||||
}{
|
||||
{"", 0, "", false},
|
||||
{"X", 0, "", false},
|
||||
{"+", 0, "", false},
|
||||
{"+08", 8 * 60 * 60, "", true},
|
||||
{"-01:02:03", -1*60*60 - 2*60 - 3, "", true},
|
||||
{"01", 1 * 60 * 60, "", true},
|
||||
{"100", 100 * 60 * 60, "", true},
|
||||
{"1000", 0, "", false},
|
||||
{"8PDT", 8 * 60 * 60, "PDT", true},
|
||||
} {
|
||||
off, out, ok := time.TzsetOffset(test.in)
|
||||
if off != test.off || out != test.out || ok != test.ok {
|
||||
t.Errorf("tzsetName(%q) = %d, %q, %t, want %d, %q, %t", test.in, off, out, ok, test.off, test.out, test.ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTzsetRule(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
in string
|
||||
r time.Rule
|
||||
out string
|
||||
ok bool
|
||||
}{
|
||||
{"", time.Rule{}, "", false},
|
||||
{"X", time.Rule{}, "", false},
|
||||
{"J10", time.Rule{Kind: time.RuleJulian, Day: 10, Time: 2 * 60 * 60}, "", true},
|
||||
{"20", time.Rule{Kind: time.RuleDOY, Day: 20, Time: 2 * 60 * 60}, "", true},
|
||||
{"M1.2.3", time.Rule{Kind: time.RuleMonthWeekDay, Mon: 1, Week: 2, Day: 3, Time: 2 * 60 * 60}, "", true},
|
||||
{"30/03:00:00", time.Rule{Kind: time.RuleDOY, Day: 30, Time: 3 * 60 * 60}, "", true},
|
||||
{"M4.5.6/03:00:00", time.Rule{Kind: time.RuleMonthWeekDay, Mon: 4, Week: 5, Day: 6, Time: 3 * 60 * 60}, "", true},
|
||||
{"M4.5.7/03:00:00", time.Rule{}, "", false},
|
||||
{"M4.5.6/-04", time.Rule{Kind: time.RuleMonthWeekDay, Mon: 4, Week: 5, Day: 6, Time: -4 * 60 * 60}, "", true},
|
||||
} {
|
||||
r, out, ok := time.TzsetRule(test.in)
|
||||
if r != test.r || out != test.out || ok != test.ok {
|
||||
t.Errorf("tzsetName(%q) = %#v, %q, %t, want %#v, %q, %t", test.in, r, out, ok, test.r, test.out, test.ok)
|
||||
}
|
||||
}
|
||||
}
|
||||
69
src/time/zoneinfo_unix.go
Normal file
69
src/time/zoneinfo_unix.go
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build unix && !ios && !android
|
||||
|
||||
// Parse "zoneinfo" time zone file.
|
||||
// This is a fairly standard file format used on OS X, Linux, BSD, Sun, and others.
|
||||
// See tzfile(5), https://en.wikipedia.org/wiki/Zoneinfo,
|
||||
// and ftp://munnari.oz.au/pub/oldtz/
|
||||
|
||||
package time
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Many systems use /usr/share/zoneinfo, Solaris 2 has
|
||||
// /usr/share/lib/zoneinfo, IRIX 6 has /usr/lib/locale/TZ,
|
||||
// NixOS has /etc/zoneinfo.
|
||||
var platformZoneSources = []string{
|
||||
"/usr/share/zoneinfo/",
|
||||
"/usr/share/lib/zoneinfo/",
|
||||
"/usr/lib/locale/TZ/",
|
||||
"/etc/zoneinfo",
|
||||
}
|
||||
|
||||
func initLocal() {
|
||||
// consult $TZ to find the time zone to use.
|
||||
// no $TZ means use the system default /etc/localtime.
|
||||
// $TZ="" means use UTC.
|
||||
// $TZ="foo" or $TZ=":foo" if foo is an absolute path, then the file pointed
|
||||
// by foo will be used to initialize timezone; otherwise, file
|
||||
// /usr/share/zoneinfo/foo will be used.
|
||||
|
||||
tz, ok := syscall.Getenv("TZ")
|
||||
switch {
|
||||
case !ok:
|
||||
z, err := loadLocation("localtime", []string{"/etc"})
|
||||
if err == nil {
|
||||
localLoc = *z
|
||||
localLoc.name = "Local"
|
||||
return
|
||||
}
|
||||
case tz != "":
|
||||
if tz[0] == ':' {
|
||||
tz = tz[1:]
|
||||
}
|
||||
if tz != "" && tz[0] == '/' {
|
||||
if z, err := loadLocation(tz, []string{""}); err == nil {
|
||||
localLoc = *z
|
||||
if tz == "/etc/localtime" {
|
||||
localLoc.name = "Local"
|
||||
} else {
|
||||
localLoc.name = tz
|
||||
}
|
||||
return
|
||||
}
|
||||
} else if tz != "" && tz != "UTC" {
|
||||
if z, err := loadLocation(tz, platformZoneSources); err == nil {
|
||||
localLoc = *z
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fall back to UTC.
|
||||
localLoc.name = "UTC"
|
||||
}
|
||||
90
src/time/zoneinfo_unix_test.go
Normal file
90
src/time/zoneinfo_unix_test.go
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright 2020 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build unix && !ios && !android
|
||||
|
||||
package time_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestEnvTZUsage(t *testing.T) {
|
||||
const env = "TZ"
|
||||
tz, ok := os.LookupEnv(env)
|
||||
if !ok {
|
||||
defer os.Unsetenv(env)
|
||||
} else {
|
||||
defer os.Setenv(env, tz)
|
||||
}
|
||||
defer time.ForceUSPacificForTesting()
|
||||
|
||||
localZoneName := "Local"
|
||||
// The file may not exist.
|
||||
if _, err := os.Stat("/etc/localtime"); os.IsNotExist(err) {
|
||||
localZoneName = "UTC"
|
||||
}
|
||||
|
||||
cases := []struct {
|
||||
nilFlag bool
|
||||
tz string
|
||||
local string
|
||||
}{
|
||||
// no $TZ means use the system default /etc/localtime.
|
||||
{true, "", localZoneName},
|
||||
// $TZ="" means use UTC.
|
||||
{false, "", "UTC"},
|
||||
{false, ":", "UTC"},
|
||||
{false, "Asia/Shanghai", "Asia/Shanghai"},
|
||||
{false, ":Asia/Shanghai", "Asia/Shanghai"},
|
||||
{false, "/etc/localtime", localZoneName},
|
||||
{false, ":/etc/localtime", localZoneName},
|
||||
}
|
||||
|
||||
for _, c := range cases {
|
||||
time.ResetLocalOnceForTest()
|
||||
if c.nilFlag {
|
||||
os.Unsetenv(env)
|
||||
} else {
|
||||
os.Setenv(env, c.tz)
|
||||
}
|
||||
if time.Local.String() != c.local {
|
||||
t.Errorf("invalid Local location name for %q: got %q want %q", c.tz, time.Local, c.local)
|
||||
}
|
||||
}
|
||||
|
||||
time.ResetLocalOnceForTest()
|
||||
// The file may not exist on Solaris 2 and IRIX 6.
|
||||
path := "/usr/share/zoneinfo/Asia/Shanghai"
|
||||
os.Setenv(env, path)
|
||||
if _, err := os.Stat(path); os.IsNotExist(err) {
|
||||
if time.Local.String() != "UTC" {
|
||||
t.Errorf(`invalid path should fallback to UTC: got %q want "UTC"`, time.Local)
|
||||
}
|
||||
return
|
||||
}
|
||||
if time.Local.String() != path {
|
||||
t.Errorf(`custom path should lead to path itself: got %q want %q`, time.Local, path)
|
||||
}
|
||||
|
||||
timeInUTC := time.Date(2009, 1, 1, 12, 0, 0, 0, time.UTC)
|
||||
sameTimeInShanghai := time.Date(2009, 1, 1, 20, 0, 0, 0, time.Local)
|
||||
if !timeInUTC.Equal(sameTimeInShanghai) {
|
||||
t.Errorf("invalid timezone: got %q want %q", timeInUTC, sameTimeInShanghai)
|
||||
}
|
||||
|
||||
time.ResetLocalOnceForTest()
|
||||
os.Setenv(env, ":"+path)
|
||||
if time.Local.String() != path {
|
||||
t.Errorf(`custom path should lead to path itself: got %q want %q`, time.Local, path)
|
||||
}
|
||||
|
||||
time.ResetLocalOnceForTest()
|
||||
os.Setenv(env, path[:len(path)-1])
|
||||
if time.Local.String() != "UTC" {
|
||||
t.Errorf(`invalid path should fallback to UTC: got %q want "UTC"`, time.Local)
|
||||
}
|
||||
}
|
||||
12
src/time/zoneinfo_wasip1.go
Normal file
12
src/time/zoneinfo_wasip1.go
Normal file
@@ -0,0 +1,12 @@
|
||||
// Copyright 2023 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package time
|
||||
|
||||
// in wasip1 zoneinfo is managed by the runtime.
|
||||
var platformZoneSources = []string{}
|
||||
|
||||
func initLocal() {
|
||||
localLoc.name = "Local"
|
||||
}
|
||||
237
src/time/zoneinfo_windows.go
Normal file
237
src/time/zoneinfo_windows.go
Normal file
@@ -0,0 +1,237 @@
|
||||
// Copyright 2009 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package time
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"internal/syscall/windows/registry"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
var platformZoneSources []string // none: Windows uses system calls instead
|
||||
|
||||
// TODO(rsc): Fall back to copy of zoneinfo files.
|
||||
|
||||
// BUG(brainman,rsc): On Windows, the operating system does not provide complete
|
||||
// time zone information.
|
||||
// The implementation assumes that this year's rules for daylight savings
|
||||
// time apply to all previous and future years as well.
|
||||
|
||||
// matchZoneKey checks if stdname and dstname match the corresponding key
|
||||
// values "MUI_Std" and MUI_Dlt" or "Std" and "Dlt" in the kname key stored
|
||||
// under the open registry key zones.
|
||||
func matchZoneKey(zones registry.Key, kname string, stdname, dstname string) (matched bool, err2 error) {
|
||||
k, err := registry.OpenKey(zones, kname, registry.READ)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
var std, dlt string
|
||||
// Try MUI_Std and MUI_Dlt first, fallback to Std and Dlt if *any* error occurs
|
||||
std, err = k.GetMUIStringValue("MUI_Std")
|
||||
if err == nil {
|
||||
dlt, err = k.GetMUIStringValue("MUI_Dlt")
|
||||
}
|
||||
if err != nil { // Fallback to Std and Dlt
|
||||
if std, _, err = k.GetStringValue("Std"); err != nil {
|
||||
return false, err
|
||||
}
|
||||
if dlt, _, err = k.GetStringValue("Dlt"); err != nil {
|
||||
return false, err
|
||||
}
|
||||
}
|
||||
|
||||
if std != stdname {
|
||||
return false, nil
|
||||
}
|
||||
if dlt != dstname && dstname != stdname {
|
||||
return false, nil
|
||||
}
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// toEnglishName searches the registry for an English name of a time zone
|
||||
// whose zone names are stdname and dstname and returns the English name.
|
||||
func toEnglishName(stdname, dstname string) (string, error) {
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones`, registry.ENUMERATE_SUB_KEYS|registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
names, err := k.ReadSubKeyNames()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, name := range names {
|
||||
matched, err := matchZoneKey(k, name, stdname, dstname)
|
||||
if err == nil && matched {
|
||||
return name, nil
|
||||
}
|
||||
}
|
||||
return "", errors.New(`English name for time zone "` + stdname + `" not found in registry`)
|
||||
}
|
||||
|
||||
// extractCAPS extracts capital letters from description desc.
|
||||
func extractCAPS(desc string) string {
|
||||
var short []rune
|
||||
for _, c := range desc {
|
||||
if 'A' <= c && c <= 'Z' {
|
||||
short = append(short, c)
|
||||
}
|
||||
}
|
||||
return string(short)
|
||||
}
|
||||
|
||||
// abbrev returns the abbreviations to use for the given zone z.
|
||||
func abbrev(z *syscall.Timezoneinformation) (std, dst string) {
|
||||
stdName := syscall.UTF16ToString(z.StandardName[:])
|
||||
a, ok := abbrs[stdName]
|
||||
if !ok {
|
||||
dstName := syscall.UTF16ToString(z.DaylightName[:])
|
||||
// Perhaps stdName is not English. Try to convert it.
|
||||
englishName, err := toEnglishName(stdName, dstName)
|
||||
if err == nil {
|
||||
a, ok = abbrs[englishName]
|
||||
if ok {
|
||||
return a.std, a.dst
|
||||
}
|
||||
}
|
||||
// fallback to using capital letters
|
||||
return extractCAPS(stdName), extractCAPS(dstName)
|
||||
}
|
||||
return a.std, a.dst
|
||||
}
|
||||
|
||||
// pseudoUnix returns the pseudo-Unix time (seconds since Jan 1 1970 *LOCAL TIME*)
|
||||
// denoted by the system date+time d in the given year.
|
||||
// It is up to the caller to convert this local time into a UTC-based time.
|
||||
func pseudoUnix(year int, d *syscall.Systemtime) int64 {
|
||||
// Windows specifies daylight savings information in "day in month" format:
|
||||
// d.Month is month number (1-12)
|
||||
// d.DayOfWeek is appropriate weekday (Sunday=0 to Saturday=6)
|
||||
// d.Day is week within the month (1 to 5, where 5 is last week of the month)
|
||||
// d.Hour, d.Minute and d.Second are absolute time
|
||||
day := 1
|
||||
t := Date(year, Month(d.Month), day, int(d.Hour), int(d.Minute), int(d.Second), 0, UTC)
|
||||
i := int(d.DayOfWeek) - int(t.Weekday())
|
||||
if i < 0 {
|
||||
i += 7
|
||||
}
|
||||
day += i
|
||||
if week := int(d.Day) - 1; week < 4 {
|
||||
day += week * 7
|
||||
} else {
|
||||
// "Last" instance of the day.
|
||||
day += 4 * 7
|
||||
if day > daysIn(Month(d.Month), year) {
|
||||
day -= 7
|
||||
}
|
||||
}
|
||||
return t.sec() + int64(day-1)*secondsPerDay + internalToUnix
|
||||
}
|
||||
|
||||
func initLocalFromTZI(i *syscall.Timezoneinformation) {
|
||||
l := &localLoc
|
||||
|
||||
l.name = "Local"
|
||||
|
||||
nzone := 1
|
||||
if i.StandardDate.Month > 0 {
|
||||
nzone++
|
||||
}
|
||||
l.zone = make([]zone, nzone)
|
||||
|
||||
stdname, dstname := abbrev(i)
|
||||
|
||||
std := &l.zone[0]
|
||||
std.name = stdname
|
||||
if nzone == 1 {
|
||||
// No daylight savings.
|
||||
std.offset = -int(i.Bias) * 60
|
||||
l.cacheStart = alpha
|
||||
l.cacheEnd = omega
|
||||
l.cacheZone = std
|
||||
l.tx = make([]zoneTrans, 1)
|
||||
l.tx[0].when = l.cacheStart
|
||||
l.tx[0].index = 0
|
||||
return
|
||||
}
|
||||
|
||||
// StandardBias must be ignored if StandardDate is not set,
|
||||
// so this computation is delayed until after the nzone==1
|
||||
// return above.
|
||||
std.offset = -int(i.Bias+i.StandardBias) * 60
|
||||
|
||||
dst := &l.zone[1]
|
||||
dst.name = dstname
|
||||
dst.offset = -int(i.Bias+i.DaylightBias) * 60
|
||||
dst.isDST = true
|
||||
|
||||
// Arrange so that d0 is first transition date, d1 second,
|
||||
// i0 is index of zone after first transition, i1 second.
|
||||
d0 := &i.StandardDate
|
||||
d1 := &i.DaylightDate
|
||||
i0 := 0
|
||||
i1 := 1
|
||||
if d0.Month > d1.Month {
|
||||
d0, d1 = d1, d0
|
||||
i0, i1 = i1, i0
|
||||
}
|
||||
|
||||
// 2 tx per year, 100 years on each side of this year
|
||||
l.tx = make([]zoneTrans, 400)
|
||||
|
||||
t := Now().UTC()
|
||||
year := t.Year()
|
||||
txi := 0
|
||||
for y := year - 100; y < year+100; y++ {
|
||||
tx := &l.tx[txi]
|
||||
tx.when = pseudoUnix(y, d0) - int64(l.zone[i1].offset)
|
||||
tx.index = uint8(i0)
|
||||
txi++
|
||||
|
||||
tx = &l.tx[txi]
|
||||
tx.when = pseudoUnix(y, d1) - int64(l.zone[i0].offset)
|
||||
tx.index = uint8(i1)
|
||||
txi++
|
||||
}
|
||||
}
|
||||
|
||||
var usPacific = syscall.Timezoneinformation{
|
||||
Bias: 8 * 60,
|
||||
StandardName: [32]uint16{
|
||||
'P', 'a', 'c', 'i', 'f', 'i', 'c', ' ', 'S', 't', 'a', 'n', 'd', 'a', 'r', 'd', ' ', 'T', 'i', 'm', 'e',
|
||||
},
|
||||
StandardDate: syscall.Systemtime{Month: 11, Day: 1, Hour: 2},
|
||||
DaylightName: [32]uint16{
|
||||
'P', 'a', 'c', 'i', 'f', 'i', 'c', ' ', 'D', 'a', 'y', 'l', 'i', 'g', 'h', 't', ' ', 'T', 'i', 'm', 'e',
|
||||
},
|
||||
DaylightDate: syscall.Systemtime{Month: 3, Day: 2, Hour: 2},
|
||||
DaylightBias: -60,
|
||||
}
|
||||
|
||||
var aus = syscall.Timezoneinformation{
|
||||
Bias: -10 * 60,
|
||||
StandardName: [32]uint16{
|
||||
'A', 'U', 'S', ' ', 'E', 'a', 's', 't', 'e', 'r', 'n', ' ', 'S', 't', 'a', 'n', 'd', 'a', 'r', 'd', ' ', 'T', 'i', 'm', 'e',
|
||||
},
|
||||
StandardDate: syscall.Systemtime{Month: 4, Day: 1, Hour: 3},
|
||||
DaylightName: [32]uint16{
|
||||
'A', 'U', 'S', ' ', 'E', 'a', 's', 't', 'e', 'r', 'n', ' ', 'D', 'a', 'y', 'l', 'i', 'g', 'h', 't', ' ', 'T', 'i', 'm', 'e',
|
||||
},
|
||||
DaylightDate: syscall.Systemtime{Month: 10, Day: 1, Hour: 2},
|
||||
DaylightBias: -60,
|
||||
}
|
||||
|
||||
func initLocal() {
|
||||
var i syscall.Timezoneinformation
|
||||
if _, err := syscall.GetTimeZoneInformation(&i); err != nil {
|
||||
localLoc.name = "UTC"
|
||||
return
|
||||
}
|
||||
initLocalFromTZI(&i)
|
||||
}
|
||||
69
src/time/zoneinfo_windows_test.go
Normal file
69
src/time/zoneinfo_windows_test.go
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package time_test
|
||||
|
||||
import (
|
||||
"internal/syscall/windows/registry"
|
||||
"testing"
|
||||
. "time"
|
||||
)
|
||||
|
||||
func testZoneAbbr(t *testing.T) {
|
||||
t1 := Now()
|
||||
// discard nsec
|
||||
t1 = Date(t1.Year(), t1.Month(), t1.Day(), t1.Hour(), t1.Minute(), t1.Second(), 0, t1.Location())
|
||||
|
||||
t2, err := Parse(RFC1123, t1.Format(RFC1123))
|
||||
if err != nil {
|
||||
t.Fatalf("Parse failed: %v", err)
|
||||
}
|
||||
if t1 != t2 {
|
||||
t.Fatalf("t1 (%v) is not equal to t2 (%v)", t1, t2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUSPacificZoneAbbr(t *testing.T) {
|
||||
ForceUSPacificFromTZIForTesting() // reset the Once to trigger the race
|
||||
defer ForceUSPacificForTesting()
|
||||
testZoneAbbr(t)
|
||||
}
|
||||
|
||||
func TestAusZoneAbbr(t *testing.T) {
|
||||
ForceAusFromTZIForTesting()
|
||||
defer ForceUSPacificForTesting()
|
||||
testZoneAbbr(t)
|
||||
}
|
||||
|
||||
func TestToEnglishName(t *testing.T) {
|
||||
const want = "Central Europe Standard Time"
|
||||
k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones\`+want, registry.READ)
|
||||
if err != nil {
|
||||
t.Fatalf("cannot open CEST time zone information from registry: %s", err)
|
||||
}
|
||||
defer k.Close()
|
||||
|
||||
var std, dlt string
|
||||
// Try MUI_Std and MUI_Dlt first, fallback to Std and Dlt if *any* error occurs
|
||||
std, err = k.GetMUIStringValue("MUI_Std")
|
||||
if err == nil {
|
||||
dlt, err = k.GetMUIStringValue("MUI_Dlt")
|
||||
}
|
||||
if err != nil { // Fallback to Std and Dlt
|
||||
if std, _, err = k.GetStringValue("Std"); err != nil {
|
||||
t.Fatalf("cannot read CEST Std registry key: %s", err)
|
||||
}
|
||||
if dlt, _, err = k.GetStringValue("Dlt"); err != nil {
|
||||
t.Fatalf("cannot read CEST Dlt registry key: %s", err)
|
||||
}
|
||||
}
|
||||
|
||||
name, err := ToEnglishName(std, dlt)
|
||||
if err != nil {
|
||||
t.Fatalf("toEnglishName failed: %s", err)
|
||||
}
|
||||
if name != want {
|
||||
t.Fatalf("english name: %q, want: %q", name, want)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user