Initial commit: Go 1.23 release state

This commit is contained in:
Vorapol Rinsatitnon
2024-09-21 23:49:08 +10:00
commit 17cd57a668
13231 changed files with 3114330 additions and 0 deletions

12
src/time/embed.go Normal file
View 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
View 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
}

View 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
View 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]

View 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

File diff suppressed because it is too large Load Diff

188
src/time/format_rfc3339.go Normal file
View 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

File diff suppressed because it is too large Load Diff

157
src/time/genzabbrs.go Normal file
View 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

Binary file not shown.

BIN
src/time/testdata/2021a_America_Nuuk vendored Normal file

Binary file not shown.

BIN
src/time/testdata/2021a_Asia_Gaza vendored Normal file

Binary file not shown.

BIN
src/time/testdata/2021a_Europe_Dublin vendored Normal file

Binary file not shown.

91
src/time/tick.go Normal file
View 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
View 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

File diff suppressed because it is too large Load Diff

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
View 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
View 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
View 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
}

View 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
}

View 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
}

View 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)
}
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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"
}

View 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)
}
}

View 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"
}

View 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)
}

View 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)
}
}