summaryrefslogtreecommitdiffstats
path: root/src/time
diff options
context:
space:
mode:
Diffstat (limited to 'src/time')
-rw-r--r--src/time/embed.go12
-rw-r--r--src/time/example_test.go739
-rw-r--r--src/time/export_android_test.go16
-rw-r--r--src/time/export_test.go140
-rw-r--r--src/time/export_windows_test.go19
-rw-r--r--src/time/format.go1686
-rw-r--r--src/time/format_rfc3339.go188
-rw-r--r--src/time/format_test.go1066
-rw-r--r--src/time/genzabbrs.go156
-rw-r--r--src/time/internal_test.go72
-rw-r--r--src/time/mono_test.go278
-rw-r--r--src/time/sleep.go178
-rw-r--r--src/time/sleep_test.go821
-rw-r--r--src/time/sys_plan9.go54
-rw-r--r--src/time/sys_unix.go62
-rw-r--r--src/time/sys_windows.go55
-rw-r--r--src/time/testdata/2020b_Europe_Berlinbin0 -> 705 bytes
-rw-r--r--src/time/testdata/2021a_America_Nuukbin0 -> 465 bytes
-rw-r--r--src/time/testdata/2021a_Asia_Gazabin0 -> 1213 bytes
-rw-r--r--src/time/testdata/2021a_Europe_Dublinbin0 -> 1496 bytes
-rw-r--r--src/time/tick.go71
-rw-r--r--src/time/tick_test.go177
-rw-r--r--src/time/time.go1667
-rw-r--r--src/time/time_test.go1891
-rw-r--r--src/time/tzdata/tzdata.go110
-rw-r--r--src/time/tzdata_test.go99
-rw-r--r--src/time/zoneinfo.go712
-rw-r--r--src/time/zoneinfo_abbrs_windows.go155
-rw-r--r--src/time/zoneinfo_android.go87
-rw-r--r--src/time/zoneinfo_android_test.go18
-rw-r--r--src/time/zoneinfo_goroot.go14
-rw-r--r--src/time/zoneinfo_ios.go45
-rw-r--r--src/time/zoneinfo_js.go45
-rw-r--r--src/time/zoneinfo_plan9.go139
-rw-r--r--src/time/zoneinfo_read.go597
-rw-r--r--src/time/zoneinfo_test.go350
-rw-r--r--src/time/zoneinfo_unix.go69
-rw-r--r--src/time/zoneinfo_unix_test.go90
-rw-r--r--src/time/zoneinfo_wasip1.go12
-rw-r--r--src/time/zoneinfo_windows.go237
-rw-r--r--src/time/zoneinfo_windows_test.go69
41 files changed, 12196 insertions, 0 deletions
diff --git a/src/time/embed.go b/src/time/embed.go
new file mode 100644
index 0000000..2a9821b
--- /dev/null
+++ b/src/time/embed.go
@@ -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"
diff --git a/src/time/example_test.go b/src/time/example_test.go
new file mode 100644
index 0000000..cfdee8f
--- /dev/null
+++ b/src/time/example_test.go
@@ -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(), micro)
+ // 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
+}
diff --git a/src/time/export_android_test.go b/src/time/export_android_test.go
new file mode 100644
index 0000000..17e0219
--- /dev/null
+++ b/src/time/export_android_test.go
@@ -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
+ }
+}
diff --git a/src/time/export_test.go b/src/time/export_test.go
new file mode 100644
index 0000000..a4940d1
--- /dev/null
+++ b/src/time/export_test.go
@@ -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]
diff --git a/src/time/export_windows_test.go b/src/time/export_windows_test.go
new file mode 100644
index 0000000..cc9d6dd
--- /dev/null
+++ b/src/time/export_windows_test.go
@@ -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)
+}
diff --git a/src/time/format.go b/src/time/format.go
new file mode 100644
index 0000000..7fbeddb
--- /dev/null
+++ b/src/time/format.go
@@ -0,0 +1,1686 @@
+// Copyright 2010 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"
+
+// These are predefined layouts for use in Time.Format and time.Parse.
+// The reference time used in these layouts is the specific time stamp:
+//
+// 01/02 03:04:05PM '06 -0700
+//
+// (January 2, 15:04:05, 2006, in time zone seven hours west of GMT).
+// That value is recorded as the constant named Layout, listed below. As a Unix
+// time, this is 1136239445. Since MST is GMT-0700, the reference would be
+// printed by the Unix date command as:
+//
+// Mon Jan 2 15:04:05 MST 2006
+//
+// It is a regrettable historic error that the date uses the American convention
+// of putting the numerical month before the day.
+//
+// The example for Time.Format demonstrates the working of the layout string
+// in detail and is a good reference.
+//
+// Note that the RFC822, RFC850, and RFC1123 formats should be applied
+// only to local times. Applying them to UTC times will use "UTC" as the
+// time zone abbreviation, while strictly speaking those RFCs require the
+// use of "GMT" in that case.
+// In general RFC1123Z should be used instead of RFC1123 for servers
+// that insist on that format, and RFC3339 should be preferred for new protocols.
+// RFC3339, RFC822, RFC822Z, RFC1123, and RFC1123Z are useful for formatting;
+// when used with time.Parse they do not accept all the time formats
+// permitted by the RFCs and they do accept time formats not formally defined.
+// The RFC3339Nano format removes trailing zeros from the seconds field
+// and thus may not sort correctly once formatted.
+//
+// Most programs can use one of the defined constants as the layout passed to
+// Format or Parse. The rest of this comment can be ignored unless you are
+// creating a custom layout string.
+//
+// To define your own format, write down what the reference time would look like
+// formatted your way; see the values of constants like ANSIC, StampMicro or
+// Kitchen for examples. The model is to demonstrate what the reference time
+// looks like so that the Format and Parse methods can apply the same
+// transformation to a general time value.
+//
+// Here is a summary of the components of a layout string. Each element shows by
+// example the formatting of an element of the reference time. Only these values
+// are recognized. Text in the layout string that is not recognized as part of
+// the reference time is echoed verbatim during Format and expected to appear
+// verbatim in the input to Parse.
+//
+// Year: "2006" "06"
+// Month: "Jan" "January" "01" "1"
+// Day of the week: "Mon" "Monday"
+// Day of the month: "2" "_2" "02"
+// Day of the year: "__2" "002"
+// Hour: "15" "3" "03" (PM or AM)
+// Minute: "4" "04"
+// Second: "5" "05"
+// AM/PM mark: "PM"
+//
+// Numeric time zone offsets format as follows:
+//
+// "-0700" ±hhmm
+// "-07:00" ±hh:mm
+// "-07" ±hh
+// "-070000" ±hhmmss
+// "-07:00:00" ±hh:mm:ss
+//
+// Replacing the sign in the format with a Z triggers
+// the ISO 8601 behavior of printing Z instead of an
+// offset for the UTC zone. Thus:
+//
+// "Z0700" Z or ±hhmm
+// "Z07:00" Z or ±hh:mm
+// "Z07" Z or ±hh
+// "Z070000" Z or ±hhmmss
+// "Z07:00:00" Z or ±hh:mm:ss
+//
+// Within the format string, the underscores in "_2" and "__2" represent spaces
+// that may be replaced by digits if the following number has multiple digits,
+// for compatibility with fixed-width Unix time formats. A leading zero represents
+// a zero-padded value.
+//
+// The formats __2 and 002 are space-padded and zero-padded
+// three-character day of year; there is no unpadded day of year format.
+//
+// A comma or decimal point followed by one or more zeros represents
+// a fractional second, printed to the given number of decimal places.
+// A comma or decimal point followed by one or more nines represents
+// a fractional second, printed to the given number of decimal places, with
+// trailing zeros removed.
+// For example "15:04:05,000" or "15:04:05.000" formats or parses with
+// millisecond precision.
+//
+// Some valid layouts are invalid time values for time.Parse, due to formats
+// such as _ for space padding and Z for zone information.
+const (
+ Layout = "01/02 03:04:05PM '06 -0700" // The reference time, in numerical order.
+ ANSIC = "Mon Jan _2 15:04:05 2006"
+ UnixDate = "Mon Jan _2 15:04:05 MST 2006"
+ RubyDate = "Mon Jan 02 15:04:05 -0700 2006"
+ RFC822 = "02 Jan 06 15:04 MST"
+ RFC822Z = "02 Jan 06 15:04 -0700" // RFC822 with numeric zone
+ RFC850 = "Monday, 02-Jan-06 15:04:05 MST"
+ RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
+ RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
+ RFC3339 = "2006-01-02T15:04:05Z07:00"
+ RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
+ Kitchen = "3:04PM"
+ // Handy time stamps.
+ Stamp = "Jan _2 15:04:05"
+ StampMilli = "Jan _2 15:04:05.000"
+ StampMicro = "Jan _2 15:04:05.000000"
+ StampNano = "Jan _2 15:04:05.000000000"
+ DateTime = "2006-01-02 15:04:05"
+ DateOnly = "2006-01-02"
+ TimeOnly = "15:04:05"
+)
+
+const (
+ _ = iota
+ stdLongMonth = iota + stdNeedDate // "January"
+ stdMonth // "Jan"
+ stdNumMonth // "1"
+ stdZeroMonth // "01"
+ stdLongWeekDay // "Monday"
+ stdWeekDay // "Mon"
+ stdDay // "2"
+ stdUnderDay // "_2"
+ stdZeroDay // "02"
+ stdUnderYearDay // "__2"
+ stdZeroYearDay // "002"
+ stdHour = iota + stdNeedClock // "15"
+ stdHour12 // "3"
+ stdZeroHour12 // "03"
+ stdMinute // "4"
+ stdZeroMinute // "04"
+ stdSecond // "5"
+ stdZeroSecond // "05"
+ stdLongYear = iota + stdNeedDate // "2006"
+ stdYear // "06"
+ stdPM = iota + stdNeedClock // "PM"
+ stdpm // "pm"
+ stdTZ = iota // "MST"
+ stdISO8601TZ // "Z0700" // prints Z for UTC
+ stdISO8601SecondsTZ // "Z070000"
+ stdISO8601ShortTZ // "Z07"
+ stdISO8601ColonTZ // "Z07:00" // prints Z for UTC
+ stdISO8601ColonSecondsTZ // "Z07:00:00"
+ stdNumTZ // "-0700" // always numeric
+ stdNumSecondsTz // "-070000"
+ stdNumShortTZ // "-07" // always numeric
+ stdNumColonTZ // "-07:00" // always numeric
+ stdNumColonSecondsTZ // "-07:00:00"
+ stdFracSecond0 // ".0", ".00", ... , trailing zeros included
+ stdFracSecond9 // ".9", ".99", ..., trailing zeros omitted
+
+ stdNeedDate = 1 << 8 // need month, day, year
+ stdNeedClock = 2 << 8 // need hour, minute, second
+ stdArgShift = 16 // extra argument in high bits, above low stdArgShift
+ stdSeparatorShift = 28 // extra argument in high 4 bits for fractional second separators
+ stdMask = 1<<stdArgShift - 1 // mask out argument
+)
+
+// std0x records the std values for "01", "02", ..., "06".
+var std0x = [...]int{stdZeroMonth, stdZeroDay, stdZeroHour12, stdZeroMinute, stdZeroSecond, stdYear}
+
+// startsWithLowerCase reports whether the string has a lower-case letter at the beginning.
+// Its purpose is to prevent matching strings like "Month" when looking for "Mon".
+func startsWithLowerCase(str string) bool {
+ if len(str) == 0 {
+ return false
+ }
+ c := str[0]
+ return 'a' <= c && c <= 'z'
+}
+
+// nextStdChunk finds the first occurrence of a std string in
+// layout and returns the text before, the std string, and the text after.
+func nextStdChunk(layout string) (prefix string, std int, suffix string) {
+ for i := 0; i < len(layout); i++ {
+ switch c := int(layout[i]); c {
+ case 'J': // January, Jan
+ if len(layout) >= i+3 && layout[i:i+3] == "Jan" {
+ if len(layout) >= i+7 && layout[i:i+7] == "January" {
+ return layout[0:i], stdLongMonth, layout[i+7:]
+ }
+ if !startsWithLowerCase(layout[i+3:]) {
+ return layout[0:i], stdMonth, layout[i+3:]
+ }
+ }
+
+ case 'M': // Monday, Mon, MST
+ if len(layout) >= i+3 {
+ if layout[i:i+3] == "Mon" {
+ if len(layout) >= i+6 && layout[i:i+6] == "Monday" {
+ return layout[0:i], stdLongWeekDay, layout[i+6:]
+ }
+ if !startsWithLowerCase(layout[i+3:]) {
+ return layout[0:i], stdWeekDay, layout[i+3:]
+ }
+ }
+ if layout[i:i+3] == "MST" {
+ return layout[0:i], stdTZ, layout[i+3:]
+ }
+ }
+
+ case '0': // 01, 02, 03, 04, 05, 06, 002
+ if len(layout) >= i+2 && '1' <= layout[i+1] && layout[i+1] <= '6' {
+ return layout[0:i], std0x[layout[i+1]-'1'], layout[i+2:]
+ }
+ if len(layout) >= i+3 && layout[i+1] == '0' && layout[i+2] == '2' {
+ return layout[0:i], stdZeroYearDay, layout[i+3:]
+ }
+
+ case '1': // 15, 1
+ if len(layout) >= i+2 && layout[i+1] == '5' {
+ return layout[0:i], stdHour, layout[i+2:]
+ }
+ return layout[0:i], stdNumMonth, layout[i+1:]
+
+ case '2': // 2006, 2
+ if len(layout) >= i+4 && layout[i:i+4] == "2006" {
+ return layout[0:i], stdLongYear, layout[i+4:]
+ }
+ return layout[0:i], stdDay, layout[i+1:]
+
+ case '_': // _2, _2006, __2
+ if len(layout) >= i+2 && layout[i+1] == '2' {
+ //_2006 is really a literal _, followed by stdLongYear
+ if len(layout) >= i+5 && layout[i+1:i+5] == "2006" {
+ return layout[0 : i+1], stdLongYear, layout[i+5:]
+ }
+ return layout[0:i], stdUnderDay, layout[i+2:]
+ }
+ if len(layout) >= i+3 && layout[i+1] == '_' && layout[i+2] == '2' {
+ return layout[0:i], stdUnderYearDay, layout[i+3:]
+ }
+
+ case '3':
+ return layout[0:i], stdHour12, layout[i+1:]
+
+ case '4':
+ return layout[0:i], stdMinute, layout[i+1:]
+
+ case '5':
+ return layout[0:i], stdSecond, layout[i+1:]
+
+ case 'P': // PM
+ if len(layout) >= i+2 && layout[i+1] == 'M' {
+ return layout[0:i], stdPM, layout[i+2:]
+ }
+
+ case 'p': // pm
+ if len(layout) >= i+2 && layout[i+1] == 'm' {
+ return layout[0:i], stdpm, layout[i+2:]
+ }
+
+ case '-': // -070000, -07:00:00, -0700, -07:00, -07
+ if len(layout) >= i+7 && layout[i:i+7] == "-070000" {
+ return layout[0:i], stdNumSecondsTz, layout[i+7:]
+ }
+ if len(layout) >= i+9 && layout[i:i+9] == "-07:00:00" {
+ return layout[0:i], stdNumColonSecondsTZ, layout[i+9:]
+ }
+ if len(layout) >= i+5 && layout[i:i+5] == "-0700" {
+ return layout[0:i], stdNumTZ, layout[i+5:]
+ }
+ if len(layout) >= i+6 && layout[i:i+6] == "-07:00" {
+ return layout[0:i], stdNumColonTZ, layout[i+6:]
+ }
+ if len(layout) >= i+3 && layout[i:i+3] == "-07" {
+ return layout[0:i], stdNumShortTZ, layout[i+3:]
+ }
+
+ case 'Z': // Z070000, Z07:00:00, Z0700, Z07:00,
+ if len(layout) >= i+7 && layout[i:i+7] == "Z070000" {
+ return layout[0:i], stdISO8601SecondsTZ, layout[i+7:]
+ }
+ if len(layout) >= i+9 && layout[i:i+9] == "Z07:00:00" {
+ return layout[0:i], stdISO8601ColonSecondsTZ, layout[i+9:]
+ }
+ if len(layout) >= i+5 && layout[i:i+5] == "Z0700" {
+ return layout[0:i], stdISO8601TZ, layout[i+5:]
+ }
+ if len(layout) >= i+6 && layout[i:i+6] == "Z07:00" {
+ return layout[0:i], stdISO8601ColonTZ, layout[i+6:]
+ }
+ if len(layout) >= i+3 && layout[i:i+3] == "Z07" {
+ return layout[0:i], stdISO8601ShortTZ, layout[i+3:]
+ }
+
+ case '.', ',': // ,000, or .000, or ,999, or .999 - repeated digits for fractional seconds.
+ if i+1 < len(layout) && (layout[i+1] == '0' || layout[i+1] == '9') {
+ ch := layout[i+1]
+ j := i + 1
+ for j < len(layout) && layout[j] == ch {
+ j++
+ }
+ // String of digits must end here - only fractional second is all digits.
+ if !isDigit(layout, j) {
+ code := stdFracSecond0
+ if layout[i+1] == '9' {
+ code = stdFracSecond9
+ }
+ std := stdFracSecond(code, j-(i+1), c)
+ return layout[0:i], std, layout[j:]
+ }
+ }
+ }
+ }
+ return layout, 0, ""
+}
+
+var longDayNames = []string{
+ "Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+}
+
+var shortDayNames = []string{
+ "Sun",
+ "Mon",
+ "Tue",
+ "Wed",
+ "Thu",
+ "Fri",
+ "Sat",
+}
+
+var shortMonthNames = []string{
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+}
+
+var longMonthNames = []string{
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December",
+}
+
+// match reports whether s1 and s2 match ignoring case.
+// It is assumed s1 and s2 are the same length.
+func match(s1, s2 string) bool {
+ for i := 0; i < len(s1); i++ {
+ c1 := s1[i]
+ c2 := s2[i]
+ if c1 != c2 {
+ // Switch to lower-case; 'a'-'A' is known to be a single bit.
+ c1 |= 'a' - 'A'
+ c2 |= 'a' - 'A'
+ if c1 != c2 || c1 < 'a' || c1 > 'z' {
+ return false
+ }
+ }
+ }
+ return true
+}
+
+func lookup(tab []string, val string) (int, string, error) {
+ for i, v := range tab {
+ if len(val) >= len(v) && match(val[0:len(v)], v) {
+ return i, val[len(v):], nil
+ }
+ }
+ return -1, val, errBad
+}
+
+// appendInt appends the decimal form of x to b and returns the result.
+// If the decimal form (excluding sign) is shorter than width, the result is padded with leading 0's.
+// Duplicates functionality in strconv, but avoids dependency.
+func appendInt(b []byte, x int, width int) []byte {
+ u := uint(x)
+ if x < 0 {
+ b = append(b, '-')
+ u = uint(-x)
+ }
+
+ // 2-digit and 4-digit fields are the most common in time formats.
+ utod := func(u uint) byte { return '0' + byte(u) }
+ switch {
+ case width == 2 && u < 1e2:
+ return append(b, utod(u/1e1), utod(u%1e1))
+ case width == 4 && u < 1e4:
+ return append(b, utod(u/1e3), utod(u/1e2%1e1), utod(u/1e1%1e1), utod(u%1e1))
+ }
+
+ // Compute the number of decimal digits.
+ var n int
+ if u == 0 {
+ n = 1
+ }
+ for u2 := u; u2 > 0; u2 /= 10 {
+ n++
+ }
+
+ // Add 0-padding.
+ for pad := width - n; pad > 0; pad-- {
+ b = append(b, '0')
+ }
+
+ // Ensure capacity.
+ if len(b)+n <= cap(b) {
+ b = b[:len(b)+n]
+ } else {
+ b = append(b, make([]byte, n)...)
+ }
+
+ // Assemble decimal in reverse order.
+ i := len(b) - 1
+ for u >= 10 && i > 0 {
+ q := u / 10
+ b[i] = utod(u - q*10)
+ u = q
+ i--
+ }
+ b[i] = utod(u)
+ return b
+}
+
+// Never printed, just needs to be non-nil for return by atoi.
+var errAtoi = errors.New("time: invalid number")
+
+// Duplicates functionality in strconv, but avoids dependency.
+func atoi[bytes []byte | string](s bytes) (x int, err error) {
+ neg := false
+ if len(s) > 0 && (s[0] == '-' || s[0] == '+') {
+ neg = s[0] == '-'
+ s = s[1:]
+ }
+ q, rem, err := leadingInt(s)
+ x = int(q)
+ if err != nil || len(rem) > 0 {
+ return 0, errAtoi
+ }
+ if neg {
+ x = -x
+ }
+ return x, nil
+}
+
+// The "std" value passed to appendNano contains two packed fields: the number of
+// digits after the decimal and the separator character (period or comma).
+// These functions pack and unpack that variable.
+func stdFracSecond(code, n, c int) int {
+ // Use 0xfff to make the failure case even more absurd.
+ if c == '.' {
+ return code | ((n & 0xfff) << stdArgShift)
+ }
+ return code | ((n & 0xfff) << stdArgShift) | 1<<stdSeparatorShift
+}
+
+func digitsLen(std int) int {
+ return (std >> stdArgShift) & 0xfff
+}
+
+func separator(std int) byte {
+ if (std >> stdSeparatorShift) == 0 {
+ return '.'
+ }
+ return ','
+}
+
+// appendNano appends a fractional second, as nanoseconds, to b
+// and returns the result. The nanosec must be within [0, 999999999].
+func appendNano(b []byte, nanosec int, std int) []byte {
+ trim := std&stdMask == stdFracSecond9
+ n := digitsLen(std)
+ if trim && (n == 0 || nanosec == 0) {
+ return b
+ }
+ dot := separator(std)
+ b = append(b, dot)
+ b = appendInt(b, nanosec, 9)
+ if n < 9 {
+ b = b[:len(b)-9+n]
+ }
+ if trim {
+ for len(b) > 0 && b[len(b)-1] == '0' {
+ b = b[:len(b)-1]
+ }
+ if len(b) > 0 && b[len(b)-1] == dot {
+ b = b[:len(b)-1]
+ }
+ }
+ return b
+}
+
+// String returns the time formatted using the format string
+//
+// "2006-01-02 15:04:05.999999999 -0700 MST"
+//
+// If the time has a monotonic clock reading, the returned string
+// includes a final field "m=±<value>", where value is the monotonic
+// clock reading formatted as a decimal number of seconds.
+//
+// The returned string is meant for debugging; for a stable serialized
+// representation, use t.MarshalText, t.MarshalBinary, or t.Format
+// with an explicit format string.
+func (t Time) String() string {
+ s := t.Format("2006-01-02 15:04:05.999999999 -0700 MST")
+
+ // Format monotonic clock reading as m=±ddd.nnnnnnnnn.
+ if t.wall&hasMonotonic != 0 {
+ m2 := uint64(t.ext)
+ sign := byte('+')
+ if t.ext < 0 {
+ sign = '-'
+ m2 = -m2
+ }
+ m1, m2 := m2/1e9, m2%1e9
+ m0, m1 := m1/1e9, m1%1e9
+ buf := make([]byte, 0, 24)
+ buf = append(buf, " m="...)
+ buf = append(buf, sign)
+ wid := 0
+ if m0 != 0 {
+ buf = appendInt(buf, int(m0), 0)
+ wid = 9
+ }
+ buf = appendInt(buf, int(m1), wid)
+ buf = append(buf, '.')
+ buf = appendInt(buf, int(m2), 9)
+ s += string(buf)
+ }
+ return s
+}
+
+// GoString implements fmt.GoStringer and formats t to be printed in Go source
+// code.
+func (t Time) GoString() string {
+ abs := t.abs()
+ year, month, day, _ := absDate(abs, true)
+ hour, minute, second := absClock(abs)
+
+ buf := make([]byte, 0, len("time.Date(9999, time.September, 31, 23, 59, 59, 999999999, time.Local)"))
+ buf = append(buf, "time.Date("...)
+ buf = appendInt(buf, year, 0)
+ if January <= month && month <= December {
+ buf = append(buf, ", time."...)
+ buf = append(buf, longMonthNames[month-1]...)
+ } else {
+ // It's difficult to construct a time.Time with a date outside the
+ // standard range but we might as well try to handle the case.
+ buf = appendInt(buf, int(month), 0)
+ }
+ buf = append(buf, ", "...)
+ buf = appendInt(buf, day, 0)
+ buf = append(buf, ", "...)
+ buf = appendInt(buf, hour, 0)
+ buf = append(buf, ", "...)
+ buf = appendInt(buf, minute, 0)
+ buf = append(buf, ", "...)
+ buf = appendInt(buf, second, 0)
+ buf = append(buf, ", "...)
+ buf = appendInt(buf, t.Nanosecond(), 0)
+ buf = append(buf, ", "...)
+ switch loc := t.Location(); loc {
+ case UTC, nil:
+ buf = append(buf, "time.UTC"...)
+ case Local:
+ buf = append(buf, "time.Local"...)
+ default:
+ // there are several options for how we could display this, none of
+ // which are great:
+ //
+ // - use Location(loc.name), which is not technically valid syntax
+ // - use LoadLocation(loc.name), which will cause a syntax error when
+ // embedded and also would require us to escape the string without
+ // importing fmt or strconv
+ // - try to use FixedZone, which would also require escaping the name
+ // and would represent e.g. "America/Los_Angeles" daylight saving time
+ // shifts inaccurately
+ // - use the pointer format, which is no worse than you'd get with the
+ // old fmt.Sprintf("%#v", t) format.
+ //
+ // Of these, Location(loc.name) is the least disruptive. This is an edge
+ // case we hope not to hit too often.
+ buf = append(buf, `time.Location(`...)
+ buf = append(buf, quote(loc.name)...)
+ buf = append(buf, ')')
+ }
+ buf = append(buf, ')')
+ return string(buf)
+}
+
+// Format returns a textual representation of the time value formatted according
+// to the layout defined by the argument. See the documentation for the
+// constant called Layout to see how to represent the layout format.
+//
+// The executable example for Time.Format demonstrates the working
+// of the layout string in detail and is a good reference.
+func (t Time) Format(layout string) string {
+ const bufSize = 64
+ var b []byte
+ max := len(layout) + 10
+ if max < bufSize {
+ var buf [bufSize]byte
+ b = buf[:0]
+ } else {
+ b = make([]byte, 0, max)
+ }
+ b = t.AppendFormat(b, layout)
+ return string(b)
+}
+
+// AppendFormat is like Format but appends the textual
+// representation to b and returns the extended buffer.
+func (t Time) AppendFormat(b []byte, layout string) []byte {
+ // Optimize for RFC3339 as it accounts for over half of all representations.
+ switch layout {
+ case RFC3339:
+ return t.appendFormatRFC3339(b, false)
+ case RFC3339Nano:
+ return t.appendFormatRFC3339(b, true)
+ default:
+ return t.appendFormat(b, layout)
+ }
+}
+
+func (t Time) appendFormat(b []byte, layout string) []byte {
+ var (
+ name, offset, abs = t.locabs()
+
+ year int = -1
+ month Month
+ day int
+ yday int
+ hour int = -1
+ min int
+ sec int
+ )
+
+ // Each iteration generates one std value.
+ for layout != "" {
+ prefix, std, suffix := nextStdChunk(layout)
+ if prefix != "" {
+ b = append(b, prefix...)
+ }
+ if std == 0 {
+ break
+ }
+ layout = suffix
+
+ // Compute year, month, day if needed.
+ if year < 0 && std&stdNeedDate != 0 {
+ year, month, day, yday = absDate(abs, true)
+ yday++
+ }
+
+ // Compute hour, minute, second if needed.
+ if hour < 0 && std&stdNeedClock != 0 {
+ hour, min, sec = absClock(abs)
+ }
+
+ switch std & stdMask {
+ case stdYear:
+ y := year
+ if y < 0 {
+ y = -y
+ }
+ b = appendInt(b, y%100, 2)
+ case stdLongYear:
+ b = appendInt(b, year, 4)
+ case stdMonth:
+ b = append(b, month.String()[:3]...)
+ case stdLongMonth:
+ m := month.String()
+ b = append(b, m...)
+ case stdNumMonth:
+ b = appendInt(b, int(month), 0)
+ case stdZeroMonth:
+ b = appendInt(b, int(month), 2)
+ case stdWeekDay:
+ b = append(b, absWeekday(abs).String()[:3]...)
+ case stdLongWeekDay:
+ s := absWeekday(abs).String()
+ b = append(b, s...)
+ case stdDay:
+ b = appendInt(b, day, 0)
+ case stdUnderDay:
+ if day < 10 {
+ b = append(b, ' ')
+ }
+ b = appendInt(b, day, 0)
+ case stdZeroDay:
+ b = appendInt(b, day, 2)
+ case stdUnderYearDay:
+ if yday < 100 {
+ b = append(b, ' ')
+ if yday < 10 {
+ b = append(b, ' ')
+ }
+ }
+ b = appendInt(b, yday, 0)
+ case stdZeroYearDay:
+ b = appendInt(b, yday, 3)
+ case stdHour:
+ b = appendInt(b, hour, 2)
+ case stdHour12:
+ // Noon is 12PM, midnight is 12AM.
+ hr := hour % 12
+ if hr == 0 {
+ hr = 12
+ }
+ b = appendInt(b, hr, 0)
+ case stdZeroHour12:
+ // Noon is 12PM, midnight is 12AM.
+ hr := hour % 12
+ if hr == 0 {
+ hr = 12
+ }
+ b = appendInt(b, hr, 2)
+ case stdMinute:
+ b = appendInt(b, min, 0)
+ case stdZeroMinute:
+ b = appendInt(b, min, 2)
+ case stdSecond:
+ b = appendInt(b, sec, 0)
+ case stdZeroSecond:
+ b = appendInt(b, sec, 2)
+ case stdPM:
+ if hour >= 12 {
+ b = append(b, "PM"...)
+ } else {
+ b = append(b, "AM"...)
+ }
+ case stdpm:
+ if hour >= 12 {
+ b = append(b, "pm"...)
+ } else {
+ b = append(b, "am"...)
+ }
+ case stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ShortTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumColonTZ, stdNumSecondsTz, stdNumShortTZ, stdNumColonSecondsTZ:
+ // Ugly special case. We cheat and take the "Z" variants
+ // to mean "the time zone as formatted for ISO 8601".
+ if offset == 0 && (std == stdISO8601TZ || std == stdISO8601ColonTZ || std == stdISO8601SecondsTZ || std == stdISO8601ShortTZ || std == stdISO8601ColonSecondsTZ) {
+ b = append(b, 'Z')
+ break
+ }
+ zone := offset / 60 // convert to minutes
+ absoffset := offset
+ if zone < 0 {
+ b = append(b, '-')
+ zone = -zone
+ absoffset = -absoffset
+ } else {
+ b = append(b, '+')
+ }
+ b = appendInt(b, zone/60, 2)
+ if std == stdISO8601ColonTZ || std == stdNumColonTZ || std == stdISO8601ColonSecondsTZ || std == stdNumColonSecondsTZ {
+ b = append(b, ':')
+ }
+ if std != stdNumShortTZ && std != stdISO8601ShortTZ {
+ b = appendInt(b, zone%60, 2)
+ }
+
+ // append seconds if appropriate
+ if std == stdISO8601SecondsTZ || std == stdNumSecondsTz || std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ {
+ if std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ {
+ b = append(b, ':')
+ }
+ b = appendInt(b, absoffset%60, 2)
+ }
+
+ case stdTZ:
+ if name != "" {
+ b = append(b, name...)
+ break
+ }
+ // No time zone known for this time, but we must print one.
+ // Use the -0700 format.
+ 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 = appendInt(b, zone%60, 2)
+ case stdFracSecond0, stdFracSecond9:
+ b = appendNano(b, t.Nanosecond(), std)
+ }
+ }
+ return b
+}
+
+var errBad = errors.New("bad value for field") // placeholder not passed to user
+
+// ParseError describes a problem parsing a time string.
+type ParseError struct {
+ Layout string
+ Value string
+ LayoutElem string
+ ValueElem string
+ Message string
+}
+
+// newParseError creates a new ParseError.
+// The provided value and valueElem are cloned to avoid escaping their values.
+func newParseError(layout, value, layoutElem, valueElem, message string) *ParseError {
+ valueCopy := cloneString(value)
+ valueElemCopy := cloneString(valueElem)
+ return &ParseError{layout, valueCopy, layoutElem, valueElemCopy, message}
+}
+
+// cloneString returns a string copy of s.
+// Do not use strings.Clone to avoid dependency on strings package.
+func cloneString(s string) string {
+ return string([]byte(s))
+}
+
+// These are borrowed from unicode/utf8 and strconv and replicate behavior in
+// that package, since we can't take a dependency on either.
+const (
+ lowerhex = "0123456789abcdef"
+ runeSelf = 0x80
+ runeError = '\uFFFD'
+)
+
+func quote(s string) string {
+ buf := make([]byte, 1, len(s)+2) // slice will be at least len(s) + quotes
+ buf[0] = '"'
+ for i, c := range s {
+ if c >= runeSelf || c < ' ' {
+ // This means you are asking us to parse a time.Duration or
+ // time.Location with unprintable or non-ASCII characters in it.
+ // We don't expect to hit this case very often. We could try to
+ // reproduce strconv.Quote's behavior with full fidelity but
+ // given how rarely we expect to hit these edge cases, speed and
+ // conciseness are better.
+ var width int
+ if c == runeError {
+ width = 1
+ if i+2 < len(s) && s[i:i+3] == string(runeError) {
+ width = 3
+ }
+ } else {
+ width = len(string(c))
+ }
+ for j := 0; j < width; j++ {
+ buf = append(buf, `\x`...)
+ buf = append(buf, lowerhex[s[i+j]>>4])
+ buf = append(buf, lowerhex[s[i+j]&0xF])
+ }
+ } else {
+ if c == '"' || c == '\\' {
+ buf = append(buf, '\\')
+ }
+ buf = append(buf, string(c)...)
+ }
+ }
+ buf = append(buf, '"')
+ return string(buf)
+}
+
+// Error returns the string representation of a ParseError.
+func (e *ParseError) Error() string {
+ if e.Message == "" {
+ return "parsing time " +
+ quote(e.Value) + " as " +
+ quote(e.Layout) + ": cannot parse " +
+ quote(e.ValueElem) + " as " +
+ quote(e.LayoutElem)
+ }
+ return "parsing time " +
+ quote(e.Value) + e.Message
+}
+
+// isDigit reports whether s[i] is in range and is a decimal digit.
+func isDigit[bytes []byte | string](s bytes, i int) bool {
+ if len(s) <= i {
+ return false
+ }
+ c := s[i]
+ return '0' <= c && c <= '9'
+}
+
+// getnum parses s[0:1] or s[0:2] (fixed forces s[0:2])
+// as a decimal integer and returns the integer and the
+// remainder of the string.
+func getnum(s string, fixed bool) (int, string, error) {
+ if !isDigit(s, 0) {
+ return 0, s, errBad
+ }
+ if !isDigit(s, 1) {
+ if fixed {
+ return 0, s, errBad
+ }
+ return int(s[0] - '0'), s[1:], nil
+ }
+ return int(s[0]-'0')*10 + int(s[1]-'0'), s[2:], nil
+}
+
+// getnum3 parses s[0:1], s[0:2], or s[0:3] (fixed forces s[0:3])
+// as a decimal integer and returns the integer and the remainder
+// of the string.
+func getnum3(s string, fixed bool) (int, string, error) {
+ var n, i int
+ for i = 0; i < 3 && isDigit(s, i); i++ {
+ n = n*10 + int(s[i]-'0')
+ }
+ if i == 0 || fixed && i != 3 {
+ return 0, s, errBad
+ }
+ return n, s[i:], nil
+}
+
+func cutspace(s string) string {
+ for len(s) > 0 && s[0] == ' ' {
+ s = s[1:]
+ }
+ return s
+}
+
+// skip removes the given prefix from value,
+// treating runs of space characters as equivalent.
+func skip(value, prefix string) (string, error) {
+ for len(prefix) > 0 {
+ if prefix[0] == ' ' {
+ if len(value) > 0 && value[0] != ' ' {
+ return value, errBad
+ }
+ prefix = cutspace(prefix)
+ value = cutspace(value)
+ continue
+ }
+ if len(value) == 0 || value[0] != prefix[0] {
+ return value, errBad
+ }
+ prefix = prefix[1:]
+ value = value[1:]
+ }
+ return value, nil
+}
+
+// Parse parses a formatted string and returns the time value it represents.
+// See the documentation for the constant called Layout to see how to
+// represent the format. The second argument must be parseable using
+// the format string (layout) provided as the first argument.
+//
+// The example for Time.Format demonstrates the working of the layout string
+// in detail and is a good reference.
+//
+// When parsing (only), the input may contain a fractional second
+// field immediately after the seconds field, even if the layout does not
+// signify its presence. In that case either a comma or a decimal point
+// followed by a maximal series of digits is parsed as a fractional second.
+// Fractional seconds are truncated to nanosecond precision.
+//
+// Elements omitted from the layout are assumed to be zero or, when
+// zero is impossible, one, so parsing "3:04pm" returns the time
+// corresponding to Jan 1, year 0, 15:04:00 UTC (note that because the year is
+// 0, this time is before the zero Time).
+// Years must be in the range 0000..9999. The day of the week is checked
+// for syntax but it is otherwise ignored.
+//
+// For layouts specifying the two-digit year 06, a value NN >= 69 will be treated
+// as 19NN and a value NN < 69 will be treated as 20NN.
+//
+// The remainder of this comment describes the handling of time zones.
+//
+// In the absence of a time zone indicator, Parse returns a time in UTC.
+//
+// When parsing a time with a zone offset like -0700, if the offset corresponds
+// to a time zone used by the current location (Local), then Parse uses that
+// location and zone in the returned time. Otherwise it records the time as
+// being in a fabricated location with time fixed at the given zone offset.
+//
+// When parsing a time with a zone abbreviation like MST, if the zone abbreviation
+// has a defined offset in the current location, then that offset is used.
+// The zone abbreviation "UTC" is recognized as UTC regardless of location.
+// If the zone abbreviation is unknown, Parse records the time as being
+// in a fabricated location with the given zone abbreviation and a zero offset.
+// This choice means that such a time can be parsed and reformatted with the
+// same layout losslessly, but the exact instant used in the representation will
+// differ by the actual zone offset. To avoid such problems, prefer time layouts
+// that use a numeric zone offset, or use ParseInLocation.
+func Parse(layout, value string) (Time, error) {
+ // Optimize for RFC3339 as it accounts for over half of all representations.
+ if layout == RFC3339 || layout == RFC3339Nano {
+ if t, ok := parseRFC3339(value, Local); ok {
+ return t, nil
+ }
+ }
+ return parse(layout, value, UTC, Local)
+}
+
+// ParseInLocation is like Parse but differs in two important ways.
+// First, in the absence of time zone information, Parse interprets a time as UTC;
+// ParseInLocation interprets the time as in the given location.
+// Second, when given a zone offset or abbreviation, Parse tries to match it
+// against the Local location; ParseInLocation uses the given location.
+func ParseInLocation(layout, value string, loc *Location) (Time, error) {
+ // Optimize for RFC3339 as it accounts for over half of all representations.
+ if layout == RFC3339 || layout == RFC3339Nano {
+ if t, ok := parseRFC3339(value, loc); ok {
+ return t, nil
+ }
+ }
+ return parse(layout, value, loc, loc)
+}
+
+func parse(layout, value string, defaultLocation, local *Location) (Time, error) {
+ alayout, avalue := layout, value
+ rangeErrString := "" // set if a value is out of range
+ amSet := false // do we need to subtract 12 from the hour for midnight?
+ pmSet := false // do we need to add 12 to the hour?
+
+ // Time being constructed.
+ var (
+ year int
+ month int = -1
+ day int = -1
+ yday int = -1
+ hour int
+ min int
+ sec int
+ nsec int
+ z *Location
+ zoneOffset int = -1
+ zoneName string
+ )
+
+ // Each iteration processes one std value.
+ for {
+ var err error
+ prefix, std, suffix := nextStdChunk(layout)
+ stdstr := layout[len(prefix) : len(layout)-len(suffix)]
+ value, err = skip(value, prefix)
+ if err != nil {
+ return Time{}, newParseError(alayout, avalue, prefix, value, "")
+ }
+ if std == 0 {
+ if len(value) != 0 {
+ return Time{}, newParseError(alayout, avalue, "", value, ": extra text: "+quote(value))
+ }
+ break
+ }
+ layout = suffix
+ var p string
+ hold := value
+ switch std & stdMask {
+ case stdYear:
+ if len(value) < 2 {
+ err = errBad
+ break
+ }
+ p, value = value[0:2], value[2:]
+ year, err = atoi(p)
+ if err != nil {
+ break
+ }
+ if year >= 69 { // Unix time starts Dec 31 1969 in some time zones
+ year += 1900
+ } else {
+ year += 2000
+ }
+ case stdLongYear:
+ if len(value) < 4 || !isDigit(value, 0) {
+ err = errBad
+ break
+ }
+ p, value = value[0:4], value[4:]
+ year, err = atoi(p)
+ case stdMonth:
+ month, value, err = lookup(shortMonthNames, value)
+ month++
+ case stdLongMonth:
+ month, value, err = lookup(longMonthNames, value)
+ month++
+ case stdNumMonth, stdZeroMonth:
+ month, value, err = getnum(value, std == stdZeroMonth)
+ if err == nil && (month <= 0 || 12 < month) {
+ rangeErrString = "month"
+ }
+ case stdWeekDay:
+ // Ignore weekday except for error checking.
+ _, value, err = lookup(shortDayNames, value)
+ case stdLongWeekDay:
+ _, value, err = lookup(longDayNames, value)
+ case stdDay, stdUnderDay, stdZeroDay:
+ if std == stdUnderDay && len(value) > 0 && value[0] == ' ' {
+ value = value[1:]
+ }
+ day, value, err = getnum(value, std == stdZeroDay)
+ // Note that we allow any one- or two-digit day here.
+ // The month, day, year combination is validated after we've completed parsing.
+ case stdUnderYearDay, stdZeroYearDay:
+ for i := 0; i < 2; i++ {
+ if std == stdUnderYearDay && len(value) > 0 && value[0] == ' ' {
+ value = value[1:]
+ }
+ }
+ yday, value, err = getnum3(value, std == stdZeroYearDay)
+ // Note that we allow any one-, two-, or three-digit year-day here.
+ // The year-day, year combination is validated after we've completed parsing.
+ case stdHour:
+ hour, value, err = getnum(value, false)
+ if hour < 0 || 24 <= hour {
+ rangeErrString = "hour"
+ }
+ case stdHour12, stdZeroHour12:
+ hour, value, err = getnum(value, std == stdZeroHour12)
+ if hour < 0 || 12 < hour {
+ rangeErrString = "hour"
+ }
+ case stdMinute, stdZeroMinute:
+ min, value, err = getnum(value, std == stdZeroMinute)
+ if min < 0 || 60 <= min {
+ rangeErrString = "minute"
+ }
+ case stdSecond, stdZeroSecond:
+ sec, value, err = getnum(value, std == stdZeroSecond)
+ if err != nil {
+ break
+ }
+ if sec < 0 || 60 <= sec {
+ rangeErrString = "second"
+ break
+ }
+ // Special case: do we have a fractional second but no
+ // fractional second in the format?
+ if len(value) >= 2 && commaOrPeriod(value[0]) && isDigit(value, 1) {
+ _, std, _ = nextStdChunk(layout)
+ std &= stdMask
+ if std == stdFracSecond0 || std == stdFracSecond9 {
+ // Fractional second in the layout; proceed normally
+ break
+ }
+ // No fractional second in the layout but we have one in the input.
+ n := 2
+ for ; n < len(value) && isDigit(value, n); n++ {
+ }
+ nsec, rangeErrString, err = parseNanoseconds(value, n)
+ value = value[n:]
+ }
+ case stdPM:
+ if len(value) < 2 {
+ err = errBad
+ break
+ }
+ p, value = value[0:2], value[2:]
+ switch p {
+ case "PM":
+ pmSet = true
+ case "AM":
+ amSet = true
+ default:
+ err = errBad
+ }
+ case stdpm:
+ if len(value) < 2 {
+ err = errBad
+ break
+ }
+ p, value = value[0:2], value[2:]
+ switch p {
+ case "pm":
+ pmSet = true
+ case "am":
+ amSet = true
+ default:
+ err = errBad
+ }
+ case stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ShortTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ, stdNumSecondsTz, stdNumColonSecondsTZ:
+ if (std == stdISO8601TZ || std == stdISO8601ShortTZ || std == stdISO8601ColonTZ) && len(value) >= 1 && value[0] == 'Z' {
+ value = value[1:]
+ z = UTC
+ break
+ }
+ var sign, hour, min, seconds string
+ if std == stdISO8601ColonTZ || std == stdNumColonTZ {
+ if len(value) < 6 {
+ err = errBad
+ break
+ }
+ if value[3] != ':' {
+ err = errBad
+ break
+ }
+ sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], "00", value[6:]
+ } else if std == stdNumShortTZ || std == stdISO8601ShortTZ {
+ if len(value) < 3 {
+ err = errBad
+ break
+ }
+ sign, hour, min, seconds, value = value[0:1], value[1:3], "00", "00", value[3:]
+ } else if std == stdISO8601ColonSecondsTZ || std == stdNumColonSecondsTZ {
+ if len(value) < 9 {
+ err = errBad
+ break
+ }
+ if value[3] != ':' || value[6] != ':' {
+ err = errBad
+ break
+ }
+ sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], value[7:9], value[9:]
+ } else if std == stdISO8601SecondsTZ || std == stdNumSecondsTz {
+ if len(value) < 7 {
+ err = errBad
+ break
+ }
+ sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], value[5:7], value[7:]
+ } else {
+ if len(value) < 5 {
+ err = errBad
+ break
+ }
+ sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], "00", value[5:]
+ }
+ var hr, mm, ss int
+ hr, _, err = getnum(hour, true)
+ if err == nil {
+ mm, _, err = getnum(min, true)
+ }
+ if err == nil {
+ ss, _, err = getnum(seconds, true)
+ }
+ zoneOffset = (hr*60+mm)*60 + ss // offset is in seconds
+ switch sign[0] {
+ case '+':
+ case '-':
+ zoneOffset = -zoneOffset
+ default:
+ err = errBad
+ }
+ case stdTZ:
+ // Does it look like a time zone?
+ if len(value) >= 3 && value[0:3] == "UTC" {
+ z = UTC
+ value = value[3:]
+ break
+ }
+ n, ok := parseTimeZone(value)
+ if !ok {
+ err = errBad
+ break
+ }
+ zoneName, value = value[:n], value[n:]
+
+ case stdFracSecond0:
+ // stdFracSecond0 requires the exact number of digits as specified in
+ // the layout.
+ ndigit := 1 + digitsLen(std)
+ if len(value) < ndigit {
+ err = errBad
+ break
+ }
+ nsec, rangeErrString, err = parseNanoseconds(value, ndigit)
+ value = value[ndigit:]
+
+ case stdFracSecond9:
+ if len(value) < 2 || !commaOrPeriod(value[0]) || value[1] < '0' || '9' < value[1] {
+ // Fractional second omitted.
+ break
+ }
+ // Take any number of digits, even more than asked for,
+ // because it is what the stdSecond case would do.
+ i := 0
+ for i+1 < len(value) && '0' <= value[i+1] && value[i+1] <= '9' {
+ i++
+ }
+ nsec, rangeErrString, err = parseNanoseconds(value, 1+i)
+ value = value[1+i:]
+ }
+ if rangeErrString != "" {
+ return Time{}, newParseError(alayout, avalue, stdstr, value, ": "+rangeErrString+" out of range")
+ }
+ if err != nil {
+ return Time{}, newParseError(alayout, avalue, stdstr, hold, "")
+ }
+ }
+ if pmSet && hour < 12 {
+ hour += 12
+ } else if amSet && hour == 12 {
+ hour = 0
+ }
+
+ // Convert yday to day, month.
+ if yday >= 0 {
+ var d int
+ var m int
+ if isLeap(year) {
+ if yday == 31+29 {
+ m = int(February)
+ d = 29
+ } else if yday > 31+29 {
+ yday--
+ }
+ }
+ if yday < 1 || yday > 365 {
+ return Time{}, newParseError(alayout, avalue, "", value, ": day-of-year out of range")
+ }
+ if m == 0 {
+ m = (yday-1)/31 + 1
+ if int(daysBefore[m]) < yday {
+ m++
+ }
+ d = yday - int(daysBefore[m-1])
+ }
+ // If month, day already seen, yday's m, d must match.
+ // Otherwise, set them from m, d.
+ if month >= 0 && month != m {
+ return Time{}, newParseError(alayout, avalue, "", value, ": day-of-year does not match month")
+ }
+ month = m
+ if day >= 0 && day != d {
+ return Time{}, newParseError(alayout, avalue, "", value, ": day-of-year does not match day")
+ }
+ day = d
+ } else {
+ if month < 0 {
+ month = int(January)
+ }
+ if day < 0 {
+ day = 1
+ }
+ }
+
+ // Validate the day of the month.
+ if day < 1 || day > daysIn(Month(month), year) {
+ return Time{}, newParseError(alayout, avalue, "", value, ": day out of range")
+ }
+
+ if z != nil {
+ return Date(year, Month(month), day, hour, min, sec, nsec, z), nil
+ }
+
+ if zoneOffset != -1 {
+ t := Date(year, Month(month), day, hour, min, sec, nsec, UTC)
+ t.addSec(-int64(zoneOffset))
+
+ // Look for local zone with the given offset.
+ // If that zone was in effect at the given time, use it.
+ name, offset, _, _, _ := local.lookup(t.unixSec())
+ if offset == zoneOffset && (zoneName == "" || name == zoneName) {
+ t.setLoc(local)
+ return t, nil
+ }
+
+ // Otherwise create fake zone to record offset.
+ zoneNameCopy := cloneString(zoneName) // avoid leaking the input value
+ t.setLoc(FixedZone(zoneNameCopy, zoneOffset))
+ return t, nil
+ }
+
+ if zoneName != "" {
+ t := Date(year, Month(month), day, hour, min, sec, nsec, UTC)
+ // Look for local zone with the given offset.
+ // If that zone was in effect at the given time, use it.
+ offset, ok := local.lookupName(zoneName, t.unixSec())
+ if ok {
+ t.addSec(-int64(offset))
+ t.setLoc(local)
+ return t, nil
+ }
+
+ // Otherwise, create fake zone with unknown offset.
+ if len(zoneName) > 3 && zoneName[:3] == "GMT" {
+ offset, _ = atoi(zoneName[3:]) // Guaranteed OK by parseGMT.
+ offset *= 3600
+ }
+ zoneNameCopy := cloneString(zoneName) // avoid leaking the input value
+ t.setLoc(FixedZone(zoneNameCopy, offset))
+ return t, nil
+ }
+
+ // Otherwise, fall back to default.
+ return Date(year, Month(month), day, hour, min, sec, nsec, defaultLocation), nil
+}
+
+// parseTimeZone parses a time zone string and returns its length. Time zones
+// are human-generated and unpredictable. We can't do precise error checking.
+// On the other hand, for a correct parse there must be a time zone at the
+// beginning of the string, so it's almost always true that there's one
+// there. We look at the beginning of the string for a run of upper-case letters.
+// If there are more than 5, it's an error.
+// If there are 4 or 5 and the last is a T, it's a time zone.
+// If there are 3, it's a time zone.
+// Otherwise, other than special cases, it's not a time zone.
+// GMT is special because it can have an hour offset.
+func parseTimeZone(value string) (length int, ok bool) {
+ if len(value) < 3 {
+ return 0, false
+ }
+ // Special case 1: ChST and MeST are the only zones with a lower-case letter.
+ if len(value) >= 4 && (value[:4] == "ChST" || value[:4] == "MeST") {
+ return 4, true
+ }
+ // Special case 2: GMT may have an hour offset; treat it specially.
+ if value[:3] == "GMT" {
+ length = parseGMT(value)
+ return length, true
+ }
+ // Special Case 3: Some time zones are not named, but have +/-00 format
+ if value[0] == '+' || value[0] == '-' {
+ length = parseSignedOffset(value)
+ ok := length > 0 // parseSignedOffset returns 0 in case of bad input
+ return length, ok
+ }
+ // How many upper-case letters are there? Need at least three, at most five.
+ var nUpper int
+ for nUpper = 0; nUpper < 6; nUpper++ {
+ if nUpper >= len(value) {
+ break
+ }
+ if c := value[nUpper]; c < 'A' || 'Z' < c {
+ break
+ }
+ }
+ switch nUpper {
+ case 0, 1, 2, 6:
+ return 0, false
+ case 5: // Must end in T to match.
+ if value[4] == 'T' {
+ return 5, true
+ }
+ case 4:
+ // Must end in T, except one special case.
+ if value[3] == 'T' || value[:4] == "WITA" {
+ return 4, true
+ }
+ case 3:
+ return 3, true
+ }
+ return 0, false
+}
+
+// parseGMT parses a GMT time zone. The input string is known to start "GMT".
+// The function checks whether that is followed by a sign and a number in the
+// range -23 through +23 excluding zero.
+func parseGMT(value string) int {
+ value = value[3:]
+ if len(value) == 0 {
+ return 3
+ }
+
+ return 3 + parseSignedOffset(value)
+}
+
+// parseSignedOffset parses a signed timezone offset (e.g. "+03" or "-04").
+// The function checks for a signed number in the range -23 through +23 excluding zero.
+// Returns length of the found offset string or 0 otherwise.
+func parseSignedOffset(value string) int {
+ sign := value[0]
+ if sign != '-' && sign != '+' {
+ return 0
+ }
+ x, rem, err := leadingInt(value[1:])
+
+ // fail if nothing consumed by leadingInt
+ if err != nil || value[1:] == rem {
+ return 0
+ }
+ if x > 23 {
+ return 0
+ }
+ return len(value) - len(rem)
+}
+
+func commaOrPeriod(b byte) bool {
+ return b == '.' || b == ','
+}
+
+func parseNanoseconds[bytes []byte | string](value bytes, nbytes int) (ns int, rangeErrString string, err error) {
+ if !commaOrPeriod(value[0]) {
+ err = errBad
+ return
+ }
+ if nbytes > 10 {
+ value = value[:10]
+ nbytes = 10
+ }
+ if ns, err = atoi(value[1:nbytes]); err != nil {
+ return
+ }
+ if ns < 0 {
+ rangeErrString = "fractional second"
+ return
+ }
+ // We need nanoseconds, which means scaling by the number
+ // of missing digits in the format, maximum length 10.
+ scaleDigits := 10 - nbytes
+ for i := 0; i < scaleDigits; i++ {
+ ns *= 10
+ }
+ return
+}
+
+var errLeadingInt = errors.New("time: bad [0-9]*") // never printed
+
+// leadingInt consumes the leading [0-9]* from s.
+func leadingInt[bytes []byte | string](s bytes) (x uint64, rem bytes, err error) {
+ i := 0
+ for ; i < len(s); i++ {
+ c := s[i]
+ if c < '0' || c > '9' {
+ break
+ }
+ if x > 1<<63/10 {
+ // overflow
+ return 0, rem, errLeadingInt
+ }
+ x = x*10 + uint64(c) - '0'
+ if x > 1<<63 {
+ // overflow
+ return 0, rem, errLeadingInt
+ }
+ }
+ return x, s[i:], nil
+}
+
+// leadingFraction consumes the leading [0-9]* from s.
+// It is used only for fractions, so does not return an error on overflow,
+// it just stops accumulating precision.
+func leadingFraction(s string) (x uint64, scale float64, rem string) {
+ i := 0
+ scale = 1
+ overflow := false
+ for ; i < len(s); i++ {
+ c := s[i]
+ if c < '0' || c > '9' {
+ break
+ }
+ if overflow {
+ continue
+ }
+ if x > (1<<63-1)/10 {
+ // It's possible for overflow to give a positive number, so take care.
+ overflow = true
+ continue
+ }
+ y := x*10 + uint64(c) - '0'
+ if y > 1<<63 {
+ overflow = true
+ continue
+ }
+ x = y
+ scale *= 10
+ }
+ return x, scale, s[i:]
+}
+
+var unitMap = map[string]uint64{
+ "ns": uint64(Nanosecond),
+ "us": uint64(Microsecond),
+ "µs": uint64(Microsecond), // U+00B5 = micro symbol
+ "μs": uint64(Microsecond), // U+03BC = Greek letter mu
+ "ms": uint64(Millisecond),
+ "s": uint64(Second),
+ "m": uint64(Minute),
+ "h": uint64(Hour),
+}
+
+// ParseDuration parses a duration string.
+// A duration string is a possibly signed sequence of
+// decimal numbers, each with optional fraction and a unit suffix,
+// such as "300ms", "-1.5h" or "2h45m".
+// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
+func ParseDuration(s string) (Duration, error) {
+ // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
+ orig := s
+ var d uint64
+ neg := false
+
+ // Consume [-+]?
+ if s != "" {
+ c := s[0]
+ if c == '-' || c == '+' {
+ neg = c == '-'
+ s = s[1:]
+ }
+ }
+ // Special case: if all that is left is "0", this is zero.
+ if s == "0" {
+ return 0, nil
+ }
+ if s == "" {
+ return 0, errors.New("time: invalid duration " + quote(orig))
+ }
+ for s != "" {
+ var (
+ v, f uint64 // integers before, after decimal point
+ scale float64 = 1 // value = v + f/scale
+ )
+
+ var err error
+
+ // The next character must be [0-9.]
+ if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') {
+ return 0, errors.New("time: invalid duration " + quote(orig))
+ }
+ // Consume [0-9]*
+ pl := len(s)
+ v, s, err = leadingInt(s)
+ if err != nil {
+ return 0, errors.New("time: invalid duration " + quote(orig))
+ }
+ pre := pl != len(s) // whether we consumed anything before a period
+
+ // Consume (\.[0-9]*)?
+ post := false
+ if s != "" && s[0] == '.' {
+ s = s[1:]
+ pl := len(s)
+ f, scale, s = leadingFraction(s)
+ post = pl != len(s)
+ }
+ if !pre && !post {
+ // no digits (e.g. ".s" or "-.s")
+ return 0, errors.New("time: invalid duration " + quote(orig))
+ }
+
+ // Consume unit.
+ i := 0
+ for ; i < len(s); i++ {
+ c := s[i]
+ if c == '.' || '0' <= c && c <= '9' {
+ break
+ }
+ }
+ if i == 0 {
+ return 0, errors.New("time: missing unit in duration " + quote(orig))
+ }
+ u := s[:i]
+ s = s[i:]
+ unit, ok := unitMap[u]
+ if !ok {
+ return 0, errors.New("time: unknown unit " + quote(u) + " in duration " + quote(orig))
+ }
+ if v > 1<<63/unit {
+ // overflow
+ return 0, errors.New("time: invalid duration " + quote(orig))
+ }
+ v *= unit
+ if f > 0 {
+ // float64 is needed to be nanosecond accurate for fractions of hours.
+ // v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit)
+ v += uint64(float64(f) * (float64(unit) / scale))
+ if v > 1<<63 {
+ // overflow
+ return 0, errors.New("time: invalid duration " + quote(orig))
+ }
+ }
+ d += v
+ if d > 1<<63 {
+ return 0, errors.New("time: invalid duration " + quote(orig))
+ }
+ }
+ if neg {
+ return -Duration(d), nil
+ }
+ if d > 1<<63-1 {
+ return 0, errors.New("time: invalid duration " + quote(orig))
+ }
+ return Duration(d), nil
+}
diff --git a/src/time/format_rfc3339.go b/src/time/format_rfc3339.go
new file mode 100644
index 0000000..1151666
--- /dev/null
+++ b/src/time/format_rfc3339.go
@@ -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
+}
diff --git a/src/time/format_test.go b/src/time/format_test.go
new file mode 100644
index 0000000..8a26eaa
--- /dev/null
+++ b/src/time/format_test.go
@@ -0,0 +1,1066 @@
+// 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 (
+ "bytes"
+ "fmt"
+ "math"
+ "strconv"
+ "strings"
+ "testing"
+ "testing/quick"
+ . "time"
+)
+
+var nextStdChunkTests = []string{
+ "(2006)-(01)-(02)T(15):(04):(05)(Z07:00)",
+ "(2006)-(01)-(02) (002) (15):(04):(05)",
+ "(2006)-(01) (002) (15):(04):(05)",
+ "(2006)-(002) (15):(04):(05)",
+ "(2006)(002)(01) (15):(04):(05)",
+ "(2006)(002)(04) (15):(04):(05)",
+}
+
+func TestNextStdChunk(t *testing.T) {
+ // Most bugs in Parse or Format boil down to problems with
+ // the exact detection of format chunk boundaries in the
+ // helper function nextStdChunk (here called as NextStdChunk).
+ // This test checks nextStdChunk's behavior directly,
+ // instead of needing to test it only indirectly through Parse/Format.
+
+ // markChunks returns format with each detected
+ // 'format chunk' parenthesized.
+ // For example showChunks("2006-01-02") == "(2006)-(01)-(02)".
+ markChunks := func(format string) string {
+ // Note that NextStdChunk and StdChunkNames
+ // are not part of time's public API.
+ // They are exported in export_test for this test.
+ out := ""
+ for s := format; s != ""; {
+ prefix, std, suffix := NextStdChunk(s)
+ out += prefix
+ if std > 0 {
+ out += "(" + StdChunkNames[std] + ")"
+ }
+ s = suffix
+ }
+ return out
+ }
+
+ noParens := func(r rune) rune {
+ if r == '(' || r == ')' {
+ return -1
+ }
+ return r
+ }
+
+ for _, marked := range nextStdChunkTests {
+ // marked is an expected output from markChunks.
+ // If we delete the parens and pass it through markChunks,
+ // we should get the original back.
+ format := strings.Map(noParens, marked)
+ out := markChunks(format)
+ if out != marked {
+ t.Errorf("nextStdChunk parses %q as %q, want %q", format, out, marked)
+ }
+ }
+}
+
+type TimeFormatTest struct {
+ time Time
+ formattedValue string
+}
+
+var rfc3339Formats = []TimeFormatTest{
+ {Date(2008, 9, 17, 20, 4, 26, 0, UTC), "2008-09-17T20:04:26Z"},
+ {Date(1994, 9, 17, 20, 4, 26, 0, FixedZone("EST", -18000)), "1994-09-17T20:04:26-05:00"},
+ {Date(2000, 12, 26, 1, 15, 6, 0, FixedZone("OTO", 15600)), "2000-12-26T01:15:06+04:20"},
+}
+
+func TestRFC3339Conversion(t *testing.T) {
+ for _, f := range rfc3339Formats {
+ if f.time.Format(RFC3339) != f.formattedValue {
+ t.Error("RFC3339:")
+ t.Errorf(" want=%+v", f.formattedValue)
+ t.Errorf(" have=%+v", f.time.Format(RFC3339))
+ }
+ }
+}
+
+func TestAppendInt(t *testing.T) {
+ tests := []struct {
+ in int
+ width int
+ want string
+ }{
+ {0, 0, "0"},
+ {0, 1, "0"},
+ {0, 2, "00"},
+ {0, 3, "000"},
+ {1, 0, "1"},
+ {1, 1, "1"},
+ {1, 2, "01"},
+ {1, 3, "001"},
+ {-1, 0, "-1"},
+ {-1, 1, "-1"},
+ {-1, 2, "-01"},
+ {-1, 3, "-001"},
+ {99, 2, "99"},
+ {100, 2, "100"},
+ {1, 4, "0001"},
+ {12, 4, "0012"},
+ {123, 4, "0123"},
+ {1234, 4, "1234"},
+ {12345, 4, "12345"},
+ {1, 5, "00001"},
+ {12, 5, "00012"},
+ {123, 5, "00123"},
+ {1234, 5, "01234"},
+ {12345, 5, "12345"},
+ {123456, 5, "123456"},
+ {0, 9, "000000000"},
+ {123, 9, "000000123"},
+ {123456, 9, "000123456"},
+ {123456789, 9, "123456789"},
+ }
+ var got []byte
+ for _, tt := range tests {
+ got = AppendInt(got[:0], tt.in, tt.width)
+ if string(got) != tt.want {
+ t.Errorf("appendInt(%d, %d) = %s, want %s", tt.in, tt.width, got, tt.want)
+ }
+ }
+}
+
+type FormatTest struct {
+ name string
+ format string
+ result string
+}
+
+var formatTests = []FormatTest{
+ {"ANSIC", ANSIC, "Wed Feb 4 21:00:57 2009"},
+ {"UnixDate", UnixDate, "Wed Feb 4 21:00:57 PST 2009"},
+ {"RubyDate", RubyDate, "Wed Feb 04 21:00:57 -0800 2009"},
+ {"RFC822", RFC822, "04 Feb 09 21:00 PST"},
+ {"RFC850", RFC850, "Wednesday, 04-Feb-09 21:00:57 PST"},
+ {"RFC1123", RFC1123, "Wed, 04 Feb 2009 21:00:57 PST"},
+ {"RFC1123Z", RFC1123Z, "Wed, 04 Feb 2009 21:00:57 -0800"},
+ {"RFC3339", RFC3339, "2009-02-04T21:00:57-08:00"},
+ {"RFC3339Nano", RFC3339Nano, "2009-02-04T21:00:57.0123456-08:00"},
+ {"Kitchen", Kitchen, "9:00PM"},
+ {"am/pm", "3pm", "9pm"},
+ {"AM/PM", "3PM", "9PM"},
+ {"two-digit year", "06 01 02", "09 02 04"},
+ // Three-letter months and days must not be followed by lower-case letter.
+ {"Janet", "Hi Janet, the Month is January", "Hi Janet, the Month is February"},
+ // Time stamps, Fractional seconds.
+ {"Stamp", Stamp, "Feb 4 21:00:57"},
+ {"StampMilli", StampMilli, "Feb 4 21:00:57.012"},
+ {"StampMicro", StampMicro, "Feb 4 21:00:57.012345"},
+ {"StampNano", StampNano, "Feb 4 21:00:57.012345600"},
+ {"DateTime", DateTime, "2009-02-04 21:00:57"},
+ {"DateOnly", DateOnly, "2009-02-04"},
+ {"TimeOnly", TimeOnly, "21:00:57"},
+ {"YearDay", "Jan 2 002 __2 2", "Feb 4 035 35 4"},
+ {"Year", "2006 6 06 _6 __6 ___6", "2009 6 09 _6 __6 ___6"},
+ {"Month", "Jan January 1 01 _1", "Feb February 2 02 _2"},
+ {"DayOfMonth", "2 02 _2 __2", "4 04 4 35"},
+ {"DayOfWeek", "Mon Monday", "Wed Wednesday"},
+ {"Hour", "15 3 03 _3", "21 9 09 _9"},
+ {"Minute", "4 04 _4", "0 00 _0"},
+ {"Second", "5 05 _5", "57 57 _57"},
+}
+
+func TestFormat(t *testing.T) {
+ // The numeric time represents Thu Feb 4 21:00:57.012345600 PST 2009
+ time := Unix(0, 1233810057012345600)
+ for _, test := range formatTests {
+ result := time.Format(test.format)
+ if result != test.result {
+ t.Errorf("%s expected %q got %q", test.name, test.result, result)
+ }
+ }
+}
+
+var goStringTests = []struct {
+ in Time
+ want string
+}{
+ {Date(2009, February, 5, 5, 0, 57, 12345600, UTC),
+ "time.Date(2009, time.February, 5, 5, 0, 57, 12345600, time.UTC)"},
+ {Date(2009, February, 5, 5, 0, 57, 12345600, Local),
+ "time.Date(2009, time.February, 5, 5, 0, 57, 12345600, time.Local)"},
+ {Date(2009, February, 5, 5, 0, 57, 12345600, FixedZone("Europe/Berlin", 3*60*60)),
+ `time.Date(2009, time.February, 5, 5, 0, 57, 12345600, time.Location("Europe/Berlin"))`,
+ },
+ {Date(2009, February, 5, 5, 0, 57, 12345600, FixedZone("Non-ASCII character ⏰", 3*60*60)),
+ `time.Date(2009, time.February, 5, 5, 0, 57, 12345600, time.Location("Non-ASCII character \xe2\x8f\xb0"))`,
+ },
+}
+
+func TestGoString(t *testing.T) {
+ // The numeric time represents Thu Feb 4 21:00:57.012345600 PST 2009
+ for _, tt := range goStringTests {
+ if tt.in.GoString() != tt.want {
+ t.Errorf("GoString (%q): got %q want %q", tt.in, tt.in.GoString(), tt.want)
+ }
+ }
+}
+
+// issue 12440.
+func TestFormatSingleDigits(t *testing.T) {
+ time := Date(2001, 2, 3, 4, 5, 6, 700000000, UTC)
+ test := FormatTest{"single digit format", "3:4:5", "4:5:6"}
+ result := time.Format(test.format)
+ if result != test.result {
+ t.Errorf("%s expected %q got %q", test.name, test.result, result)
+ }
+}
+
+func TestFormatShortYear(t *testing.T) {
+ years := []int{
+ -100001, -100000, -99999,
+ -10001, -10000, -9999,
+ -1001, -1000, -999,
+ -101, -100, -99,
+ -11, -10, -9,
+ -1, 0, 1,
+ 9, 10, 11,
+ 99, 100, 101,
+ 999, 1000, 1001,
+ 9999, 10000, 10001,
+ 99999, 100000, 100001,
+ }
+
+ for _, y := range years {
+ time := Date(y, January, 1, 0, 0, 0, 0, UTC)
+ result := time.Format("2006.01.02")
+ var want string
+ if y < 0 {
+ // The 4 in %04d counts the - sign, so print -y instead
+ // and introduce our own - sign.
+ want = fmt.Sprintf("-%04d.%02d.%02d", -y, 1, 1)
+ } else {
+ want = fmt.Sprintf("%04d.%02d.%02d", y, 1, 1)
+ }
+ if result != want {
+ t.Errorf("(jan 1 %d).Format(\"2006.01.02\") = %q, want %q", y, result, want)
+ }
+ }
+}
+
+type ParseTest struct {
+ name string
+ format string
+ value string
+ hasTZ bool // contains a time zone
+ hasWD bool // contains a weekday
+ yearSign int // sign of year, -1 indicates the year is not present in the format
+ fracDigits int // number of digits of fractional second
+}
+
+var parseTests = []ParseTest{
+ {"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1, 0},
+ {"UnixDate", UnixDate, "Thu Feb 4 21:00:57 PST 2010", true, true, 1, 0},
+ {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0800 2010", true, true, 1, 0},
+ {"RFC850", RFC850, "Thursday, 04-Feb-10 21:00:57 PST", true, true, 1, 0},
+ {"RFC1123", RFC1123, "Thu, 04 Feb 2010 21:00:57 PST", true, true, 1, 0},
+ {"RFC1123", RFC1123, "Thu, 04 Feb 2010 22:00:57 PDT", true, true, 1, 0},
+ {"RFC1123Z", RFC1123Z, "Thu, 04 Feb 2010 21:00:57 -0800", true, true, 1, 0},
+ {"RFC3339", RFC3339, "2010-02-04T21:00:57-08:00", true, false, 1, 0},
+ {"custom: \"2006-01-02 15:04:05-07\"", "2006-01-02 15:04:05-07", "2010-02-04 21:00:57-08", true, false, 1, 0},
+ // Optional fractional seconds.
+ {"ANSIC", ANSIC, "Thu Feb 4 21:00:57.0 2010", false, true, 1, 1},
+ {"UnixDate", UnixDate, "Thu Feb 4 21:00:57.01 PST 2010", true, true, 1, 2},
+ {"RubyDate", RubyDate, "Thu Feb 04 21:00:57.012 -0800 2010", true, true, 1, 3},
+ {"RFC850", RFC850, "Thursday, 04-Feb-10 21:00:57.0123 PST", true, true, 1, 4},
+ {"RFC1123", RFC1123, "Thu, 04 Feb 2010 21:00:57.01234 PST", true, true, 1, 5},
+ {"RFC1123Z", RFC1123Z, "Thu, 04 Feb 2010 21:00:57.01234 -0800", true, true, 1, 5},
+ {"RFC3339", RFC3339, "2010-02-04T21:00:57.012345678-08:00", true, false, 1, 9},
+ {"custom: \"2006-01-02 15:04:05\"", "2006-01-02 15:04:05", "2010-02-04 21:00:57.0", false, false, 1, 0},
+ // Amount of white space should not matter.
+ {"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1, 0},
+ {"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1, 0},
+ // Case should not matter
+ {"ANSIC", ANSIC, "THU FEB 4 21:00:57 2010", false, true, 1, 0},
+ {"ANSIC", ANSIC, "thu feb 4 21:00:57 2010", false, true, 1, 0},
+ // Fractional seconds.
+ {"millisecond:: dot separator", "Mon Jan _2 15:04:05.000 2006", "Thu Feb 4 21:00:57.012 2010", false, true, 1, 3},
+ {"microsecond:: dot separator", "Mon Jan _2 15:04:05.000000 2006", "Thu Feb 4 21:00:57.012345 2010", false, true, 1, 6},
+ {"nanosecond:: dot separator", "Mon Jan _2 15:04:05.000000000 2006", "Thu Feb 4 21:00:57.012345678 2010", false, true, 1, 9},
+ {"millisecond:: comma separator", "Mon Jan _2 15:04:05,000 2006", "Thu Feb 4 21:00:57.012 2010", false, true, 1, 3},
+ {"microsecond:: comma separator", "Mon Jan _2 15:04:05,000000 2006", "Thu Feb 4 21:00:57.012345 2010", false, true, 1, 6},
+ {"nanosecond:: comma separator", "Mon Jan _2 15:04:05,000000000 2006", "Thu Feb 4 21:00:57.012345678 2010", false, true, 1, 9},
+
+ // Leading zeros in other places should not be taken as fractional seconds.
+ {"zero1", "2006.01.02.15.04.05.0", "2010.02.04.21.00.57.0", false, false, 1, 1},
+ {"zero2", "2006.01.02.15.04.05.00", "2010.02.04.21.00.57.01", false, false, 1, 2},
+ // Month and day names only match when not followed by a lower-case letter.
+ {"Janet", "Hi Janet, the Month is January: Jan _2 15:04:05 2006", "Hi Janet, the Month is February: Feb 4 21:00:57 2010", false, true, 1, 0},
+
+ // GMT with offset.
+ {"GMT-8", UnixDate, "Fri Feb 5 05:00:57 GMT-8 2010", true, true, 1, 0},
+
+ // Accept any number of fractional second digits (including none) for .999...
+ // In Go 1, .999... was completely ignored in the format, meaning the first two
+ // cases would succeed, but the next four would not. Go 1.1 accepts all six.
+ // decimal "." separator.
+ {"", "2006-01-02 15:04:05.9999 -0700 MST", "2010-02-04 21:00:57 -0800 PST", true, false, 1, 0},
+ {"", "2006-01-02 15:04:05.999999999 -0700 MST", "2010-02-04 21:00:57 -0800 PST", true, false, 1, 0},
+ {"", "2006-01-02 15:04:05.9999 -0700 MST", "2010-02-04 21:00:57.0123 -0800 PST", true, false, 1, 4},
+ {"", "2006-01-02 15:04:05.999999999 -0700 MST", "2010-02-04 21:00:57.0123 -0800 PST", true, false, 1, 4},
+ {"", "2006-01-02 15:04:05.9999 -0700 MST", "2010-02-04 21:00:57.012345678 -0800 PST", true, false, 1, 9},
+ {"", "2006-01-02 15:04:05.999999999 -0700 MST", "2010-02-04 21:00:57.012345678 -0800 PST", true, false, 1, 9},
+ // comma "," separator.
+ {"", "2006-01-02 15:04:05,9999 -0700 MST", "2010-02-04 21:00:57 -0800 PST", true, false, 1, 0},
+ {"", "2006-01-02 15:04:05,999999999 -0700 MST", "2010-02-04 21:00:57 -0800 PST", true, false, 1, 0},
+ {"", "2006-01-02 15:04:05,9999 -0700 MST", "2010-02-04 21:00:57.0123 -0800 PST", true, false, 1, 4},
+ {"", "2006-01-02 15:04:05,999999999 -0700 MST", "2010-02-04 21:00:57.0123 -0800 PST", true, false, 1, 4},
+ {"", "2006-01-02 15:04:05,9999 -0700 MST", "2010-02-04 21:00:57.012345678 -0800 PST", true, false, 1, 9},
+ {"", "2006-01-02 15:04:05,999999999 -0700 MST", "2010-02-04 21:00:57.012345678 -0800 PST", true, false, 1, 9},
+
+ // issue 4502.
+ {"", StampNano, "Feb 4 21:00:57.012345678", false, false, -1, 9},
+ {"", "Jan _2 15:04:05.999", "Feb 4 21:00:57.012300000", false, false, -1, 4},
+ {"", "Jan _2 15:04:05.999", "Feb 4 21:00:57.012345678", false, false, -1, 9},
+ {"", "Jan _2 15:04:05.999999999", "Feb 4 21:00:57.0123", false, false, -1, 4},
+ {"", "Jan _2 15:04:05.999999999", "Feb 4 21:00:57.012345678", false, false, -1, 9},
+
+ // Day of year.
+ {"", "2006-01-02 002 15:04:05", "2010-02-04 035 21:00:57", false, false, 1, 0},
+ {"", "2006-01 002 15:04:05", "2010-02 035 21:00:57", false, false, 1, 0},
+ {"", "2006-002 15:04:05", "2010-035 21:00:57", false, false, 1, 0},
+ {"", "200600201 15:04:05", "201003502 21:00:57", false, false, 1, 0},
+ {"", "200600204 15:04:05", "201003504 21:00:57", false, false, 1, 0},
+}
+
+func TestParse(t *testing.T) {
+ for _, test := range parseTests {
+ time, err := Parse(test.format, test.value)
+ if err != nil {
+ t.Errorf("%s error: %v", test.name, err)
+ } else {
+ checkTime(time, &test, t)
+ }
+ }
+}
+
+// All parsed with ANSIC.
+var dayOutOfRangeTests = []struct {
+ date string
+ ok bool
+}{
+ {"Thu Jan 99 21:00:57 2010", false},
+ {"Thu Jan 31 21:00:57 2010", true},
+ {"Thu Jan 32 21:00:57 2010", false},
+ {"Thu Feb 28 21:00:57 2012", true},
+ {"Thu Feb 29 21:00:57 2012", true},
+ {"Thu Feb 29 21:00:57 2010", false},
+ {"Thu Mar 31 21:00:57 2010", true},
+ {"Thu Mar 32 21:00:57 2010", false},
+ {"Thu Apr 30 21:00:57 2010", true},
+ {"Thu Apr 31 21:00:57 2010", false},
+ {"Thu May 31 21:00:57 2010", true},
+ {"Thu May 32 21:00:57 2010", false},
+ {"Thu Jun 30 21:00:57 2010", true},
+ {"Thu Jun 31 21:00:57 2010", false},
+ {"Thu Jul 31 21:00:57 2010", true},
+ {"Thu Jul 32 21:00:57 2010", false},
+ {"Thu Aug 31 21:00:57 2010", true},
+ {"Thu Aug 32 21:00:57 2010", false},
+ {"Thu Sep 30 21:00:57 2010", true},
+ {"Thu Sep 31 21:00:57 2010", false},
+ {"Thu Oct 31 21:00:57 2010", true},
+ {"Thu Oct 32 21:00:57 2010", false},
+ {"Thu Nov 30 21:00:57 2010", true},
+ {"Thu Nov 31 21:00:57 2010", false},
+ {"Thu Dec 31 21:00:57 2010", true},
+ {"Thu Dec 32 21:00:57 2010", false},
+ {"Thu Dec 00 21:00:57 2010", false},
+}
+
+func TestParseDayOutOfRange(t *testing.T) {
+ for _, test := range dayOutOfRangeTests {
+ _, err := Parse(ANSIC, test.date)
+ switch {
+ case test.ok && err == nil:
+ // OK
+ case !test.ok && err != nil:
+ if !strings.Contains(err.Error(), "day out of range") {
+ t.Errorf("%q: expected 'day' error, got %v", test.date, err)
+ }
+ case test.ok && err != nil:
+ t.Errorf("%q: unexpected error: %v", test.date, err)
+ case !test.ok && err == nil:
+ t.Errorf("%q: expected 'day' error, got none", test.date)
+ }
+ }
+}
+
+// TestParseInLocation checks that the Parse and ParseInLocation
+// functions do not get confused by the fact that AST (Arabia Standard
+// Time) and AST (Atlantic Standard Time) are different time zones,
+// even though they have the same abbreviation.
+//
+// ICANN has been slowly phasing out invented abbreviation in favor of
+// numeric time zones (for example, the Asia/Baghdad time zone
+// abbreviation got changed from AST to +03 in the 2017a tzdata
+// release); but we still want to make sure that the time package does
+// not get confused on systems with slightly older tzdata packages.
+func TestParseInLocation(t *testing.T) {
+
+ baghdad, err := LoadLocation("Asia/Baghdad")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ var t1, t2 Time
+
+ t1, err = ParseInLocation("Jan 02 2006 MST", "Feb 01 2013 AST", baghdad)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ _, offset := t1.Zone()
+
+ // A zero offset means that ParseInLocation did not recognize the
+ // 'AST' abbreviation as matching the current location (Baghdad,
+ // where we'd expect a +03 hrs offset); likely because we're using
+ // a recent tzdata release (2017a or newer).
+ // If it happens, skip the Baghdad test.
+ if offset != 0 {
+ t2 = Date(2013, February, 1, 00, 00, 00, 0, baghdad)
+ if t1 != t2 {
+ t.Fatalf("ParseInLocation(Feb 01 2013 AST, Baghdad) = %v, want %v", t1, t2)
+ }
+ if offset != 3*60*60 {
+ t.Fatalf("ParseInLocation(Feb 01 2013 AST, Baghdad).Zone = _, %d, want _, %d", offset, 3*60*60)
+ }
+ }
+
+ blancSablon, err := LoadLocation("America/Blanc-Sablon")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // In this case 'AST' means 'Atlantic Standard Time', and we
+ // expect the abbreviation to correctly match the american
+ // location.
+ t1, err = ParseInLocation("Jan 02 2006 MST", "Feb 01 2013 AST", blancSablon)
+ if err != nil {
+ t.Fatal(err)
+ }
+ t2 = Date(2013, February, 1, 00, 00, 00, 0, blancSablon)
+ if t1 != t2 {
+ t.Fatalf("ParseInLocation(Feb 01 2013 AST, Blanc-Sablon) = %v, want %v", t1, t2)
+ }
+ _, offset = t1.Zone()
+ if offset != -4*60*60 {
+ t.Fatalf("ParseInLocation(Feb 01 2013 AST, Blanc-Sablon).Zone = _, %d, want _, %d", offset, -4*60*60)
+ }
+}
+
+func TestLoadLocationZipFile(t *testing.T) {
+ undo := DisablePlatformSources()
+ defer undo()
+
+ _, err := LoadLocation("Australia/Sydney")
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+var rubyTests = []ParseTest{
+ {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0800 2010", true, true, 1, 0},
+ // Ignore the time zone in the test. If it parses, it'll be OK.
+ {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0000 2010", false, true, 1, 0},
+ {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 +0000 2010", false, true, 1, 0},
+ {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 +1130 2010", false, true, 1, 0},
+}
+
+// Problematic time zone format needs special tests.
+func TestRubyParse(t *testing.T) {
+ for _, test := range rubyTests {
+ time, err := Parse(test.format, test.value)
+ if err != nil {
+ t.Errorf("%s error: %v", test.name, err)
+ } else {
+ checkTime(time, &test, t)
+ }
+ }
+}
+
+func checkTime(time Time, test *ParseTest, t *testing.T) {
+ // The time should be Thu Feb 4 21:00:57 PST 2010
+ if test.yearSign >= 0 && test.yearSign*time.Year() != 2010 {
+ t.Errorf("%s: bad year: %d not %d", test.name, time.Year(), 2010)
+ }
+ if time.Month() != February {
+ t.Errorf("%s: bad month: %s not %s", test.name, time.Month(), February)
+ }
+ if time.Day() != 4 {
+ t.Errorf("%s: bad day: %d not %d", test.name, time.Day(), 4)
+ }
+ if time.Hour() != 21 {
+ t.Errorf("%s: bad hour: %d not %d", test.name, time.Hour(), 21)
+ }
+ if time.Minute() != 0 {
+ t.Errorf("%s: bad minute: %d not %d", test.name, time.Minute(), 0)
+ }
+ if time.Second() != 57 {
+ t.Errorf("%s: bad second: %d not %d", test.name, time.Second(), 57)
+ }
+ // Nanoseconds must be checked against the precision of the input.
+ nanosec, err := strconv.ParseUint("012345678"[:test.fracDigits]+"000000000"[:9-test.fracDigits], 10, 0)
+ if err != nil {
+ panic(err)
+ }
+ if time.Nanosecond() != int(nanosec) {
+ t.Errorf("%s: bad nanosecond: %d not %d", test.name, time.Nanosecond(), nanosec)
+ }
+ name, offset := time.Zone()
+ if test.hasTZ && offset != -28800 {
+ t.Errorf("%s: bad tz offset: %s %d not %d", test.name, name, offset, -28800)
+ }
+ if test.hasWD && time.Weekday() != Thursday {
+ t.Errorf("%s: bad weekday: %s not %s", test.name, time.Weekday(), Thursday)
+ }
+}
+
+func TestFormatAndParse(t *testing.T) {
+ const fmt = "Mon MST " + RFC3339 // all fields
+ f := func(sec int64) bool {
+ t1 := Unix(sec/2, 0)
+ if t1.Year() < 1000 || t1.Year() > 9999 || t1.Unix() != sec {
+ // not required to work
+ return true
+ }
+ t2, err := Parse(fmt, t1.Format(fmt))
+ if err != nil {
+ t.Errorf("error: %s", err)
+ return false
+ }
+ if t1.Unix() != t2.Unix() || t1.Nanosecond() != t2.Nanosecond() {
+ t.Errorf("FormatAndParse %d: %q(%d) %q(%d)", sec, t1, t1.Unix(), t2, t2.Unix())
+ return false
+ }
+ return true
+ }
+ f32 := func(sec int32) bool { return f(int64(sec)) }
+ cfg := &quick.Config{MaxCount: 10000}
+
+ // Try a reasonable date first, then the huge ones.
+ if err := quick.Check(f32, cfg); err != nil {
+ t.Fatal(err)
+ }
+ if err := quick.Check(f, cfg); err != nil {
+ t.Fatal(err)
+ }
+}
+
+type ParseTimeZoneTest struct {
+ value string
+ length int
+ ok bool
+}
+
+var parseTimeZoneTests = []ParseTimeZoneTest{
+ {"gmt hi there", 0, false},
+ {"GMT hi there", 3, true},
+ {"GMT+12 hi there", 6, true},
+ {"GMT+00 hi there", 6, true},
+ {"GMT+", 3, true},
+ {"GMT+3", 5, true},
+ {"GMT+a", 3, true},
+ {"GMT+3a", 5, true},
+ {"GMT-5 hi there", 5, true},
+ {"GMT-51 hi there", 3, true},
+ {"ChST hi there", 4, true},
+ {"MeST hi there", 4, true},
+ {"MSDx", 3, true},
+ {"MSDY", 0, false}, // four letters must end in T.
+ {"ESAST hi", 5, true},
+ {"ESASTT hi", 0, false}, // run of upper-case letters too long.
+ {"ESATY hi", 0, false}, // five letters must end in T.
+ {"WITA hi", 4, true}, // Issue #18251
+ // Issue #24071
+ {"+03 hi", 3, true},
+ {"-04 hi", 3, true},
+ // Issue #26032
+ {"+00", 3, true},
+ {"-11", 3, true},
+ {"-12", 3, true},
+ {"-23", 3, true},
+ {"-24", 0, false},
+ {"+13", 3, true},
+ {"+14", 3, true},
+ {"+23", 3, true},
+ {"+24", 0, false},
+}
+
+func TestParseTimeZone(t *testing.T) {
+ for _, test := range parseTimeZoneTests {
+ length, ok := ParseTimeZone(test.value)
+ if ok != test.ok {
+ t.Errorf("expected %t for %q got %t", test.ok, test.value, ok)
+ } else if length != test.length {
+ t.Errorf("expected %d for %q got %d", test.length, test.value, length)
+ }
+ }
+}
+
+type ParseErrorTest struct {
+ format string
+ value string
+ expect string // must appear within the error
+}
+
+var parseErrorTests = []ParseErrorTest{
+ {ANSIC, "Feb 4 21:00:60 2010", `cannot parse "Feb 4 21:00:60 2010" as "Mon"`},
+ {ANSIC, "Thu Feb 4 21:00:57 @2010", `cannot parse "@2010" as "2006"`},
+ {ANSIC, "Thu Feb 4 21:00:60 2010", "second out of range"},
+ {ANSIC, "Thu Feb 4 21:61:57 2010", "minute out of range"},
+ {ANSIC, "Thu Feb 4 24:00:60 2010", "hour out of range"},
+ {"Mon Jan _2 15:04:05.000 2006", "Thu Feb 4 23:00:59x01 2010", `cannot parse "x01 2010" as ".000"`},
+ {"Mon Jan _2 15:04:05.000 2006", "Thu Feb 4 23:00:59.xxx 2010", `cannot parse ".xxx 2010" as ".000"`},
+ {"Mon Jan _2 15:04:05.000 2006", "Thu Feb 4 23:00:59.-123 2010", "fractional second out of range"},
+ // issue 4502. StampNano requires exactly 9 digits of precision.
+ {StampNano, "Dec 7 11:22:01.000000", `cannot parse ".000000" as ".000000000"`},
+ {StampNano, "Dec 7 11:22:01.0000000000", `extra text: "0"`},
+ // issue 4493. Helpful errors.
+ {RFC3339, "2006-01-02T15:04:05Z07:00", `parsing time "2006-01-02T15:04:05Z07:00": extra text: "07:00"`},
+ {RFC3339, "2006-01-02T15:04_abc", `parsing time "2006-01-02T15:04_abc" as "2006-01-02T15:04:05Z07:00": cannot parse "_abc" as ":"`},
+ {RFC3339, "2006-01-02T15:04:05_abc", `parsing time "2006-01-02T15:04:05_abc" as "2006-01-02T15:04:05Z07:00": cannot parse "_abc" as "Z07:00"`},
+ {RFC3339, "2006-01-02T15:04:05Z_abc", `parsing time "2006-01-02T15:04:05Z_abc": extra text: "_abc"`},
+ // invalid second followed by optional fractional seconds
+ {RFC3339, "2010-02-04T21:00:67.012345678-08:00", "second out of range"},
+ // issue 54569
+ {RFC3339, "0000-01-01T00:00:.0+00:00", `parsing time "0000-01-01T00:00:.0+00:00" as "2006-01-02T15:04:05Z07:00": cannot parse ".0+00:00" as "05"`},
+ // issue 21113
+ {"_2 Jan 06 15:04 MST", "4 --- 00 00:00 GMT", `cannot parse "--- 00 00:00 GMT" as "Jan"`},
+ {"_2 January 06 15:04 MST", "4 --- 00 00:00 GMT", `cannot parse "--- 00 00:00 GMT" as "January"`},
+
+ // invalid or mismatched day-of-year
+ {"Jan _2 002 2006", "Feb 4 034 2006", "day-of-year does not match day"},
+ {"Jan _2 002 2006", "Feb 4 004 2006", "day-of-year does not match month"},
+
+ // issue 45391.
+ {`"2006-01-02T15:04:05Z07:00"`, "0", `parsing time "0" as "\"2006-01-02T15:04:05Z07:00\"": cannot parse "0" as "\""`},
+ {RFC3339, "\"", `parsing time "\"" as "2006-01-02T15:04:05Z07:00": cannot parse "\"" as "2006"`},
+
+ // issue 54570
+ {RFC3339, "0000-01-01T00:00:00+00:+0", `parsing time "0000-01-01T00:00:00+00:+0" as "2006-01-02T15:04:05Z07:00": cannot parse "+00:+0" as "Z07:00"`},
+ {RFC3339, "0000-01-01T00:00:00+-0:00", `parsing time "0000-01-01T00:00:00+-0:00" as "2006-01-02T15:04:05Z07:00": cannot parse "+-0:00" as "Z07:00"`},
+
+ // issue 56730
+ {"2006-01-02", "22-10-25", `parsing time "22-10-25" as "2006-01-02": cannot parse "22-10-25" as "2006"`},
+ {"06-01-02", "a2-10-25", `parsing time "a2-10-25" as "06-01-02": cannot parse "a2-10-25" as "06"`},
+ {"03:04PM", "12:03pM", `parsing time "12:03pM" as "03:04PM": cannot parse "pM" as "PM"`},
+ {"03:04pm", "12:03pM", `parsing time "12:03pM" as "03:04pm": cannot parse "pM" as "pm"`},
+}
+
+func TestParseErrors(t *testing.T) {
+ for _, test := range parseErrorTests {
+ _, err := Parse(test.format, test.value)
+ if err == nil {
+ t.Errorf("expected error for %q %q", test.format, test.value)
+ } else if !strings.Contains(err.Error(), test.expect) {
+ t.Errorf("expected error with %q for %q %q; got %s", test.expect, test.format, test.value, err)
+ }
+ }
+}
+
+func TestNoonIs12PM(t *testing.T) {
+ noon := Date(0, January, 1, 12, 0, 0, 0, UTC)
+ const expect = "12:00PM"
+ got := noon.Format("3:04PM")
+ if got != expect {
+ t.Errorf("got %q; expect %q", got, expect)
+ }
+ got = noon.Format("03:04PM")
+ if got != expect {
+ t.Errorf("got %q; expect %q", got, expect)
+ }
+}
+
+func TestMidnightIs12AM(t *testing.T) {
+ midnight := Date(0, January, 1, 0, 0, 0, 0, UTC)
+ expect := "12:00AM"
+ got := midnight.Format("3:04PM")
+ if got != expect {
+ t.Errorf("got %q; expect %q", got, expect)
+ }
+ got = midnight.Format("03:04PM")
+ if got != expect {
+ t.Errorf("got %q; expect %q", got, expect)
+ }
+}
+
+func Test12PMIsNoon(t *testing.T) {
+ noon, err := Parse("3:04PM", "12:00PM")
+ if err != nil {
+ t.Fatal("error parsing date:", err)
+ }
+ if noon.Hour() != 12 {
+ t.Errorf("got %d; expect 12", noon.Hour())
+ }
+ noon, err = Parse("03:04PM", "12:00PM")
+ if err != nil {
+ t.Fatal("error parsing date:", err)
+ }
+ if noon.Hour() != 12 {
+ t.Errorf("got %d; expect 12", noon.Hour())
+ }
+}
+
+func Test12AMIsMidnight(t *testing.T) {
+ midnight, err := Parse("3:04PM", "12:00AM")
+ if err != nil {
+ t.Fatal("error parsing date:", err)
+ }
+ if midnight.Hour() != 0 {
+ t.Errorf("got %d; expect 0", midnight.Hour())
+ }
+ midnight, err = Parse("03:04PM", "12:00AM")
+ if err != nil {
+ t.Fatal("error parsing date:", err)
+ }
+ if midnight.Hour() != 0 {
+ t.Errorf("got %d; expect 0", midnight.Hour())
+ }
+}
+
+// Check that a time without a Zone still produces a (numeric) time zone
+// when formatted with MST as a requested zone.
+func TestMissingZone(t *testing.T) {
+ time, err := Parse(RubyDate, "Thu Feb 02 16:10:03 -0500 2006")
+ if err != nil {
+ t.Fatal("error parsing date:", err)
+ }
+ expect := "Thu Feb 2 16:10:03 -0500 2006" // -0500 not EST
+ str := time.Format(UnixDate) // uses MST as its time zone
+ if str != expect {
+ t.Errorf("got %s; expect %s", str, expect)
+ }
+}
+
+func TestMinutesInTimeZone(t *testing.T) {
+ time, err := Parse(RubyDate, "Mon Jan 02 15:04:05 +0123 2006")
+ if err != nil {
+ t.Fatal("error parsing date:", err)
+ }
+ expected := (1*60 + 23) * 60
+ _, offset := time.Zone()
+ if offset != expected {
+ t.Errorf("ZoneOffset = %d, want %d", offset, expected)
+ }
+}
+
+type SecondsTimeZoneOffsetTest struct {
+ format string
+ value string
+ expectedoffset int
+}
+
+var secondsTimeZoneOffsetTests = []SecondsTimeZoneOffsetTest{
+ {"2006-01-02T15:04:05-070000", "1871-01-01T05:33:02-003408", -(34*60 + 8)},
+ {"2006-01-02T15:04:05-07:00:00", "1871-01-01T05:33:02-00:34:08", -(34*60 + 8)},
+ {"2006-01-02T15:04:05-070000", "1871-01-01T05:33:02+003408", 34*60 + 8},
+ {"2006-01-02T15:04:05-07:00:00", "1871-01-01T05:33:02+00:34:08", 34*60 + 8},
+ {"2006-01-02T15:04:05Z070000", "1871-01-01T05:33:02-003408", -(34*60 + 8)},
+ {"2006-01-02T15:04:05Z07:00:00", "1871-01-01T05:33:02+00:34:08", 34*60 + 8},
+ {"2006-01-02T15:04:05-07", "1871-01-01T05:33:02+01", 1 * 60 * 60},
+ {"2006-01-02T15:04:05-07", "1871-01-01T05:33:02-02", -2 * 60 * 60},
+ {"2006-01-02T15:04:05Z07", "1871-01-01T05:33:02-02", -2 * 60 * 60},
+}
+
+func TestParseSecondsInTimeZone(t *testing.T) {
+ // should accept timezone offsets with seconds like: Zone America/New_York -4:56:02 - LMT 1883 Nov 18 12:03:58
+ for _, test := range secondsTimeZoneOffsetTests {
+ time, err := Parse(test.format, test.value)
+ if err != nil {
+ t.Fatal("error parsing date:", err)
+ }
+ _, offset := time.Zone()
+ if offset != test.expectedoffset {
+ t.Errorf("ZoneOffset = %d, want %d", offset, test.expectedoffset)
+ }
+ }
+}
+
+func TestFormatSecondsInTimeZone(t *testing.T) {
+ for _, test := range secondsTimeZoneOffsetTests {
+ d := Date(1871, 1, 1, 5, 33, 2, 0, FixedZone("LMT", test.expectedoffset))
+ timestr := d.Format(test.format)
+ if timestr != test.value {
+ t.Errorf("Format = %s, want %s", timestr, test.value)
+ }
+ }
+}
+
+// Issue 11334.
+func TestUnderscoreTwoThousand(t *testing.T) {
+ format := "15:04_20060102"
+ input := "14:38_20150618"
+ time, err := Parse(format, input)
+ if err != nil {
+ t.Error(err)
+ }
+ if y, m, d := time.Date(); y != 2015 || m != 6 || d != 18 {
+ t.Errorf("Incorrect y/m/d, got %d/%d/%d", y, m, d)
+ }
+ if h := time.Hour(); h != 14 {
+ t.Errorf("Incorrect hour, got %d", h)
+ }
+ if m := time.Minute(); m != 38 {
+ t.Errorf("Incorrect minute, got %d", m)
+ }
+}
+
+// Issue 29918, 29916
+func TestStd0xParseError(t *testing.T) {
+ tests := []struct {
+ format, value, valueElemPrefix string
+ }{
+ {"01 MST", "0 MST", "0"},
+ {"01 MST", "1 MST", "1"},
+ {RFC850, "Thursday, 04-Feb-1 21:00:57 PST", "1"},
+ }
+ for _, tt := range tests {
+ _, err := Parse(tt.format, tt.value)
+ if err == nil {
+ t.Errorf("Parse(%q, %q) did not fail as expected", tt.format, tt.value)
+ } else if perr, ok := err.(*ParseError); !ok {
+ t.Errorf("Parse(%q, %q) returned error type %T, expected ParseError", tt.format, tt.value, perr)
+ } else if !strings.Contains(perr.Error(), "cannot parse") || !strings.HasPrefix(perr.ValueElem, tt.valueElemPrefix) {
+ t.Errorf("Parse(%q, %q) returned wrong parsing error message: %v", tt.format, tt.value, perr)
+ }
+ }
+}
+
+var monthOutOfRangeTests = []struct {
+ value string
+ ok bool
+}{
+ {"00-01", false},
+ {"13-01", false},
+ {"01-01", true},
+}
+
+func TestParseMonthOutOfRange(t *testing.T) {
+ for _, test := range monthOutOfRangeTests {
+ _, err := Parse("01-02", test.value)
+ switch {
+ case !test.ok && err != nil:
+ if !strings.Contains(err.Error(), "month out of range") {
+ t.Errorf("%q: expected 'month' error, got %v", test.value, err)
+ }
+ case test.ok && err != nil:
+ t.Errorf("%q: unexpected error: %v", test.value, err)
+ case !test.ok && err == nil:
+ t.Errorf("%q: expected 'month' error, got none", test.value)
+ }
+ }
+}
+
+// Issue 37387.
+func TestParseYday(t *testing.T) {
+ t.Parallel()
+ for i := 1; i <= 365; i++ {
+ d := fmt.Sprintf("2020-%03d", i)
+ tm, err := Parse("2006-002", d)
+ if err != nil {
+ t.Errorf("unexpected error for %s: %v", d, err)
+ } else if tm.Year() != 2020 || tm.YearDay() != i {
+ t.Errorf("got year %d yearday %d, want %d %d", tm.Year(), tm.YearDay(), 2020, i)
+ }
+ }
+}
+
+// Issue 45391.
+func TestQuote(t *testing.T) {
+ tests := []struct {
+ s, want string
+ }{
+ {`"`, `"\""`},
+ {`abc"xyz"`, `"abc\"xyz\""`},
+ {"", `""`},
+ {"abc", `"abc"`},
+ {`☺`, `"\xe2\x98\xba"`},
+ {`☺ hello ☺ hello`, `"\xe2\x98\xba hello \xe2\x98\xba hello"`},
+ {"\x04", `"\x04"`},
+ }
+ for _, tt := range tests {
+ if q := Quote(tt.s); q != tt.want {
+ t.Errorf("Quote(%q) = got %q, want %q", tt.s, q, tt.want)
+ }
+ }
+
+}
+
+// Issue 48037
+func TestFormatFractionalSecondSeparators(t *testing.T) {
+ tests := []struct {
+ s, want string
+ }{
+ {`15:04:05.000`, `21:00:57.012`},
+ {`15:04:05.999`, `21:00:57.012`},
+ {`15:04:05,000`, `21:00:57,012`},
+ {`15:04:05,999`, `21:00:57,012`},
+ }
+
+ // The numeric time represents Thu Feb 4 21:00:57.012345600 PST 2009
+ time := Unix(0, 1233810057012345600)
+ for _, tt := range tests {
+ if q := time.Format(tt.s); q != tt.want {
+ t.Errorf("Format(%q) = got %q, want %q", tt.s, q, tt.want)
+ }
+ }
+}
+
+var longFractionalDigitsTests = []struct {
+ value string
+ want int
+}{
+ // 9 digits
+ {"2021-09-29T16:04:33.000000000Z", 0},
+ {"2021-09-29T16:04:33.000000001Z", 1},
+ {"2021-09-29T16:04:33.100000000Z", 100_000_000},
+ {"2021-09-29T16:04:33.100000001Z", 100_000_001},
+ {"2021-09-29T16:04:33.999999999Z", 999_999_999},
+ {"2021-09-29T16:04:33.012345678Z", 12_345_678},
+ // 10 digits, truncates
+ {"2021-09-29T16:04:33.0000000000Z", 0},
+ {"2021-09-29T16:04:33.0000000001Z", 0},
+ {"2021-09-29T16:04:33.1000000000Z", 100_000_000},
+ {"2021-09-29T16:04:33.1000000009Z", 100_000_000},
+ {"2021-09-29T16:04:33.9999999999Z", 999_999_999},
+ {"2021-09-29T16:04:33.0123456789Z", 12_345_678},
+ // 11 digits, truncates
+ {"2021-09-29T16:04:33.10000000000Z", 100_000_000},
+ {"2021-09-29T16:04:33.00123456789Z", 1_234_567},
+ // 12 digits, truncates
+ {"2021-09-29T16:04:33.000123456789Z", 123_456},
+ // 15 digits, truncates
+ {"2021-09-29T16:04:33.9999999999999999Z", 999_999_999},
+}
+
+// Issue 48685 and 54567.
+func TestParseFractionalSecondsLongerThanNineDigits(t *testing.T) {
+ for _, tt := range longFractionalDigitsTests {
+ for _, format := range []string{RFC3339, RFC3339Nano} {
+ tm, err := Parse(format, tt.value)
+ if err != nil {
+ t.Errorf("Parse(%q, %q) error: %v", format, tt.value, err)
+ continue
+ }
+ if got := tm.Nanosecond(); got != tt.want {
+ t.Errorf("Parse(%q, %q) = got %d, want %d", format, tt.value, got, tt.want)
+ }
+ }
+ }
+}
+
+func FuzzFormatRFC3339(f *testing.F) {
+ for _, ts := range [][2]int64{
+ {math.MinInt64, math.MinInt64}, // 292277026304-08-26T15:42:51Z
+ {-62167219200, 0}, // 0000-01-01T00:00:00Z
+ {1661201140, 676836973}, // 2022-08-22T20:45:40.676836973Z
+ {253402300799, 999999999}, // 9999-12-31T23:59:59.999999999Z
+ {math.MaxInt64, math.MaxInt64}, // -292277022365-05-08T08:17:07Z
+ } {
+ f.Add(ts[0], ts[1], true, false, 0)
+ f.Add(ts[0], ts[1], false, true, 0)
+ for _, offset := range []int{0, 60, 60 * 60, 99*60*60 + 99*60, 123456789} {
+ f.Add(ts[0], ts[1], false, false, -offset)
+ f.Add(ts[0], ts[1], false, false, +offset)
+ }
+ }
+
+ f.Fuzz(func(t *testing.T, sec, nsec int64, useUTC, useLocal bool, tzOffset int) {
+ var loc *Location
+ switch {
+ case useUTC:
+ loc = UTC
+ case useLocal:
+ loc = Local
+ default:
+ loc = FixedZone("", tzOffset)
+ }
+ ts := Unix(sec, nsec).In(loc)
+
+ got := AppendFormatRFC3339(ts, nil, false)
+ want := AppendFormatAny(ts, nil, RFC3339)
+ if !bytes.Equal(got, want) {
+ t.Errorf("Format(%s, RFC3339) mismatch:\n\tgot: %s\n\twant: %s", ts, got, want)
+ }
+
+ gotNanos := AppendFormatRFC3339(ts, nil, true)
+ wantNanos := AppendFormatAny(ts, nil, RFC3339Nano)
+ if !bytes.Equal(got, want) {
+ t.Errorf("Format(%s, RFC3339Nano) mismatch:\n\tgot: %s\n\twant: %s", ts, gotNanos, wantNanos)
+ }
+ })
+}
+
+func FuzzParseRFC3339(f *testing.F) {
+ for _, tt := range formatTests {
+ f.Add(tt.result)
+ }
+ for _, tt := range parseTests {
+ f.Add(tt.value)
+ }
+ for _, tt := range parseErrorTests {
+ f.Add(tt.value)
+ }
+ for _, tt := range longFractionalDigitsTests {
+ f.Add(tt.value)
+ }
+
+ f.Fuzz(func(t *testing.T, s string) {
+ // equalTime is like time.Time.Equal, but also compares the time zone.
+ equalTime := func(t1, t2 Time) bool {
+ name1, offset1 := t1.Zone()
+ name2, offset2 := t2.Zone()
+ return t1.Equal(t2) && name1 == name2 && offset1 == offset2
+ }
+
+ for _, tz := range []*Location{UTC, Local} {
+ // Parsing as RFC3339 or RFC3339Nano should be identical.
+ t1, err1 := ParseAny(RFC3339, s, UTC, tz)
+ t2, err2 := ParseAny(RFC3339Nano, s, UTC, tz)
+ switch {
+ case (err1 == nil) != (err2 == nil):
+ t.Fatalf("ParseAny(%q) error mismatch:\n\tgot: %v\n\twant: %v", s, err1, err2)
+ case !equalTime(t1, t2):
+ t.Fatalf("ParseAny(%q) value mismatch:\n\tgot: %v\n\twant: %v", s, t1, t2)
+ }
+
+ // TODO(https://go.dev/issue/54580):
+ // Remove these checks after ParseAny rejects all invalid RFC 3339.
+ if err1 == nil {
+ num2 := func(s string) byte { return 10*(s[0]-'0') + (s[1] - '0') }
+ switch {
+ case len(s) > 12 && s[12] == ':':
+ t.Skipf("ParseAny(%q) incorrectly allows single-digit hour fields", s)
+ case len(s) > 19 && s[19] == ',':
+ t.Skipf("ParseAny(%q) incorrectly allows comma as sub-second separator", s)
+ case !strings.HasSuffix(s, "Z") && len(s) > 4 && (num2(s[len(s)-5:]) >= 24 || num2(s[len(s)-2:]) >= 60):
+ t.Skipf("ParseAny(%q) incorrectly allows out-of-range zone offset", s)
+ }
+ }
+
+ // Customized parser should be identical to general parser.
+ switch got, ok := ParseRFC3339(s, tz); {
+ case ok != (err1 == nil):
+ t.Fatalf("ParseRFC3339(%q) error mismatch:\n\tgot: %v\n\twant: %v", s, ok, err1 == nil)
+ case !equalTime(got, t1):
+ t.Fatalf("ParseRFC3339(%q) value mismatch:\n\tgot: %v\n\twant: %v", s, got, t2)
+ }
+ }
+ })
+}
diff --git a/src/time/genzabbrs.go b/src/time/genzabbrs.go
new file mode 100644
index 0000000..7dbd22f
--- /dev/null
+++ b/src/time/genzabbrs.go
@@ -0,0 +1,156 @@
+// 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"
+ "sort"
+ "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)
+ }
+ sort.Slice(zs, func(i, j int) bool {
+ return zs[i].UnixName < zs[j].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}}}
+
+`
diff --git a/src/time/internal_test.go b/src/time/internal_test.go
new file mode 100644
index 0000000..4c4a720
--- /dev/null
+++ b/src/time/internal_test.go
@@ -0,0 +1,72 @@
+// 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) {}
+
+// 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.
+ r := &runtimeTimer{
+ when: runtimeNano(),
+ period: 1<<63 - 1,
+ f: empty,
+ arg: nil,
+ }
+ startTimer(r)
+ defer stopTimer(r)
+
+ // 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}
+)
diff --git a/src/time/mono_test.go b/src/time/mono_test.go
new file mode 100644
index 0000000..cdbb11e
--- /dev/null
+++ b/src/time/mono_test.go
@@ -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)
+ }
+ }
+}
diff --git a/src/time/sleep.go b/src/time/sleep.go
new file mode 100644
index 0000000..0aec4ca
--- /dev/null
+++ b/src/time/sleep.go
@@ -0,0 +1,178 @@
+// 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
+
+// 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)
+
+// Interface to timers implemented in package runtime.
+// Must be in sync with ../runtime/time.go:/^type timer
+type runtimeTimer struct {
+ pp uintptr
+ when int64
+ period int64
+ f func(any, uintptr) // NOTE: must not be closure
+ arg any
+ seq uintptr
+ nextwhen int64
+ status uint32
+}
+
+// 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
+}
+
+func startTimer(*runtimeTimer)
+func stopTimer(*runtimeTimer) bool
+func resetTimer(*runtimeTimer, int64) bool
+func modTimer(t *runtimeTimer, when, period int64, f func(any, uintptr), arg any, seq uintptr)
+
+// 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
+ r runtimeTimer
+}
+
+// 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.
+// Stop does not close the channel, to prevent a read from the channel succeeding
+// incorrectly.
+//
+// To ensure the channel is empty after a call to Stop, check the
+// return value and drain the channel.
+// For example, assuming the program has not received from t.C already:
+//
+// if !t.Stop() {
+// <-t.C
+// }
+//
+// This cannot be done concurrent to other receives from the Timer's
+// channel or other calls to the Timer's Stop method.
+//
+// For a 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.
+func (t *Timer) Stop() bool {
+ if t.r.f == nil {
+ panic("time: Stop called on uninitialized Timer")
+ }
+ return stopTimer(&t.r)
+}
+
+// NewTimer creates a new Timer that will send
+// the current time on its channel after at least duration d.
+func NewTimer(d Duration) *Timer {
+ c := make(chan Time, 1)
+ t := &Timer{
+ C: c,
+ r: runtimeTimer{
+ when: when(d),
+ f: sendTime,
+ arg: c,
+ },
+ }
+ startTimer(&t.r)
+ 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 Timer created with NewTimer, Reset should be invoked only on
+// stopped or expired timers with drained channels.
+//
+// If a program has already received a value from t.C, the timer is known
+// to have expired and the channel drained, so t.Reset can be used directly.
+// If a program has not yet received a value from t.C, however,
+// the timer must be stopped and—if Stop reports that the timer expired
+// before being stopped—the channel explicitly drained:
+//
+// if !t.Stop() {
+// <-t.C
+// }
+// t.Reset(d)
+//
+// This should not be done concurrent to other receives from the Timer's
+// channel.
+//
+// Note that it is not possible to use Reset's return value correctly, as there
+// is a race condition between draining the channel and the new timer expiring.
+// Reset should always be invoked on stopped or expired channels, as described above.
+// The return value exists to preserve compatibility with existing programs.
+//
+// For a 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.
+func (t *Timer) Reset(d Duration) bool {
+ if t.r.f == nil {
+ panic("time: Reset called on uninitialized Timer")
+ }
+ w := when(d)
+ return resetTimer(&t.r, w)
+}
+
+// sendTime does a non-blocking send of the current time on c.
+func sendTime(c any, seq uintptr) {
+ select {
+ case c.(chan Time) <- Now():
+ 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.
+// The underlying Timer is not recovered by the garbage collector
+// until the timer fires. If efficiency is a concern, use NewTimer
+// instead and call Timer.Stop if the timer is no longer needed.
+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 {
+ t := &Timer{
+ r: runtimeTimer{
+ when: when(d),
+ f: goFunc,
+ arg: f,
+ },
+ }
+ startTimer(&t.r)
+ return t
+}
+
+func goFunc(arg any, seq uintptr) {
+ go arg.(func())()
+}
diff --git a/src/time/sleep_test.go b/src/time/sleep_test.go
new file mode 100644
index 0000000..2f79124
--- /dev/null
+++ b/src/time/sleep_test.go
@@ -0,0 +1,821 @@
+// 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"
+)
+
+// 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).
+// windowsInaccuracy is to ignore such errors.
+const windowsInaccuracy = 17 * Millisecond
+
+func TestSleep(t *testing.T) {
+ const delay = 100 * Millisecond
+ go func() {
+ Sleep(delay / 2)
+ Interrupt()
+ }()
+ start := Now()
+ Sleep(delay)
+ delayadj := delay
+ if runtime.GOOS == "windows" {
+ delayadj -= windowsInaccuracy
+ }
+ duration := Now().Sub(start)
+ if duration < delayadj {
+ t.Fatalf("Sleep(%s) slept for only %s", delay, duration)
+ }
+}
+
+// Test the basic function calling behavior. Correct queueing
+// 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 TestAfterStress(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 benchmark(b *testing.B, bench func(n int)) {
+
+ // 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(func(pb *testing.PB) {
+ for pb.Next() {
+ bench(1000)
+ }
+ })
+ b.StopTimer()
+
+ for _, garbage := range garbageAll {
+ for _, t := range garbage {
+ t.Stop()
+ }
+ }
+}
+
+func BenchmarkAfterFunc(b *testing.B) {
+ benchmark(b, func(n int) {
+ 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(n int) {
+ for i := 0; i < n; i++ {
+ <-After(1)
+ }
+ })
+}
+
+func BenchmarkStop(b *testing.B) {
+ benchmark(b, func(n int) {
+ for i := 0; i < n; i++ {
+ NewTimer(1 * Second).Stop()
+ }
+ })
+}
+
+func BenchmarkSimultaneousAfterFunc(b *testing.B) {
+ benchmark(b, func(n int) {
+ var wg sync.WaitGroup
+ wg.Add(n)
+ for i := 0; i < n; i++ {
+ AfterFunc(0, wg.Done)
+ }
+ wg.Wait()
+ })
+}
+
+func BenchmarkStartStop(b *testing.B) {
+ benchmark(b, func(n int) {
+ timers := make([]*Timer, n)
+ for i := 0; i < n; i++ {
+ timers[i] = AfterFunc(Hour, nil)
+ }
+
+ for i := 0; i < n; i++ {
+ timers[i].Stop()
+ }
+ })
+}
+
+func BenchmarkReset(b *testing.B) {
+ benchmark(b, func(n int) {
+ t := NewTimer(Hour)
+ for i := 0; i < n; i++ {
+ t.Reset(Hour)
+ }
+ t.Stop()
+ })
+}
+
+func BenchmarkSleep(b *testing.B) {
+ benchmark(b, func(n int) {
+ var wg sync.WaitGroup
+ wg.Add(n)
+ for i := 0; i < n; i++ {
+ go func() {
+ Sleep(Nanosecond)
+ wg.Done()
+ }()
+ }
+ wg.Wait()
+ })
+}
+
+func TestAfter(t *testing.T) {
+ const delay = 100 * Millisecond
+ start := Now()
+ end := <-After(delay)
+ delayadj := delay
+ if runtime.GOOS == "windows" {
+ delayadj -= windowsInaccuracy
+ }
+ if duration := Now().Sub(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) {
+ 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) {
+ // 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) {
+ // 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 = testAfterQueuing(delta); 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 testAfterQueuing(delta Duration) error {
+ // make the result channel buffered because we don't want
+ // to depend on channel queueing 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
+ }
+ 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) {
+ 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 := NewTimer(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] = NewTimer(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 {
+ }
+}
diff --git a/src/time/sys_plan9.go b/src/time/sys_plan9.go
new file mode 100644
index 0000000..ba37cf5
--- /dev/null
+++ b/src/time/sys_plan9.go
@@ -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
+}
diff --git a/src/time/sys_unix.go b/src/time/sys_unix.go
new file mode 100644
index 0000000..63634ac
--- /dev/null
+++ b/src/time/sys_unix.go
@@ -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
+}
diff --git a/src/time/sys_windows.go b/src/time/sys_windows.go
new file mode 100644
index 0000000..78e182d
--- /dev/null
+++ b/src/time/sys_windows.go
@@ -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
+}
diff --git a/src/time/testdata/2020b_Europe_Berlin b/src/time/testdata/2020b_Europe_Berlin
new file mode 100644
index 0000000..465546b
--- /dev/null
+++ b/src/time/testdata/2020b_Europe_Berlin
Binary files differ
diff --git a/src/time/testdata/2021a_America_Nuuk b/src/time/testdata/2021a_America_Nuuk
new file mode 100644
index 0000000..4ddc99d
--- /dev/null
+++ b/src/time/testdata/2021a_America_Nuuk
Binary files differ
diff --git a/src/time/testdata/2021a_Asia_Gaza b/src/time/testdata/2021a_Asia_Gaza
new file mode 100644
index 0000000..58e9fdf
--- /dev/null
+++ b/src/time/testdata/2021a_Asia_Gaza
Binary files differ
diff --git a/src/time/testdata/2021a_Europe_Dublin b/src/time/testdata/2021a_Europe_Dublin
new file mode 100644
index 0000000..4a45ea8
--- /dev/null
+++ b/src/time/testdata/2021a_Europe_Dublin
Binary files differ
diff --git a/src/time/tick.go b/src/time/tick.go
new file mode 100644
index 0000000..9da16b5
--- /dev/null
+++ b/src/time/tick.go
@@ -0,0 +1,71 @@
+// 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
+
+// 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.
+ r runtimeTimer
+}
+
+// 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. Stop the ticker to release associated resources.
+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{
+ C: c,
+ r: runtimeTimer{
+ when: when(d),
+ period: int64(d),
+ f: sendTime,
+ arg: c,
+ },
+ }
+ startTimer(&t.r)
+ 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() {
+ stopTimer(&t.r)
+}
+
+// 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.r.f == nil {
+ panic("time: Reset called on uninitialized Ticker")
+ }
+ modTimer(&t.r, when(d), int64(d), t.r.f, t.r.arg, t.r.seq)
+}
+
+// Tick is a convenience wrapper for NewTicker providing access to the ticking
+// channel only. While Tick is useful for clients that have no need to shut down
+// the Ticker, be aware that without a way to shut it down the underlying
+// Ticker cannot be recovered by the garbage collector; it "leaks".
+// Unlike NewTicker, Tick will return nil if d <= 0.
+func Tick(d Duration) <-chan Time {
+ if d <= 0 {
+ return nil
+ }
+ return NewTicker(d).C
+}
diff --git a/src/time/tick_test.go b/src/time/tick_test.go
new file mode 100644
index 0000000..f539091
--- /dev/null
+++ b/src/time/tick_test.go
@@ -0,0 +1,177 @@
+// 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"
+ "testing"
+ . "time"
+)
+
+func TestTicker(t *testing.T) {
+ // 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 i := 0; i < count/2; i++ {
+ <-ticker.C
+ }
+ ticker.Reset(delta * 2)
+ for i := count / 2; i < count; i++ {
+ <-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) {
+ Delta := 100 * Millisecond
+ if testing.Short() {
+ Delta = 20 * Millisecond
+ }
+ for i := 0; i < 3; i++ {
+ 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 BenchmarkTicker(b *testing.B) {
+ benchmark(b, func(n int) {
+ ticker := NewTicker(Nanosecond)
+ for i := 0; i < n; i++ {
+ <-ticker.C
+ }
+ ticker.Stop()
+ })
+}
+
+func BenchmarkTickerReset(b *testing.B) {
+ benchmark(b, func(n int) {
+ ticker := NewTicker(Nanosecond)
+ for i := 0; i < n; i++ {
+ ticker.Reset(Nanosecond * 2)
+ }
+ ticker.Stop()
+ })
+}
+
+func BenchmarkTickerResetNaive(b *testing.B) {
+ benchmark(b, func(n int) {
+ ticker := NewTicker(Nanosecond)
+ for i := 0; i < n; i++ {
+ ticker.Stop()
+ ticker = NewTicker(Nanosecond * 2)
+ }
+ ticker.Stop()
+ })
+}
diff --git a/src/time/time.go b/src/time/time.go
new file mode 100644
index 0000000..9d4c6e9
--- /dev/null
+++ b/src/time/time.go
@@ -0,0 +1,1667 @@
+// 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 provides functionality for measuring and displaying time.
+//
+// The calendrical calculations always assume a Gregorian calendar, with
+// no leap seconds.
+//
+// # Monotonic Clocks
+//
+// Operating systems provide both a “wall clock,” which is subject to
+// changes for clock synchronization, and a “monotonic clock,” which is
+// not. The general rule is that the wall clock is for telling time and
+// the monotonic clock is for measuring time. Rather than split the API,
+// in this package the Time returned by time.Now contains both a wall
+// clock reading and a monotonic clock reading; later time-telling
+// operations use the wall clock reading, but later time-measuring
+// operations, specifically comparisons and subtractions, use the
+// monotonic clock reading.
+//
+// For example, this code always computes a positive elapsed time of
+// approximately 20 milliseconds, even if the wall clock is changed during
+// the operation being timed:
+//
+// start := time.Now()
+// ... operation that takes 20 milliseconds ...
+// t := time.Now()
+// elapsed := t.Sub(start)
+//
+// Other idioms, such as time.Since(start), time.Until(deadline), and
+// time.Now().Before(deadline), are similarly robust against wall clock
+// resets.
+//
+// The rest of this section gives the precise details of how operations
+// use monotonic clocks, but understanding those details is not required
+// to use this package.
+//
+// The Time returned by time.Now contains a monotonic clock reading.
+// If Time t has a monotonic clock reading, t.Add adds the same duration to
+// both the wall clock and monotonic clock readings to compute the result.
+// Because t.AddDate(y, m, d), t.Round(d), and t.Truncate(d) are wall time
+// computations, they always strip any monotonic clock reading from their results.
+// Because t.In, t.Local, and t.UTC are used for their effect on the interpretation
+// of the wall time, they also strip any monotonic clock reading from their results.
+// The canonical way to strip a monotonic clock reading is to use t = t.Round(0).
+//
+// If Times t and u both contain monotonic clock readings, the operations
+// t.After(u), t.Before(u), t.Equal(u), t.Compare(u), and t.Sub(u) are carried out
+// using the monotonic clock readings alone, ignoring the wall clock
+// readings. If either t or u contains no monotonic clock reading, these
+// operations fall back to using the wall clock readings.
+//
+// On some systems the monotonic clock will stop if the computer goes to sleep.
+// On such a system, t.Sub(u) may not accurately reflect the actual
+// time that passed between t and u.
+//
+// Because the monotonic clock reading has no meaning outside
+// the current process, the serialized forms generated by t.GobEncode,
+// t.MarshalBinary, t.MarshalJSON, and t.MarshalText omit the monotonic
+// clock reading, and t.Format provides no format for it. Similarly, the
+// constructors time.Date, time.Parse, time.ParseInLocation, and time.Unix,
+// as well as the unmarshalers t.GobDecode, t.UnmarshalBinary.
+// t.UnmarshalJSON, and t.UnmarshalText always create times with
+// no monotonic clock reading.
+//
+// The monotonic clock reading exists only in Time values. It is not
+// a part of Duration values or the Unix times returned by t.Unix and
+// friends.
+//
+// Note that the Go == operator compares not just the time instant but
+// also the Location and the monotonic clock reading. See the
+// documentation for the Time type for a discussion of equality
+// testing for Time values.
+//
+// For debugging, the result of t.String does include the monotonic
+// clock reading if present. If t != u because of different monotonic clock readings,
+// that difference will be visible when printing t.String() and u.String().
+//
+// # Timer Resolution
+//
+// Timer resolution varies depending on the Go runtime, the operating system
+// and the underlying hardware.
+// On Unix, the resolution is approximately 1ms.
+// On Windows, the default resolution is approximately 16ms, but
+// a higher resolution may be requested using [golang.org/x/sys/windows.TimeBeginPeriod].
+package time
+
+import (
+ "errors"
+ _ "unsafe" // for go:linkname
+)
+
+// A Time represents an instant in time with nanosecond precision.
+//
+// Programs using times should typically store and pass them as values,
+// not pointers. That is, time variables and struct fields should be of
+// type time.Time, not *time.Time.
+//
+// A Time value can be used by multiple goroutines simultaneously except
+// that the methods GobDecode, UnmarshalBinary, UnmarshalJSON and
+// UnmarshalText are not concurrency-safe.
+//
+// Time instants can be compared using the Before, After, and Equal methods.
+// The Sub method subtracts two instants, producing a Duration.
+// The Add method adds a Time and a Duration, producing a Time.
+//
+// The zero value of type Time is January 1, year 1, 00:00:00.000000000 UTC.
+// As this time is unlikely to come up in practice, the IsZero method gives
+// a simple way of detecting a time that has not been initialized explicitly.
+//
+// Each time has an associated Location. The methods Local, UTC, and In return a
+// Time with a specific Location. Changing the Location of a Time value with
+// these methods does not change the actual instant it represents, only the time
+// zone in which to interpret it.
+//
+// Representations of a Time value saved by the GobEncode, MarshalBinary,
+// MarshalJSON, and MarshalText methods store the Time.Location's offset, but not
+// the location name. They therefore lose information about Daylight Saving Time.
+//
+// In addition to the required “wall clock” reading, a Time may contain an optional
+// reading of the current process's monotonic clock, to provide additional precision
+// for comparison or subtraction.
+// See the “Monotonic Clocks” section in the package documentation for details.
+//
+// Note that the Go == operator compares not just the time instant but also the
+// Location and the monotonic clock reading. Therefore, Time values should not
+// be used as map or database keys without first guaranteeing that the
+// identical Location has been set for all values, which can be achieved
+// through use of the UTC or Local method, and that the monotonic clock reading
+// has been stripped by setting t = t.Round(0). In general, prefer t.Equal(u)
+// to t == u, since t.Equal uses the most accurate comparison available and
+// correctly handles the case when only one of its arguments has a monotonic
+// clock reading.
+type Time struct {
+ // wall and ext encode the wall time seconds, wall time nanoseconds,
+ // and optional monotonic clock reading in nanoseconds.
+ //
+ // From high to low bit position, wall encodes a 1-bit flag (hasMonotonic),
+ // a 33-bit seconds field, and a 30-bit wall time nanoseconds field.
+ // The nanoseconds field is in the range [0, 999999999].
+ // If the hasMonotonic bit is 0, then the 33-bit field must be zero
+ // and the full signed 64-bit wall seconds since Jan 1 year 1 is stored in ext.
+ // If the hasMonotonic bit is 1, then the 33-bit field holds a 33-bit
+ // unsigned wall seconds since Jan 1 year 1885, and ext holds a
+ // signed 64-bit monotonic clock reading, nanoseconds since process start.
+ wall uint64
+ ext int64
+
+ // loc specifies the Location that should be used to
+ // determine the minute, hour, month, day, and year
+ // that correspond to this Time.
+ // The nil location means UTC.
+ // All UTC times are represented with loc==nil, never loc==&utcLoc.
+ loc *Location
+}
+
+const (
+ hasMonotonic = 1 << 63
+ maxWall = wallToInternal + (1<<33 - 1) // year 2157
+ minWall = wallToInternal // year 1885
+ nsecMask = 1<<30 - 1
+ nsecShift = 30
+)
+
+// These helpers for manipulating the wall and monotonic clock readings
+// take pointer receivers, even when they don't modify the time,
+// to make them cheaper to call.
+
+// nsec returns the time's nanoseconds.
+func (t *Time) nsec() int32 {
+ return int32(t.wall & nsecMask)
+}
+
+// sec returns the time's seconds since Jan 1 year 1.
+func (t *Time) sec() int64 {
+ if t.wall&hasMonotonic != 0 {
+ return wallToInternal + int64(t.wall<<1>>(nsecShift+1))
+ }
+ return t.ext
+}
+
+// unixSec returns the time's seconds since Jan 1 1970 (Unix time).
+func (t *Time) unixSec() int64 { return t.sec() + internalToUnix }
+
+// addSec adds d seconds to the time.
+func (t *Time) addSec(d int64) {
+ if t.wall&hasMonotonic != 0 {
+ sec := int64(t.wall << 1 >> (nsecShift + 1))
+ dsec := sec + d
+ if 0 <= dsec && dsec <= 1<<33-1 {
+ t.wall = t.wall&nsecMask | uint64(dsec)<<nsecShift | hasMonotonic
+ return
+ }
+ // Wall second now out of range for packed field.
+ // Move to ext.
+ t.stripMono()
+ }
+
+ // Check if the sum of t.ext and d overflows and handle it properly.
+ sum := t.ext + d
+ if (sum > t.ext) == (d > 0) {
+ t.ext = sum
+ } else if d > 0 {
+ t.ext = 1<<63 - 1
+ } else {
+ t.ext = -(1<<63 - 1)
+ }
+}
+
+// setLoc sets the location associated with the time.
+func (t *Time) setLoc(loc *Location) {
+ if loc == &utcLoc {
+ loc = nil
+ }
+ t.stripMono()
+ t.loc = loc
+}
+
+// stripMono strips the monotonic clock reading in t.
+func (t *Time) stripMono() {
+ if t.wall&hasMonotonic != 0 {
+ t.ext = t.sec()
+ t.wall &= nsecMask
+ }
+}
+
+// setMono sets the monotonic clock reading in t.
+// If t cannot hold a monotonic clock reading,
+// because its wall time is too large,
+// setMono is a no-op.
+func (t *Time) setMono(m int64) {
+ if t.wall&hasMonotonic == 0 {
+ sec := t.ext
+ if sec < minWall || maxWall < sec {
+ return
+ }
+ t.wall |= hasMonotonic | uint64(sec-minWall)<<nsecShift
+ }
+ t.ext = m
+}
+
+// mono returns t's monotonic clock reading.
+// It returns 0 for a missing reading.
+// This function is used only for testing,
+// so it's OK that technically 0 is a valid
+// monotonic clock reading as well.
+func (t *Time) mono() int64 {
+ if t.wall&hasMonotonic == 0 {
+ return 0
+ }
+ return t.ext
+}
+
+// After reports whether the time instant t is after u.
+func (t Time) After(u Time) bool {
+ if t.wall&u.wall&hasMonotonic != 0 {
+ return t.ext > u.ext
+ }
+ ts := t.sec()
+ us := u.sec()
+ return ts > us || ts == us && t.nsec() > u.nsec()
+}
+
+// Before reports whether the time instant t is before u.
+func (t Time) Before(u Time) bool {
+ if t.wall&u.wall&hasMonotonic != 0 {
+ return t.ext < u.ext
+ }
+ ts := t.sec()
+ us := u.sec()
+ return ts < us || ts == us && t.nsec() < u.nsec()
+}
+
+// Compare compares the time instant t with u. If t is before u, it returns -1;
+// if t is after u, it returns +1; if they're the same, it returns 0.
+func (t Time) Compare(u Time) int {
+ var tc, uc int64
+ if t.wall&u.wall&hasMonotonic != 0 {
+ tc, uc = t.ext, u.ext
+ } else {
+ tc, uc = t.sec(), u.sec()
+ if tc == uc {
+ tc, uc = int64(t.nsec()), int64(u.nsec())
+ }
+ }
+ switch {
+ case tc < uc:
+ return -1
+ case tc > uc:
+ return +1
+ }
+ return 0
+}
+
+// Equal reports whether t and u represent the same time instant.
+// Two times can be equal even if they are in different locations.
+// For example, 6:00 +0200 and 4:00 UTC are Equal.
+// See the documentation on the Time type for the pitfalls of using == with
+// Time values; most code should use Equal instead.
+func (t Time) Equal(u Time) bool {
+ if t.wall&u.wall&hasMonotonic != 0 {
+ return t.ext == u.ext
+ }
+ return t.sec() == u.sec() && t.nsec() == u.nsec()
+}
+
+// A Month specifies a month of the year (January = 1, ...).
+type Month int
+
+const (
+ January Month = 1 + iota
+ February
+ March
+ April
+ May
+ June
+ July
+ August
+ September
+ October
+ November
+ December
+)
+
+// String returns the English name of the month ("January", "February", ...).
+func (m Month) String() string {
+ if January <= m && m <= December {
+ return longMonthNames[m-1]
+ }
+ buf := make([]byte, 20)
+ n := fmtInt(buf, uint64(m))
+ return "%!Month(" + string(buf[n:]) + ")"
+}
+
+// A Weekday specifies a day of the week (Sunday = 0, ...).
+type Weekday int
+
+const (
+ Sunday Weekday = iota
+ Monday
+ Tuesday
+ Wednesday
+ Thursday
+ Friday
+ Saturday
+)
+
+// String returns the English name of the day ("Sunday", "Monday", ...).
+func (d Weekday) String() string {
+ if Sunday <= d && d <= Saturday {
+ return longDayNames[d]
+ }
+ buf := make([]byte, 20)
+ n := fmtInt(buf, uint64(d))
+ return "%!Weekday(" + string(buf[n:]) + ")"
+}
+
+// Computations on time.
+//
+// The zero value for a Time is defined to be
+// January 1, year 1, 00:00:00.000000000 UTC
+// which (1) looks like a zero, or as close as you can get in a date
+// (1-1-1 00:00:00 UTC), (2) is unlikely enough to arise in practice to
+// be a suitable "not set" sentinel, unlike Jan 1 1970, and (3) has a
+// non-negative year even in time zones west of UTC, unlike 1-1-0
+// 00:00:00 UTC, which would be 12-31-(-1) 19:00:00 in New York.
+//
+// The zero Time value does not force a specific epoch for the time
+// representation. For example, to use the Unix epoch internally, we
+// could define that to distinguish a zero value from Jan 1 1970, that
+// time would be represented by sec=-1, nsec=1e9. However, it does
+// suggest a representation, namely using 1-1-1 00:00:00 UTC as the
+// epoch, and that's what we do.
+//
+// The Add and Sub computations are oblivious to the choice of epoch.
+//
+// The presentation computations - year, month, minute, and so on - all
+// rely heavily on division and modulus by positive constants. For
+// calendrical calculations we want these divisions to round down, even
+// for negative values, so that the remainder is always positive, but
+// Go's division (like most hardware division instructions) rounds to
+// zero. We can still do those computations and then adjust the result
+// for a negative numerator, but it's annoying to write the adjustment
+// over and over. Instead, we can change to a different epoch so long
+// ago that all the times we care about will be positive, and then round
+// to zero and round down coincide. These presentation routines already
+// have to add the zone offset, so adding the translation to the
+// alternate epoch is cheap. For example, having a non-negative time t
+// means that we can write
+//
+// sec = t % 60
+//
+// instead of
+//
+// sec = t % 60
+// if sec < 0 {
+// sec += 60
+// }
+//
+// everywhere.
+//
+// The calendar runs on an exact 400 year cycle: a 400-year calendar
+// printed for 1970-2369 will apply as well to 2370-2769. Even the days
+// of the week match up. It simplifies the computations to choose the
+// cycle boundaries so that the exceptional years are always delayed as
+// long as possible. That means choosing a year equal to 1 mod 400, so
+// that the first leap year is the 4th year, the first missed leap year
+// is the 100th year, and the missed missed leap year is the 400th year.
+// So we'd prefer instead to print a calendar for 2001-2400 and reuse it
+// for 2401-2800.
+//
+// Finally, it's convenient if the delta between the Unix epoch and
+// long-ago epoch is representable by an int64 constant.
+//
+// These three considerations—choose an epoch as early as possible, that
+// uses a year equal to 1 mod 400, and that is no more than 2⁶³ seconds
+// earlier than 1970—bring us to the year -292277022399. We refer to
+// this year as the absolute zero year, and to times measured as a uint64
+// seconds since this year as absolute times.
+//
+// Times measured as an int64 seconds since the year 1—the representation
+// used for Time's sec field—are called internal times.
+//
+// Times measured as an int64 seconds since the year 1970 are called Unix
+// times.
+//
+// It is tempting to just use the year 1 as the absolute epoch, defining
+// that the routines are only valid for years >= 1. However, the
+// routines would then be invalid when displaying the epoch in time zones
+// west of UTC, since it is year 0. It doesn't seem tenable to say that
+// printing the zero time correctly isn't supported in half the time
+// zones. By comparison, it's reasonable to mishandle some times in
+// the year -292277022399.
+//
+// All this is opaque to clients of the API and can be changed if a
+// better implementation presents itself.
+
+const (
+ // The unsigned zero year for internal calculations.
+ // Must be 1 mod 400, and times before it will not compute correctly,
+ // but otherwise can be changed at will.
+ absoluteZeroYear = -292277022399
+
+ // The year of the zero Time.
+ // Assumed by the unixToInternal computation below.
+ internalYear = 1
+
+ // Offsets to convert between internal and absolute or Unix times.
+ absoluteToInternal int64 = (absoluteZeroYear - internalYear) * 365.2425 * secondsPerDay
+ internalToAbsolute = -absoluteToInternal
+
+ unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay
+ internalToUnix int64 = -unixToInternal
+
+ wallToInternal int64 = (1884*365 + 1884/4 - 1884/100 + 1884/400) * secondsPerDay
+)
+
+// IsZero reports whether t represents the zero time instant,
+// January 1, year 1, 00:00:00 UTC.
+func (t Time) IsZero() bool {
+ return t.sec() == 0 && t.nsec() == 0
+}
+
+// abs returns the time t as an absolute time, adjusted by the zone offset.
+// It is called when computing a presentation property like Month or Hour.
+func (t Time) abs() uint64 {
+ l := t.loc
+ // Avoid function calls when possible.
+ if l == nil || l == &localLoc {
+ l = l.get()
+ }
+ sec := t.unixSec()
+ if l != &utcLoc {
+ if l.cacheZone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
+ sec += int64(l.cacheZone.offset)
+ } else {
+ _, offset, _, _, _ := l.lookup(sec)
+ sec += int64(offset)
+ }
+ }
+ return uint64(sec + (unixToInternal + internalToAbsolute))
+}
+
+// locabs is a combination of the Zone and abs methods,
+// extracting both return values from a single zone lookup.
+func (t Time) locabs() (name string, offset int, abs uint64) {
+ l := t.loc
+ if l == nil || l == &localLoc {
+ l = l.get()
+ }
+ // Avoid function call if we hit the local time cache.
+ sec := t.unixSec()
+ if l != &utcLoc {
+ if l.cacheZone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
+ name = l.cacheZone.name
+ offset = l.cacheZone.offset
+ } else {
+ name, offset, _, _, _ = l.lookup(sec)
+ }
+ sec += int64(offset)
+ } else {
+ name = "UTC"
+ }
+ abs = uint64(sec + (unixToInternal + internalToAbsolute))
+ return
+}
+
+// Date returns the year, month, and day in which t occurs.
+func (t Time) Date() (year int, month Month, day int) {
+ year, month, day, _ = t.date(true)
+ return
+}
+
+// Year returns the year in which t occurs.
+func (t Time) Year() int {
+ year, _, _, _ := t.date(false)
+ return year
+}
+
+// Month returns the month of the year specified by t.
+func (t Time) Month() Month {
+ _, month, _, _ := t.date(true)
+ return month
+}
+
+// Day returns the day of the month specified by t.
+func (t Time) Day() int {
+ _, _, day, _ := t.date(true)
+ return day
+}
+
+// Weekday returns the day of the week specified by t.
+func (t Time) Weekday() Weekday {
+ return absWeekday(t.abs())
+}
+
+// absWeekday is like Weekday but operates on an absolute time.
+func absWeekday(abs uint64) Weekday {
+ // January 1 of the absolute year, like January 1 of 2001, was a Monday.
+ sec := (abs + uint64(Monday)*secondsPerDay) % secondsPerWeek
+ return Weekday(int(sec) / secondsPerDay)
+}
+
+// ISOWeek returns the ISO 8601 year and week number in which t occurs.
+// Week ranges from 1 to 53. Jan 01 to Jan 03 of year n might belong to
+// week 52 or 53 of year n-1, and Dec 29 to Dec 31 might belong to week 1
+// of year n+1.
+func (t Time) ISOWeek() (year, week int) {
+ // According to the rule that the first calendar week of a calendar year is
+ // the week including the first Thursday of that year, and that the last one is
+ // the week immediately preceding the first calendar week of the next calendar year.
+ // See https://www.iso.org/obp/ui#iso:std:iso:8601:-1:ed-1:v1:en:term:3.1.1.23 for details.
+
+ // weeks start with Monday
+ // Monday Tuesday Wednesday Thursday Friday Saturday Sunday
+ // 1 2 3 4 5 6 7
+ // +3 +2 +1 0 -1 -2 -3
+ // the offset to Thursday
+ abs := t.abs()
+ d := Thursday - absWeekday(abs)
+ // handle Sunday
+ if d == 4 {
+ d = -3
+ }
+ // find the Thursday of the calendar week
+ abs += uint64(d) * secondsPerDay
+ year, _, _, yday := absDate(abs, false)
+ return year, yday/7 + 1
+}
+
+// Clock returns the hour, minute, and second within the day specified by t.
+func (t Time) Clock() (hour, min, sec int) {
+ return absClock(t.abs())
+}
+
+// absClock is like clock but operates on an absolute time.
+func absClock(abs uint64) (hour, min, sec int) {
+ sec = int(abs % secondsPerDay)
+ hour = sec / secondsPerHour
+ sec -= hour * secondsPerHour
+ min = sec / secondsPerMinute
+ sec -= min * secondsPerMinute
+ return
+}
+
+// Hour returns the hour within the day specified by t, in the range [0, 23].
+func (t Time) Hour() int {
+ return int(t.abs()%secondsPerDay) / secondsPerHour
+}
+
+// Minute returns the minute offset within the hour specified by t, in the range [0, 59].
+func (t Time) Minute() int {
+ return int(t.abs()%secondsPerHour) / secondsPerMinute
+}
+
+// Second returns the second offset within the minute specified by t, in the range [0, 59].
+func (t Time) Second() int {
+ return int(t.abs() % secondsPerMinute)
+}
+
+// Nanosecond returns the nanosecond offset within the second specified by t,
+// in the range [0, 999999999].
+func (t Time) Nanosecond() int {
+ return int(t.nsec())
+}
+
+// YearDay returns the day of the year specified by t, in the range [1,365] for non-leap years,
+// and [1,366] in leap years.
+func (t Time) YearDay() int {
+ _, _, _, yday := t.date(false)
+ return yday + 1
+}
+
+// A Duration represents the elapsed time between two instants
+// as an int64 nanosecond count. The representation limits the
+// largest representable duration to approximately 290 years.
+type Duration int64
+
+const (
+ minDuration Duration = -1 << 63
+ maxDuration Duration = 1<<63 - 1
+)
+
+// Common durations. There is no definition for units of Day or larger
+// to avoid confusion across daylight savings time zone transitions.
+//
+// To count the number of units in a Duration, divide:
+//
+// second := time.Second
+// fmt.Print(int64(second/time.Millisecond)) // prints 1000
+//
+// To convert an integer number of units to a Duration, multiply:
+//
+// seconds := 10
+// fmt.Print(time.Duration(seconds)*time.Second) // prints 10s
+const (
+ Nanosecond Duration = 1
+ Microsecond = 1000 * Nanosecond
+ Millisecond = 1000 * Microsecond
+ Second = 1000 * Millisecond
+ Minute = 60 * Second
+ Hour = 60 * Minute
+)
+
+// String returns a string representing the duration in the form "72h3m0.5s".
+// Leading zero units are omitted. As a special case, durations less than one
+// second format use a smaller unit (milli-, micro-, or nanoseconds) to ensure
+// that the leading digit is non-zero. The zero duration formats as 0s.
+func (d Duration) String() string {
+ // This is inlinable to take advantage of "function outlining".
+ // Thus, the caller can decide whether a string must be heap allocated.
+ var arr [32]byte
+ n := d.format(&arr)
+ return string(arr[n:])
+}
+
+// format formats the representation of d into the end of buf and
+// returns the offset of the first character.
+func (d Duration) format(buf *[32]byte) int {
+ // Largest time is 2540400h10m10.000000000s
+ w := len(buf)
+
+ u := uint64(d)
+ neg := d < 0
+ if neg {
+ u = -u
+ }
+
+ if u < uint64(Second) {
+ // Special case: if duration is smaller than a second,
+ // use smaller units, like 1.2ms
+ var prec int
+ w--
+ buf[w] = 's'
+ w--
+ switch {
+ case u == 0:
+ buf[w] = '0'
+ return w
+ case u < uint64(Microsecond):
+ // print nanoseconds
+ prec = 0
+ buf[w] = 'n'
+ case u < uint64(Millisecond):
+ // print microseconds
+ prec = 3
+ // U+00B5 'µ' micro sign == 0xC2 0xB5
+ w-- // Need room for two bytes.
+ copy(buf[w:], "µ")
+ default:
+ // print milliseconds
+ prec = 6
+ buf[w] = 'm'
+ }
+ w, u = fmtFrac(buf[:w], u, prec)
+ w = fmtInt(buf[:w], u)
+ } else {
+ w--
+ buf[w] = 's'
+
+ w, u = fmtFrac(buf[:w], u, 9)
+
+ // u is now integer seconds
+ w = fmtInt(buf[:w], u%60)
+ u /= 60
+
+ // u is now integer minutes
+ if u > 0 {
+ w--
+ buf[w] = 'm'
+ w = fmtInt(buf[:w], u%60)
+ u /= 60
+
+ // u is now integer hours
+ // Stop at hours because days can be different lengths.
+ if u > 0 {
+ w--
+ buf[w] = 'h'
+ w = fmtInt(buf[:w], u)
+ }
+ }
+ }
+
+ if neg {
+ w--
+ buf[w] = '-'
+ }
+
+ return w
+}
+
+// fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the
+// tail of buf, omitting trailing zeros. It omits the decimal
+// point too when the fraction is 0. It returns the index where the
+// output bytes begin and the value v/10**prec.
+func fmtFrac(buf []byte, v uint64, prec int) (nw int, nv uint64) {
+ // Omit trailing zeros up to and including decimal point.
+ w := len(buf)
+ print := false
+ for i := 0; i < prec; i++ {
+ digit := v % 10
+ print = print || digit != 0
+ if print {
+ w--
+ buf[w] = byte(digit) + '0'
+ }
+ v /= 10
+ }
+ if print {
+ w--
+ buf[w] = '.'
+ }
+ return w, v
+}
+
+// fmtInt formats v into the tail of buf.
+// It returns the index where the output begins.
+func fmtInt(buf []byte, v uint64) int {
+ w := len(buf)
+ if v == 0 {
+ w--
+ buf[w] = '0'
+ } else {
+ for v > 0 {
+ w--
+ buf[w] = byte(v%10) + '0'
+ v /= 10
+ }
+ }
+ return w
+}
+
+// Nanoseconds returns the duration as an integer nanosecond count.
+func (d Duration) Nanoseconds() int64 { return int64(d) }
+
+// Microseconds returns the duration as an integer microsecond count.
+func (d Duration) Microseconds() int64 { return int64(d) / 1e3 }
+
+// Milliseconds returns the duration as an integer millisecond count.
+func (d Duration) Milliseconds() int64 { return int64(d) / 1e6 }
+
+// These methods return float64 because the dominant
+// use case is for printing a floating point number like 1.5s, and
+// a truncation to integer would make them not useful in those cases.
+// Splitting the integer and fraction ourselves guarantees that
+// converting the returned float64 to an integer rounds the same
+// way that a pure integer conversion would have, even in cases
+// where, say, float64(d.Nanoseconds())/1e9 would have rounded
+// differently.
+
+// Seconds returns the duration as a floating point number of seconds.
+func (d Duration) Seconds() float64 {
+ sec := d / Second
+ nsec := d % Second
+ return float64(sec) + float64(nsec)/1e9
+}
+
+// Minutes returns the duration as a floating point number of minutes.
+func (d Duration) Minutes() float64 {
+ min := d / Minute
+ nsec := d % Minute
+ return float64(min) + float64(nsec)/(60*1e9)
+}
+
+// Hours returns the duration as a floating point number of hours.
+func (d Duration) Hours() float64 {
+ hour := d / Hour
+ nsec := d % Hour
+ return float64(hour) + float64(nsec)/(60*60*1e9)
+}
+
+// Truncate returns the result of rounding d toward zero to a multiple of m.
+// If m <= 0, Truncate returns d unchanged.
+func (d Duration) Truncate(m Duration) Duration {
+ if m <= 0 {
+ return d
+ }
+ return d - d%m
+}
+
+// lessThanHalf reports whether x+x < y but avoids overflow,
+// assuming x and y are both positive (Duration is signed).
+func lessThanHalf(x, y Duration) bool {
+ return uint64(x)+uint64(x) < uint64(y)
+}
+
+// Round returns the result of rounding d to the nearest multiple of m.
+// The rounding behavior for halfway values is to round away from zero.
+// If the result exceeds the maximum (or minimum)
+// value that can be stored in a Duration,
+// Round returns the maximum (or minimum) duration.
+// If m <= 0, Round returns d unchanged.
+func (d Duration) Round(m Duration) Duration {
+ if m <= 0 {
+ return d
+ }
+ r := d % m
+ if d < 0 {
+ r = -r
+ if lessThanHalf(r, m) {
+ return d + r
+ }
+ if d1 := d - m + r; d1 < d {
+ return d1
+ }
+ return minDuration // overflow
+ }
+ if lessThanHalf(r, m) {
+ return d - r
+ }
+ if d1 := d + m - r; d1 > d {
+ return d1
+ }
+ return maxDuration // overflow
+}
+
+// Abs returns the absolute value of d.
+// As a special case, math.MinInt64 is converted to math.MaxInt64.
+func (d Duration) Abs() Duration {
+ switch {
+ case d >= 0:
+ return d
+ case d == minDuration:
+ return maxDuration
+ default:
+ return -d
+ }
+}
+
+// Add returns the time t+d.
+func (t Time) Add(d Duration) Time {
+ dsec := int64(d / 1e9)
+ nsec := t.nsec() + int32(d%1e9)
+ if nsec >= 1e9 {
+ dsec++
+ nsec -= 1e9
+ } else if nsec < 0 {
+ dsec--
+ nsec += 1e9
+ }
+ t.wall = t.wall&^nsecMask | uint64(nsec) // update nsec
+ t.addSec(dsec)
+ if t.wall&hasMonotonic != 0 {
+ te := t.ext + int64(d)
+ if d < 0 && te > t.ext || d > 0 && te < t.ext {
+ // Monotonic clock reading now out of range; degrade to wall-only.
+ t.stripMono()
+ } else {
+ t.ext = te
+ }
+ }
+ return t
+}
+
+// Sub returns the duration t-u. If the result exceeds the maximum (or minimum)
+// value that can be stored in a Duration, the maximum (or minimum) duration
+// will be returned.
+// To compute t-d for a duration d, use t.Add(-d).
+func (t Time) Sub(u Time) Duration {
+ if t.wall&u.wall&hasMonotonic != 0 {
+ return subMono(t.ext, u.ext)
+ }
+ d := Duration(t.sec()-u.sec())*Second + Duration(t.nsec()-u.nsec())
+ // Check for overflow or underflow.
+ switch {
+ case u.Add(d).Equal(t):
+ return d // d is correct
+ case t.Before(u):
+ return minDuration // t - u is negative out of range
+ default:
+ return maxDuration // t - u is positive out of range
+ }
+}
+
+func subMono(t, u int64) Duration {
+ d := Duration(t - u)
+ if d < 0 && t > u {
+ return maxDuration // t - u is positive out of range
+ }
+ if d > 0 && t < u {
+ return minDuration // t - u is negative out of range
+ }
+ return d
+}
+
+// Since returns the time elapsed since t.
+// It is shorthand for time.Now().Sub(t).
+func Since(t Time) Duration {
+ if t.wall&hasMonotonic != 0 {
+ // Common case optimization: if t has monotonic time, then Sub will use only it.
+ return subMono(runtimeNano()-startNano, t.ext)
+ }
+ return Now().Sub(t)
+}
+
+// Until returns the duration until t.
+// It is shorthand for t.Sub(time.Now()).
+func Until(t Time) Duration {
+ if t.wall&hasMonotonic != 0 {
+ // Common case optimization: if t has monotonic time, then Sub will use only it.
+ return subMono(t.ext, runtimeNano()-startNano)
+ }
+ return t.Sub(Now())
+}
+
+// AddDate returns the time corresponding to adding the
+// given number of years, months, and days to t.
+// For example, AddDate(-1, 2, 3) applied to January 1, 2011
+// returns March 4, 2010.
+//
+// Note that dates are fundamentally coupled to timezones, and calendrical
+// periods like days don't have fixed durations. AddDate uses the Location of
+// the Time value to determine these durations. That means that the same
+// AddDate arguments can produce a different shift in absolute time depending on
+// the base Time value and its Location. For example, AddDate(0, 0, 1) applied
+// to 12:00 on March 27 always returns 12:00 on March 28. At some locations and
+// in some years this is a 24 hour shift. In others it's a 23 hour shift due to
+// daylight savings time transitions.
+//
+// AddDate normalizes its result in the same way that Date does,
+// so, for example, adding one month to October 31 yields
+// December 1, the normalized form for November 31.
+func (t Time) AddDate(years int, months int, days int) Time {
+ year, month, day := t.Date()
+ hour, min, sec := t.Clock()
+ return Date(year+years, month+Month(months), day+days, hour, min, sec, int(t.nsec()), t.Location())
+}
+
+const (
+ secondsPerMinute = 60
+ secondsPerHour = 60 * secondsPerMinute
+ secondsPerDay = 24 * secondsPerHour
+ secondsPerWeek = 7 * secondsPerDay
+ daysPer400Years = 365*400 + 97
+ daysPer100Years = 365*100 + 24
+ daysPer4Years = 365*4 + 1
+)
+
+// date computes the year, day of year, and when full=true,
+// the month and day in which t occurs.
+func (t Time) date(full bool) (year int, month Month, day int, yday int) {
+ return absDate(t.abs(), full)
+}
+
+// absDate is like date but operates on an absolute time.
+func absDate(abs uint64, full bool) (year int, month Month, day int, yday int) {
+ // Split into time and day.
+ d := abs / secondsPerDay
+
+ // Account for 400 year cycles.
+ n := d / daysPer400Years
+ y := 400 * n
+ d -= daysPer400Years * n
+
+ // Cut off 100-year cycles.
+ // The last cycle has one extra leap year, so on the last day
+ // of that year, day / daysPer100Years will be 4 instead of 3.
+ // Cut it back down to 3 by subtracting n>>2.
+ n = d / daysPer100Years
+ n -= n >> 2
+ y += 100 * n
+ d -= daysPer100Years * n
+
+ // Cut off 4-year cycles.
+ // The last cycle has a missing leap year, which does not
+ // affect the computation.
+ n = d / daysPer4Years
+ y += 4 * n
+ d -= daysPer4Years * n
+
+ // Cut off years within a 4-year cycle.
+ // The last year is a leap year, so on the last day of that year,
+ // day / 365 will be 4 instead of 3. Cut it back down to 3
+ // by subtracting n>>2.
+ n = d / 365
+ n -= n >> 2
+ y += n
+ d -= 365 * n
+
+ year = int(int64(y) + absoluteZeroYear)
+ yday = int(d)
+
+ if !full {
+ return
+ }
+
+ day = yday
+ if isLeap(year) {
+ // Leap year
+ switch {
+ case day > 31+29-1:
+ // After leap day; pretend it wasn't there.
+ day--
+ case day == 31+29-1:
+ // Leap day.
+ month = February
+ day = 29
+ return
+ }
+ }
+
+ // Estimate month on assumption that every month has 31 days.
+ // The estimate may be too low by at most one month, so adjust.
+ month = Month(day / 31)
+ end := int(daysBefore[month+1])
+ var begin int
+ if day >= end {
+ month++
+ begin = end
+ } else {
+ begin = int(daysBefore[month])
+ }
+
+ month++ // because January is 1
+ day = day - begin + 1
+ return
+}
+
+// daysBefore[m] counts the number of days in a non-leap year
+// before month m begins. There is an entry for m=12, counting
+// the number of days before January of next year (365).
+var daysBefore = [...]int32{
+ 0,
+ 31,
+ 31 + 28,
+ 31 + 28 + 31,
+ 31 + 28 + 31 + 30,
+ 31 + 28 + 31 + 30 + 31,
+ 31 + 28 + 31 + 30 + 31 + 30,
+ 31 + 28 + 31 + 30 + 31 + 30 + 31,
+ 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
+ 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
+ 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
+ 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
+ 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,
+}
+
+func daysIn(m Month, year int) int {
+ if m == February && isLeap(year) {
+ return 29
+ }
+ return int(daysBefore[m] - daysBefore[m-1])
+}
+
+// daysSinceEpoch takes a year and returns the number of days from
+// the absolute epoch to the start of that year.
+// This is basically (year - zeroYear) * 365, but accounting for leap days.
+func daysSinceEpoch(year int) uint64 {
+ y := uint64(int64(year) - absoluteZeroYear)
+
+ // Add in days from 400-year cycles.
+ n := y / 400
+ y -= 400 * n
+ d := daysPer400Years * n
+
+ // Add in 100-year cycles.
+ n = y / 100
+ y -= 100 * n
+ d += daysPer100Years * n
+
+ // Add in 4-year cycles.
+ n = y / 4
+ y -= 4 * n
+ d += daysPer4Years * n
+
+ // Add in non-leap years.
+ n = y
+ d += 365 * n
+
+ return d
+}
+
+// Provided by package runtime.
+func now() (sec int64, nsec int32, mono int64)
+
+// runtimeNano returns the current value of the runtime clock in nanoseconds.
+//
+//go:linkname runtimeNano runtime.nanotime
+func runtimeNano() int64
+
+// Monotonic times are reported as offsets from startNano.
+// We initialize startNano to runtimeNano() - 1 so that on systems where
+// monotonic time resolution is fairly low (e.g. Windows 2008
+// which appears to have a default resolution of 15ms),
+// we avoid ever reporting a monotonic time of 0.
+// (Callers may want to use 0 as "time not set".)
+var startNano int64 = runtimeNano() - 1
+
+// Now returns the current local time.
+func Now() Time {
+ sec, nsec, mono := now()
+ mono -= startNano
+ sec += unixToInternal - minWall
+ if uint64(sec)>>33 != 0 {
+ // Seconds field overflowed the 33 bits available when
+ // storing a monotonic time. This will be true after
+ // March 16, 2157.
+ return Time{uint64(nsec), sec + minWall, Local}
+ }
+ return Time{hasMonotonic | uint64(sec)<<nsecShift | uint64(nsec), mono, Local}
+}
+
+func unixTime(sec int64, nsec int32) Time {
+ return Time{uint64(nsec), sec + unixToInternal, Local}
+}
+
+// UTC returns t with the location set to UTC.
+func (t Time) UTC() Time {
+ t.setLoc(&utcLoc)
+ return t
+}
+
+// Local returns t with the location set to local time.
+func (t Time) Local() Time {
+ t.setLoc(Local)
+ return t
+}
+
+// In returns a copy of t representing the same time instant, but
+// with the copy's location information set to loc for display
+// purposes.
+//
+// In panics if loc is nil.
+func (t Time) In(loc *Location) Time {
+ if loc == nil {
+ panic("time: missing Location in call to Time.In")
+ }
+ t.setLoc(loc)
+ return t
+}
+
+// Location returns the time zone information associated with t.
+func (t Time) Location() *Location {
+ l := t.loc
+ if l == nil {
+ l = UTC
+ }
+ return l
+}
+
+// Zone computes the time zone in effect at time t, returning the abbreviated
+// name of the zone (such as "CET") and its offset in seconds east of UTC.
+func (t Time) Zone() (name string, offset int) {
+ name, offset, _, _, _ = t.loc.lookup(t.unixSec())
+ return
+}
+
+// ZoneBounds returns the bounds of the time zone in effect at time t.
+// The zone begins at start and the next zone begins at end.
+// If the zone begins at the beginning of time, start will be returned as a zero Time.
+// If the zone goes on forever, end will be returned as a zero Time.
+// The Location of the returned times will be the same as t.
+func (t Time) ZoneBounds() (start, end Time) {
+ _, _, startSec, endSec, _ := t.loc.lookup(t.unixSec())
+ if startSec != alpha {
+ start = unixTime(startSec, 0)
+ start.setLoc(t.loc)
+ }
+ if endSec != omega {
+ end = unixTime(endSec, 0)
+ end.setLoc(t.loc)
+ }
+ return
+}
+
+// Unix returns t as a Unix time, the number of seconds elapsed
+// since January 1, 1970 UTC. The result does not depend on the
+// location associated with t.
+// Unix-like operating systems often record time as a 32-bit
+// count of seconds, but since the method here returns a 64-bit
+// value it is valid for billions of years into the past or future.
+func (t Time) Unix() int64 {
+ return t.unixSec()
+}
+
+// UnixMilli returns t as a Unix time, the number of milliseconds elapsed since
+// January 1, 1970 UTC. The result is undefined if the Unix time in
+// milliseconds cannot be represented by an int64 (a date more than 292 million
+// years before or after 1970). The result does not depend on the
+// location associated with t.
+func (t Time) UnixMilli() int64 {
+ return t.unixSec()*1e3 + int64(t.nsec())/1e6
+}
+
+// UnixMicro returns t as a Unix time, the number of microseconds elapsed since
+// January 1, 1970 UTC. The result is undefined if the Unix time in
+// microseconds cannot be represented by an int64 (a date before year -290307 or
+// after year 294246). The result does not depend on the location associated
+// with t.
+func (t Time) UnixMicro() int64 {
+ return t.unixSec()*1e6 + int64(t.nsec())/1e3
+}
+
+// UnixNano returns t as a Unix time, the number of nanoseconds elapsed
+// since January 1, 1970 UTC. The result is undefined if the Unix time
+// in nanoseconds cannot be represented by an int64 (a date before the year
+// 1678 or after 2262). Note that this means the result of calling UnixNano
+// on the zero Time is undefined. The result does not depend on the
+// location associated with t.
+func (t Time) UnixNano() int64 {
+ return (t.unixSec())*1e9 + int64(t.nsec())
+}
+
+const (
+ timeBinaryVersionV1 byte = iota + 1 // For general situation
+ timeBinaryVersionV2 // For LMT only
+)
+
+// MarshalBinary implements the encoding.BinaryMarshaler interface.
+func (t Time) MarshalBinary() ([]byte, error) {
+ var offsetMin int16 // minutes east of UTC. -1 is UTC.
+ var offsetSec int8
+ version := timeBinaryVersionV1
+
+ if t.Location() == UTC {
+ offsetMin = -1
+ } else {
+ _, offset := t.Zone()
+ if offset%60 != 0 {
+ version = timeBinaryVersionV2
+ offsetSec = int8(offset % 60)
+ }
+
+ offset /= 60
+ if offset < -32768 || offset == -1 || offset > 32767 {
+ return nil, errors.New("Time.MarshalBinary: unexpected zone offset")
+ }
+ offsetMin = int16(offset)
+ }
+
+ sec := t.sec()
+ nsec := t.nsec()
+ enc := []byte{
+ version, // byte 0 : version
+ byte(sec >> 56), // bytes 1-8: seconds
+ byte(sec >> 48),
+ byte(sec >> 40),
+ byte(sec >> 32),
+ byte(sec >> 24),
+ byte(sec >> 16),
+ byte(sec >> 8),
+ byte(sec),
+ byte(nsec >> 24), // bytes 9-12: nanoseconds
+ byte(nsec >> 16),
+ byte(nsec >> 8),
+ byte(nsec),
+ byte(offsetMin >> 8), // bytes 13-14: zone offset in minutes
+ byte(offsetMin),
+ }
+ if version == timeBinaryVersionV2 {
+ enc = append(enc, byte(offsetSec))
+ }
+
+ return enc, nil
+}
+
+// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface.
+func (t *Time) UnmarshalBinary(data []byte) error {
+ buf := data
+ if len(buf) == 0 {
+ return errors.New("Time.UnmarshalBinary: no data")
+ }
+
+ version := buf[0]
+ if version != timeBinaryVersionV1 && version != timeBinaryVersionV2 {
+ return errors.New("Time.UnmarshalBinary: unsupported version")
+ }
+
+ wantLen := /*version*/ 1 + /*sec*/ 8 + /*nsec*/ 4 + /*zone offset*/ 2
+ if version == timeBinaryVersionV2 {
+ wantLen++
+ }
+ if len(buf) != wantLen {
+ return errors.New("Time.UnmarshalBinary: invalid length")
+ }
+
+ buf = buf[1:]
+ sec := int64(buf[7]) | int64(buf[6])<<8 | int64(buf[5])<<16 | int64(buf[4])<<24 |
+ int64(buf[3])<<32 | int64(buf[2])<<40 | int64(buf[1])<<48 | int64(buf[0])<<56
+
+ buf = buf[8:]
+ nsec := int32(buf[3]) | int32(buf[2])<<8 | int32(buf[1])<<16 | int32(buf[0])<<24
+
+ buf = buf[4:]
+ offset := int(int16(buf[1])|int16(buf[0])<<8) * 60
+ if version == timeBinaryVersionV2 {
+ offset += int(buf[2])
+ }
+
+ *t = Time{}
+ t.wall = uint64(nsec)
+ t.ext = sec
+
+ if offset == -1*60 {
+ t.setLoc(&utcLoc)
+ } else if _, localoff, _, _, _ := Local.lookup(t.unixSec()); offset == localoff {
+ t.setLoc(Local)
+ } else {
+ t.setLoc(FixedZone("", offset))
+ }
+
+ return nil
+}
+
+// TODO(rsc): Remove GobEncoder, GobDecoder, MarshalJSON, UnmarshalJSON in Go 2.
+// The same semantics will be provided by the generic MarshalBinary, MarshalText,
+// UnmarshalBinary, UnmarshalText.
+
+// GobEncode implements the gob.GobEncoder interface.
+func (t Time) GobEncode() ([]byte, error) {
+ return t.MarshalBinary()
+}
+
+// GobDecode implements the gob.GobDecoder interface.
+func (t *Time) GobDecode(data []byte) error {
+ return t.UnmarshalBinary(data)
+}
+
+// MarshalJSON implements the json.Marshaler interface.
+// The time is a quoted string in the RFC 3339 format with sub-second precision.
+// If the timestamp cannot be represented as valid RFC 3339
+// (e.g., the year is out of range), then an error is reported.
+func (t Time) MarshalJSON() ([]byte, error) {
+ b := make([]byte, 0, len(RFC3339Nano)+len(`""`))
+ b = append(b, '"')
+ b, err := t.appendStrictRFC3339(b)
+ b = append(b, '"')
+ if err != nil {
+ return nil, errors.New("Time.MarshalJSON: " + err.Error())
+ }
+ return b, nil
+}
+
+// UnmarshalJSON implements the json.Unmarshaler interface.
+// The time must be a quoted string in the RFC 3339 format.
+func (t *Time) UnmarshalJSON(data []byte) error {
+ if string(data) == "null" {
+ return nil
+ }
+ // TODO(https://go.dev/issue/47353): Properly unescape a JSON string.
+ if len(data) < 2 || data[0] != '"' || data[len(data)-1] != '"' {
+ return errors.New("Time.UnmarshalJSON: input is not a JSON string")
+ }
+ data = data[len(`"`) : len(data)-len(`"`)]
+ var err error
+ *t, err = parseStrictRFC3339(data)
+ return err
+}
+
+// MarshalText implements the encoding.TextMarshaler interface.
+// The time is formatted in RFC 3339 format with sub-second precision.
+// If the timestamp cannot be represented as valid RFC 3339
+// (e.g., the year is out of range), then an error is reported.
+func (t Time) MarshalText() ([]byte, error) {
+ b := make([]byte, 0, len(RFC3339Nano))
+ b, err := t.appendStrictRFC3339(b)
+ if err != nil {
+ return nil, errors.New("Time.MarshalText: " + err.Error())
+ }
+ return b, nil
+}
+
+// UnmarshalText implements the encoding.TextUnmarshaler interface.
+// The time must be in the RFC 3339 format.
+func (t *Time) UnmarshalText(data []byte) error {
+ var err error
+ *t, err = parseStrictRFC3339(data)
+ return err
+}
+
+// Unix returns the local Time corresponding to the given Unix time,
+// sec seconds and nsec nanoseconds since January 1, 1970 UTC.
+// It is valid to pass nsec outside the range [0, 999999999].
+// Not all sec values have a corresponding time value. One such
+// value is 1<<63-1 (the largest int64 value).
+func Unix(sec int64, nsec int64) Time {
+ if nsec < 0 || nsec >= 1e9 {
+ n := nsec / 1e9
+ sec += n
+ nsec -= n * 1e9
+ if nsec < 0 {
+ nsec += 1e9
+ sec--
+ }
+ }
+ return unixTime(sec, int32(nsec))
+}
+
+// UnixMilli returns the local Time corresponding to the given Unix time,
+// msec milliseconds since January 1, 1970 UTC.
+func UnixMilli(msec int64) Time {
+ return Unix(msec/1e3, (msec%1e3)*1e6)
+}
+
+// UnixMicro returns the local Time corresponding to the given Unix time,
+// usec microseconds since January 1, 1970 UTC.
+func UnixMicro(usec int64) Time {
+ return Unix(usec/1e6, (usec%1e6)*1e3)
+}
+
+// IsDST reports whether the time in the configured location is in Daylight Savings Time.
+func (t Time) IsDST() bool {
+ _, _, _, _, isDST := t.loc.lookup(t.Unix())
+ return isDST
+}
+
+func isLeap(year int) bool {
+ return year%4 == 0 && (year%100 != 0 || year%400 == 0)
+}
+
+// norm returns nhi, nlo such that
+//
+// hi * base + lo == nhi * base + nlo
+// 0 <= nlo < base
+func norm(hi, lo, base int) (nhi, nlo int) {
+ if lo < 0 {
+ n := (-lo-1)/base + 1
+ hi -= n
+ lo += n * base
+ }
+ if lo >= base {
+ n := lo / base
+ hi += n
+ lo -= n * base
+ }
+ return hi, lo
+}
+
+// Date returns the Time corresponding to
+//
+// yyyy-mm-dd hh:mm:ss + nsec nanoseconds
+//
+// in the appropriate zone for that time in the given location.
+//
+// The month, day, hour, min, sec, and nsec values may be outside
+// their usual ranges and will be normalized during the conversion.
+// For example, October 32 converts to November 1.
+//
+// A daylight savings time transition skips or repeats times.
+// For example, in the United States, March 13, 2011 2:15am never occurred,
+// while November 6, 2011 1:15am occurred twice. In such cases, the
+// choice of time zone, and therefore the time, is not well-defined.
+// Date returns a time that is correct in one of the two zones involved
+// in the transition, but it does not guarantee which.
+//
+// Date panics if loc is nil.
+func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time {
+ if loc == nil {
+ panic("time: missing Location in call to Date")
+ }
+
+ // Normalize month, overflowing into year.
+ m := int(month) - 1
+ year, m = norm(year, m, 12)
+ month = Month(m) + 1
+
+ // Normalize nsec, sec, min, hour, overflowing into day.
+ sec, nsec = norm(sec, nsec, 1e9)
+ min, sec = norm(min, sec, 60)
+ hour, min = norm(hour, min, 60)
+ day, hour = norm(day, hour, 24)
+
+ // Compute days since the absolute epoch.
+ d := daysSinceEpoch(year)
+
+ // Add in days before this month.
+ d += uint64(daysBefore[month-1])
+ if isLeap(year) && month >= March {
+ d++ // February 29
+ }
+
+ // Add in days before today.
+ d += uint64(day - 1)
+
+ // Add in time elapsed today.
+ abs := d * secondsPerDay
+ abs += uint64(hour*secondsPerHour + min*secondsPerMinute + sec)
+
+ unix := int64(abs) + (absoluteToInternal + internalToUnix)
+
+ // Look for zone offset for expected time, so we can adjust to UTC.
+ // The lookup function expects UTC, so first we pass unix in the
+ // hope that it will not be too close to a zone transition,
+ // and then adjust if it is.
+ _, offset, start, end, _ := loc.lookup(unix)
+ if offset != 0 {
+ utc := unix - int64(offset)
+ // If utc is valid for the time zone we found, then we have the right offset.
+ // If not, we get the correct offset by looking up utc in the location.
+ if utc < start || utc >= end {
+ _, offset, _, _, _ = loc.lookup(utc)
+ }
+ unix -= int64(offset)
+ }
+
+ t := unixTime(unix, int32(nsec))
+ t.setLoc(loc)
+ return t
+}
+
+// Truncate returns the result of rounding t down to a multiple of d (since the zero time).
+// If d <= 0, Truncate returns t stripped of any monotonic clock reading but otherwise unchanged.
+//
+// Truncate operates on the time as an absolute duration since the
+// zero time; it does not operate on the presentation form of the
+// time. Thus, Truncate(Hour) may return a time with a non-zero
+// minute, depending on the time's Location.
+func (t Time) Truncate(d Duration) Time {
+ t.stripMono()
+ if d <= 0 {
+ return t
+ }
+ _, r := div(t, d)
+ return t.Add(-r)
+}
+
+// Round returns the result of rounding t to the nearest multiple of d (since the zero time).
+// The rounding behavior for halfway values is to round up.
+// If d <= 0, Round returns t stripped of any monotonic clock reading but otherwise unchanged.
+//
+// Round operates on the time as an absolute duration since the
+// zero time; it does not operate on the presentation form of the
+// time. Thus, Round(Hour) may return a time with a non-zero
+// minute, depending on the time's Location.
+func (t Time) Round(d Duration) Time {
+ t.stripMono()
+ if d <= 0 {
+ return t
+ }
+ _, r := div(t, d)
+ if lessThanHalf(r, d) {
+ return t.Add(-r)
+ }
+ return t.Add(d - r)
+}
+
+// div divides t by d and returns the quotient parity and remainder.
+// We don't use the quotient parity anymore (round half up instead of round to even)
+// but it's still here in case we change our minds.
+func div(t Time, d Duration) (qmod2 int, r Duration) {
+ neg := false
+ nsec := t.nsec()
+ sec := t.sec()
+ if sec < 0 {
+ // Operate on absolute value.
+ neg = true
+ sec = -sec
+ nsec = -nsec
+ if nsec < 0 {
+ nsec += 1e9
+ sec-- // sec >= 1 before the -- so safe
+ }
+ }
+
+ switch {
+ // Special case: 2d divides 1 second.
+ case d < Second && Second%(d+d) == 0:
+ qmod2 = int(nsec/int32(d)) & 1
+ r = Duration(nsec % int32(d))
+
+ // Special case: d is a multiple of 1 second.
+ case d%Second == 0:
+ d1 := int64(d / Second)
+ qmod2 = int(sec/d1) & 1
+ r = Duration(sec%d1)*Second + Duration(nsec)
+
+ // General case.
+ // This could be faster if more cleverness were applied,
+ // but it's really only here to avoid special case restrictions in the API.
+ // No one will care about these cases.
+ default:
+ // Compute nanoseconds as 128-bit number.
+ sec := uint64(sec)
+ tmp := (sec >> 32) * 1e9
+ u1 := tmp >> 32
+ u0 := tmp << 32
+ tmp = (sec & 0xFFFFFFFF) * 1e9
+ u0x, u0 := u0, u0+tmp
+ if u0 < u0x {
+ u1++
+ }
+ u0x, u0 = u0, u0+uint64(nsec)
+ if u0 < u0x {
+ u1++
+ }
+
+ // Compute remainder by subtracting r<<k for decreasing k.
+ // Quotient parity is whether we subtract on last round.
+ d1 := uint64(d)
+ for d1>>63 != 1 {
+ d1 <<= 1
+ }
+ d0 := uint64(0)
+ for {
+ qmod2 = 0
+ if u1 > d1 || u1 == d1 && u0 >= d0 {
+ // subtract
+ qmod2 = 1
+ u0x, u0 = u0, u0-d0
+ if u0 > u0x {
+ u1--
+ }
+ u1 -= d1
+ }
+ if d1 == 0 && d0 == uint64(d) {
+ break
+ }
+ d0 >>= 1
+ d0 |= (d1 & 1) << 63
+ d1 >>= 1
+ }
+ r = Duration(u0)
+ }
+
+ if neg && r != 0 {
+ // If input was negative and not an exact multiple of d, we computed q, r such that
+ // q*d + r = -t
+ // But the right answers are given by -(q-1), d-r:
+ // q*d + r = -t
+ // -q*d - r = t
+ // -(q-1)*d + (d - r) = t
+ qmod2 ^= 1
+ r = d - r
+ }
+ return
+}
diff --git a/src/time/time_test.go b/src/time/time_test.go
new file mode 100644
index 0000000..86335e3
--- /dev/null
+++ b/src/time/time_test.go
@@ -0,0 +1,1891 @@
+// 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 (
+ "bytes"
+ "encoding/gob"
+ "encoding/json"
+ "fmt"
+ "math"
+ "math/big"
+ "math/rand"
+ "os"
+ "runtime"
+ "strings"
+ "sync"
+ "testing"
+ "testing/quick"
+ . "time"
+)
+
+// We should be in PST/PDT, but if the time zone files are missing we
+// won't be. The purpose of this test is to at least explain why some of
+// the subsequent tests fail.
+func TestZoneData(t *testing.T) {
+ lt := Now()
+ // PST is 8 hours west, PDT is 7 hours west. We could use the name but it's not unique.
+ if name, off := lt.Zone(); off != -8*60*60 && off != -7*60*60 {
+ t.Errorf("Unable to find US Pacific time zone data for testing; time zone is %q offset %d", name, off)
+ t.Error("Likely problem: the time zone files have not been installed.")
+ }
+}
+
+// parsedTime is the struct representing a parsed time value.
+type parsedTime struct {
+ Year int
+ Month Month
+ Day int
+ Hour, Minute, Second int // 15:04:05 is 15, 4, 5.
+ Nanosecond int // Fractional second.
+ Weekday Weekday
+ ZoneOffset int // seconds east of UTC, e.g. -7*60*60 for -0700
+ Zone string // e.g., "MST"
+}
+
+type TimeTest struct {
+ seconds int64
+ golden parsedTime
+}
+
+var utctests = []TimeTest{
+ {0, parsedTime{1970, January, 1, 0, 0, 0, 0, Thursday, 0, "UTC"}},
+ {1221681866, parsedTime{2008, September, 17, 20, 4, 26, 0, Wednesday, 0, "UTC"}},
+ {-1221681866, parsedTime{1931, April, 16, 3, 55, 34, 0, Thursday, 0, "UTC"}},
+ {-11644473600, parsedTime{1601, January, 1, 0, 0, 0, 0, Monday, 0, "UTC"}},
+ {599529660, parsedTime{1988, December, 31, 0, 1, 0, 0, Saturday, 0, "UTC"}},
+ {978220860, parsedTime{2000, December, 31, 0, 1, 0, 0, Sunday, 0, "UTC"}},
+}
+
+var nanoutctests = []TimeTest{
+ {0, parsedTime{1970, January, 1, 0, 0, 0, 1e8, Thursday, 0, "UTC"}},
+ {1221681866, parsedTime{2008, September, 17, 20, 4, 26, 2e8, Wednesday, 0, "UTC"}},
+}
+
+var localtests = []TimeTest{
+ {0, parsedTime{1969, December, 31, 16, 0, 0, 0, Wednesday, -8 * 60 * 60, "PST"}},
+ {1221681866, parsedTime{2008, September, 17, 13, 4, 26, 0, Wednesday, -7 * 60 * 60, "PDT"}},
+ {2159200800, parsedTime{2038, June, 3, 11, 0, 0, 0, Thursday, -7 * 60 * 60, "PDT"}},
+ {2152173599, parsedTime{2038, March, 14, 1, 59, 59, 0, Sunday, -8 * 60 * 60, "PST"}},
+ {2152173600, parsedTime{2038, March, 14, 3, 0, 0, 0, Sunday, -7 * 60 * 60, "PDT"}},
+ {2152173601, parsedTime{2038, March, 14, 3, 0, 1, 0, Sunday, -7 * 60 * 60, "PDT"}},
+ {2172733199, parsedTime{2038, November, 7, 1, 59, 59, 0, Sunday, -7 * 60 * 60, "PDT"}},
+ {2172733200, parsedTime{2038, November, 7, 1, 0, 0, 0, Sunday, -8 * 60 * 60, "PST"}},
+ {2172733201, parsedTime{2038, November, 7, 1, 0, 1, 0, Sunday, -8 * 60 * 60, "PST"}},
+}
+
+var nanolocaltests = []TimeTest{
+ {0, parsedTime{1969, December, 31, 16, 0, 0, 1e8, Wednesday, -8 * 60 * 60, "PST"}},
+ {1221681866, parsedTime{2008, September, 17, 13, 4, 26, 3e8, Wednesday, -7 * 60 * 60, "PDT"}},
+}
+
+func same(t Time, u *parsedTime) bool {
+ // Check aggregates.
+ year, month, day := t.Date()
+ hour, min, sec := t.Clock()
+ name, offset := t.Zone()
+ if year != u.Year || month != u.Month || day != u.Day ||
+ hour != u.Hour || min != u.Minute || sec != u.Second ||
+ name != u.Zone || offset != u.ZoneOffset {
+ return false
+ }
+ // Check individual entries.
+ return t.Year() == u.Year &&
+ t.Month() == u.Month &&
+ t.Day() == u.Day &&
+ t.Hour() == u.Hour &&
+ t.Minute() == u.Minute &&
+ t.Second() == u.Second &&
+ t.Nanosecond() == u.Nanosecond &&
+ t.Weekday() == u.Weekday
+}
+
+func TestSecondsToUTC(t *testing.T) {
+ for _, test := range utctests {
+ sec := test.seconds
+ golden := &test.golden
+ tm := Unix(sec, 0).UTC()
+ newsec := tm.Unix()
+ if newsec != sec {
+ t.Errorf("SecondsToUTC(%d).Seconds() = %d", sec, newsec)
+ }
+ if !same(tm, golden) {
+ t.Errorf("SecondsToUTC(%d): // %#v", sec, tm)
+ t.Errorf(" want=%+v", *golden)
+ t.Errorf(" have=%v", tm.Format(RFC3339+" MST"))
+ }
+ }
+}
+
+func TestNanosecondsToUTC(t *testing.T) {
+ for _, test := range nanoutctests {
+ golden := &test.golden
+ nsec := test.seconds*1e9 + int64(golden.Nanosecond)
+ tm := Unix(0, nsec).UTC()
+ newnsec := tm.Unix()*1e9 + int64(tm.Nanosecond())
+ if newnsec != nsec {
+ t.Errorf("NanosecondsToUTC(%d).Nanoseconds() = %d", nsec, newnsec)
+ }
+ if !same(tm, golden) {
+ t.Errorf("NanosecondsToUTC(%d):", nsec)
+ t.Errorf(" want=%+v", *golden)
+ t.Errorf(" have=%+v", tm.Format(RFC3339+" MST"))
+ }
+ }
+}
+
+func TestSecondsToLocalTime(t *testing.T) {
+ for _, test := range localtests {
+ sec := test.seconds
+ golden := &test.golden
+ tm := Unix(sec, 0)
+ newsec := tm.Unix()
+ if newsec != sec {
+ t.Errorf("SecondsToLocalTime(%d).Seconds() = %d", sec, newsec)
+ }
+ if !same(tm, golden) {
+ t.Errorf("SecondsToLocalTime(%d):", sec)
+ t.Errorf(" want=%+v", *golden)
+ t.Errorf(" have=%+v", tm.Format(RFC3339+" MST"))
+ }
+ }
+}
+
+func TestNanosecondsToLocalTime(t *testing.T) {
+ for _, test := range nanolocaltests {
+ golden := &test.golden
+ nsec := test.seconds*1e9 + int64(golden.Nanosecond)
+ tm := Unix(0, nsec)
+ newnsec := tm.Unix()*1e9 + int64(tm.Nanosecond())
+ if newnsec != nsec {
+ t.Errorf("NanosecondsToLocalTime(%d).Seconds() = %d", nsec, newnsec)
+ }
+ if !same(tm, golden) {
+ t.Errorf("NanosecondsToLocalTime(%d):", nsec)
+ t.Errorf(" want=%+v", *golden)
+ t.Errorf(" have=%+v", tm.Format(RFC3339+" MST"))
+ }
+ }
+}
+
+func TestSecondsToUTCAndBack(t *testing.T) {
+ f := func(sec int64) bool { return Unix(sec, 0).UTC().Unix() == sec }
+ f32 := func(sec int32) bool { return f(int64(sec)) }
+ cfg := &quick.Config{MaxCount: 10000}
+
+ // Try a reasonable date first, then the huge ones.
+ if err := quick.Check(f32, cfg); err != nil {
+ t.Fatal(err)
+ }
+ if err := quick.Check(f, cfg); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestNanosecondsToUTCAndBack(t *testing.T) {
+ f := func(nsec int64) bool {
+ t := Unix(0, nsec).UTC()
+ ns := t.Unix()*1e9 + int64(t.Nanosecond())
+ return ns == nsec
+ }
+ f32 := func(nsec int32) bool { return f(int64(nsec)) }
+ cfg := &quick.Config{MaxCount: 10000}
+
+ // Try a small date first, then the large ones. (The span is only a few hundred years
+ // for nanoseconds in an int64.)
+ if err := quick.Check(f32, cfg); err != nil {
+ t.Fatal(err)
+ }
+ if err := quick.Check(f, cfg); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestUnixMilli(t *testing.T) {
+ f := func(msec int64) bool {
+ t := UnixMilli(msec)
+ return t.UnixMilli() == msec
+ }
+ cfg := &quick.Config{MaxCount: 10000}
+ if err := quick.Check(f, cfg); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestUnixMicro(t *testing.T) {
+ f := func(usec int64) bool {
+ t := UnixMicro(usec)
+ return t.UnixMicro() == usec
+ }
+ cfg := &quick.Config{MaxCount: 10000}
+ if err := quick.Check(f, cfg); err != nil {
+ t.Fatal(err)
+ }
+}
+
+// The time routines provide no way to get absolute time
+// (seconds since zero), but we need it to compute the right
+// answer for bizarre roundings like "to the nearest 3 ns".
+// Compute as t - year1 = (t - 1970) + (1970 - 2001) + (2001 - 1).
+// t - 1970 is returned by Unix and Nanosecond.
+// 1970 - 2001 is -(31*365+8)*86400 = -978307200 seconds.
+// 2001 - 1 is 2000*365.2425*86400 = 63113904000 seconds.
+const unixToZero = -978307200 + 63113904000
+
+// abs returns the absolute time stored in t, as seconds and nanoseconds.
+func abs(t Time) (sec, nsec int64) {
+ unix := t.Unix()
+ nano := t.Nanosecond()
+ return unix + unixToZero, int64(nano)
+}
+
+// absString returns abs as a decimal string.
+func absString(t Time) string {
+ sec, nsec := abs(t)
+ if sec < 0 {
+ sec = -sec
+ nsec = -nsec
+ if nsec < 0 {
+ nsec += 1e9
+ sec--
+ }
+ return fmt.Sprintf("-%d%09d", sec, nsec)
+ }
+ return fmt.Sprintf("%d%09d", sec, nsec)
+}
+
+var truncateRoundTests = []struct {
+ t Time
+ d Duration
+}{
+ {Date(-1, January, 1, 12, 15, 30, 5e8, UTC), 3},
+ {Date(-1, January, 1, 12, 15, 31, 5e8, UTC), 3},
+ {Date(2012, January, 1, 12, 15, 30, 5e8, UTC), Second},
+ {Date(2012, January, 1, 12, 15, 31, 5e8, UTC), Second},
+ {Unix(-19012425939, 649146258), 7435029458905025217}, // 5.8*d rounds to 6*d, but .8*d+.8*d < 0 < d
+}
+
+func TestTruncateRound(t *testing.T) {
+ var (
+ bsec = new(big.Int)
+ bnsec = new(big.Int)
+ bd = new(big.Int)
+ bt = new(big.Int)
+ br = new(big.Int)
+ bq = new(big.Int)
+ b1e9 = new(big.Int)
+ )
+
+ b1e9.SetInt64(1e9)
+
+ testOne := func(ti, tns, di int64) bool {
+ t.Helper()
+
+ t0 := Unix(ti, tns).UTC()
+ d := Duration(di)
+ if d < 0 {
+ d = -d
+ }
+ if d <= 0 {
+ d = 1
+ }
+
+ // Compute bt = absolute nanoseconds.
+ sec, nsec := abs(t0)
+ bsec.SetInt64(sec)
+ bnsec.SetInt64(nsec)
+ bt.Mul(bsec, b1e9)
+ bt.Add(bt, bnsec)
+
+ // Compute quotient and remainder mod d.
+ bd.SetInt64(int64(d))
+ bq.DivMod(bt, bd, br)
+
+ // To truncate, subtract remainder.
+ // br is < d, so it fits in an int64.
+ r := br.Int64()
+ t1 := t0.Add(-Duration(r))
+
+ // Check that time.Truncate works.
+ if trunc := t0.Truncate(d); trunc != t1 {
+ t.Errorf("Time.Truncate(%s, %s) = %s, want %s\n"+
+ "%v trunc %v =\n%v want\n%v",
+ t0.Format(RFC3339Nano), d, trunc, t1.Format(RFC3339Nano),
+ absString(t0), int64(d), absString(trunc), absString(t1))
+ return false
+ }
+
+ // To round, add d back if remainder r > d/2 or r == exactly d/2.
+ // The commented out code would round half to even instead of up,
+ // but that makes it time-zone dependent, which is a bit strange.
+ if r > int64(d)/2 || r+r == int64(d) /*&& bq.Bit(0) == 1*/ {
+ t1 = t1.Add(d)
+ }
+
+ // Check that time.Round works.
+ if rnd := t0.Round(d); rnd != t1 {
+ t.Errorf("Time.Round(%s, %s) = %s, want %s\n"+
+ "%v round %v =\n%v want\n%v",
+ t0.Format(RFC3339Nano), d, rnd, t1.Format(RFC3339Nano),
+ absString(t0), int64(d), absString(rnd), absString(t1))
+ return false
+ }
+ return true
+ }
+
+ // manual test cases
+ for _, tt := range truncateRoundTests {
+ testOne(tt.t.Unix(), int64(tt.t.Nanosecond()), int64(tt.d))
+ }
+
+ // exhaustive near 0
+ for i := 0; i < 100; i++ {
+ for j := 1; j < 100; j++ {
+ testOne(unixToZero, int64(i), int64(j))
+ testOne(unixToZero, -int64(i), int64(j))
+ if t.Failed() {
+ return
+ }
+ }
+ }
+
+ if t.Failed() {
+ return
+ }
+
+ // randomly generated test cases
+ cfg := &quick.Config{MaxCount: 100000}
+ if testing.Short() {
+ cfg.MaxCount = 1000
+ }
+
+ // divisors of Second
+ f1 := func(ti int64, tns int32, logdi int32) bool {
+ d := Duration(1)
+ a, b := uint(logdi%9), (logdi>>16)%9
+ d <<= a
+ for i := 0; i < int(b); i++ {
+ d *= 5
+ }
+
+ // Make room for unix ↔ internal conversion.
+ // We don't care about behavior too close to ± 2^63 Unix seconds.
+ // It is full of wraparounds but will never happen in a reasonable program.
+ // (Or maybe not? See go.dev/issue/20678. In any event, they're not handled today.)
+ ti >>= 1
+
+ return testOne(ti, int64(tns), int64(d))
+ }
+ quick.Check(f1, cfg)
+
+ // multiples of Second
+ f2 := func(ti int64, tns int32, di int32) bool {
+ d := Duration(di) * Second
+ if d < 0 {
+ d = -d
+ }
+ ti >>= 1 // see comment in f1
+ return testOne(ti, int64(tns), int64(d))
+ }
+ quick.Check(f2, cfg)
+
+ // halfway cases
+ f3 := func(tns, di int64) bool {
+ di &= 0xfffffffe
+ if di == 0 {
+ di = 2
+ }
+ tns -= tns % di
+ if tns < 0 {
+ tns += di / 2
+ } else {
+ tns -= di / 2
+ }
+ return testOne(0, tns, di)
+ }
+ quick.Check(f3, cfg)
+
+ // full generality
+ f4 := func(ti int64, tns int32, di int64) bool {
+ ti >>= 1 // see comment in f1
+ return testOne(ti, int64(tns), di)
+ }
+ quick.Check(f4, cfg)
+}
+
+type ISOWeekTest struct {
+ year int // year
+ month, day int // month and day
+ yex int // expected year
+ wex int // expected week
+}
+
+var isoWeekTests = []ISOWeekTest{
+ {1981, 1, 1, 1981, 1}, {1982, 1, 1, 1981, 53}, {1983, 1, 1, 1982, 52},
+ {1984, 1, 1, 1983, 52}, {1985, 1, 1, 1985, 1}, {1986, 1, 1, 1986, 1},
+ {1987, 1, 1, 1987, 1}, {1988, 1, 1, 1987, 53}, {1989, 1, 1, 1988, 52},
+ {1990, 1, 1, 1990, 1}, {1991, 1, 1, 1991, 1}, {1992, 1, 1, 1992, 1},
+ {1993, 1, 1, 1992, 53}, {1994, 1, 1, 1993, 52}, {1995, 1, 2, 1995, 1},
+ {1996, 1, 1, 1996, 1}, {1996, 1, 7, 1996, 1}, {1996, 1, 8, 1996, 2},
+ {1997, 1, 1, 1997, 1}, {1998, 1, 1, 1998, 1}, {1999, 1, 1, 1998, 53},
+ {2000, 1, 1, 1999, 52}, {2001, 1, 1, 2001, 1}, {2002, 1, 1, 2002, 1},
+ {2003, 1, 1, 2003, 1}, {2004, 1, 1, 2004, 1}, {2005, 1, 1, 2004, 53},
+ {2006, 1, 1, 2005, 52}, {2007, 1, 1, 2007, 1}, {2008, 1, 1, 2008, 1},
+ {2009, 1, 1, 2009, 1}, {2010, 1, 1, 2009, 53}, {2010, 1, 1, 2009, 53},
+ {2011, 1, 1, 2010, 52}, {2011, 1, 2, 2010, 52}, {2011, 1, 3, 2011, 1},
+ {2011, 1, 4, 2011, 1}, {2011, 1, 5, 2011, 1}, {2011, 1, 6, 2011, 1},
+ {2011, 1, 7, 2011, 1}, {2011, 1, 8, 2011, 1}, {2011, 1, 9, 2011, 1},
+ {2011, 1, 10, 2011, 2}, {2011, 1, 11, 2011, 2}, {2011, 6, 12, 2011, 23},
+ {2011, 6, 13, 2011, 24}, {2011, 12, 25, 2011, 51}, {2011, 12, 26, 2011, 52},
+ {2011, 12, 27, 2011, 52}, {2011, 12, 28, 2011, 52}, {2011, 12, 29, 2011, 52},
+ {2011, 12, 30, 2011, 52}, {2011, 12, 31, 2011, 52}, {1995, 1, 1, 1994, 52},
+ {2012, 1, 1, 2011, 52}, {2012, 1, 2, 2012, 1}, {2012, 1, 8, 2012, 1},
+ {2012, 1, 9, 2012, 2}, {2012, 12, 23, 2012, 51}, {2012, 12, 24, 2012, 52},
+ {2012, 12, 30, 2012, 52}, {2012, 12, 31, 2013, 1}, {2013, 1, 1, 2013, 1},
+ {2013, 1, 6, 2013, 1}, {2013, 1, 7, 2013, 2}, {2013, 12, 22, 2013, 51},
+ {2013, 12, 23, 2013, 52}, {2013, 12, 29, 2013, 52}, {2013, 12, 30, 2014, 1},
+ {2014, 1, 1, 2014, 1}, {2014, 1, 5, 2014, 1}, {2014, 1, 6, 2014, 2},
+ {2015, 1, 1, 2015, 1}, {2016, 1, 1, 2015, 53}, {2017, 1, 1, 2016, 52},
+ {2018, 1, 1, 2018, 1}, {2019, 1, 1, 2019, 1}, {2020, 1, 1, 2020, 1},
+ {2021, 1, 1, 2020, 53}, {2022, 1, 1, 2021, 52}, {2023, 1, 1, 2022, 52},
+ {2024, 1, 1, 2024, 1}, {2025, 1, 1, 2025, 1}, {2026, 1, 1, 2026, 1},
+ {2027, 1, 1, 2026, 53}, {2028, 1, 1, 2027, 52}, {2029, 1, 1, 2029, 1},
+ {2030, 1, 1, 2030, 1}, {2031, 1, 1, 2031, 1}, {2032, 1, 1, 2032, 1},
+ {2033, 1, 1, 2032, 53}, {2034, 1, 1, 2033, 52}, {2035, 1, 1, 2035, 1},
+ {2036, 1, 1, 2036, 1}, {2037, 1, 1, 2037, 1}, {2038, 1, 1, 2037, 53},
+ {2039, 1, 1, 2038, 52}, {2040, 1, 1, 2039, 52},
+}
+
+func TestISOWeek(t *testing.T) {
+ // Selected dates and corner cases
+ for _, wt := range isoWeekTests {
+ dt := Date(wt.year, Month(wt.month), wt.day, 0, 0, 0, 0, UTC)
+ y, w := dt.ISOWeek()
+ if w != wt.wex || y != wt.yex {
+ t.Errorf("got %d/%d; expected %d/%d for %d-%02d-%02d",
+ y, w, wt.yex, wt.wex, wt.year, wt.month, wt.day)
+ }
+ }
+
+ // The only real invariant: Jan 04 is in week 1
+ for year := 1950; year < 2100; year++ {
+ if y, w := Date(year, January, 4, 0, 0, 0, 0, UTC).ISOWeek(); y != year || w != 1 {
+ t.Errorf("got %d/%d; expected %d/1 for Jan 04", y, w, year)
+ }
+ }
+}
+
+type YearDayTest struct {
+ year, month, day int
+ yday int
+}
+
+// Test YearDay in several different scenarios
+// and corner cases
+var yearDayTests = []YearDayTest{
+ // Non-leap-year tests
+ {2007, 1, 1, 1},
+ {2007, 1, 15, 15},
+ {2007, 2, 1, 32},
+ {2007, 2, 15, 46},
+ {2007, 3, 1, 60},
+ {2007, 3, 15, 74},
+ {2007, 4, 1, 91},
+ {2007, 12, 31, 365},
+
+ // Leap-year tests
+ {2008, 1, 1, 1},
+ {2008, 1, 15, 15},
+ {2008, 2, 1, 32},
+ {2008, 2, 15, 46},
+ {2008, 3, 1, 61},
+ {2008, 3, 15, 75},
+ {2008, 4, 1, 92},
+ {2008, 12, 31, 366},
+
+ // Looks like leap-year (but isn't) tests
+ {1900, 1, 1, 1},
+ {1900, 1, 15, 15},
+ {1900, 2, 1, 32},
+ {1900, 2, 15, 46},
+ {1900, 3, 1, 60},
+ {1900, 3, 15, 74},
+ {1900, 4, 1, 91},
+ {1900, 12, 31, 365},
+
+ // Year one tests (non-leap)
+ {1, 1, 1, 1},
+ {1, 1, 15, 15},
+ {1, 2, 1, 32},
+ {1, 2, 15, 46},
+ {1, 3, 1, 60},
+ {1, 3, 15, 74},
+ {1, 4, 1, 91},
+ {1, 12, 31, 365},
+
+ // Year minus one tests (non-leap)
+ {-1, 1, 1, 1},
+ {-1, 1, 15, 15},
+ {-1, 2, 1, 32},
+ {-1, 2, 15, 46},
+ {-1, 3, 1, 60},
+ {-1, 3, 15, 74},
+ {-1, 4, 1, 91},
+ {-1, 12, 31, 365},
+
+ // 400 BC tests (leap-year)
+ {-400, 1, 1, 1},
+ {-400, 1, 15, 15},
+ {-400, 2, 1, 32},
+ {-400, 2, 15, 46},
+ {-400, 3, 1, 61},
+ {-400, 3, 15, 75},
+ {-400, 4, 1, 92},
+ {-400, 12, 31, 366},
+
+ // Special Cases
+
+ // Gregorian calendar change (no effect)
+ {1582, 10, 4, 277},
+ {1582, 10, 15, 288},
+}
+
+// Check to see if YearDay is location sensitive
+var yearDayLocations = []*Location{
+ FixedZone("UTC-8", -8*60*60),
+ FixedZone("UTC-4", -4*60*60),
+ UTC,
+ FixedZone("UTC+4", 4*60*60),
+ FixedZone("UTC+8", 8*60*60),
+}
+
+func TestYearDay(t *testing.T) {
+ for i, loc := range yearDayLocations {
+ for _, ydt := range yearDayTests {
+ dt := Date(ydt.year, Month(ydt.month), ydt.day, 0, 0, 0, 0, loc)
+ yday := dt.YearDay()
+ if yday != ydt.yday {
+ t.Errorf("Date(%d-%02d-%02d in %v).YearDay() = %d, want %d",
+ ydt.year, ydt.month, ydt.day, loc, yday, ydt.yday)
+ continue
+ }
+
+ if ydt.year < 0 || ydt.year > 9999 {
+ continue
+ }
+ f := fmt.Sprintf("%04d-%02d-%02d %03d %+.2d00",
+ ydt.year, ydt.month, ydt.day, ydt.yday, (i-2)*4)
+ dt1, err := Parse("2006-01-02 002 -0700", f)
+ if err != nil {
+ t.Errorf(`Parse("2006-01-02 002 -0700", %q): %v`, f, err)
+ continue
+ }
+ if !dt1.Equal(dt) {
+ t.Errorf(`Parse("2006-01-02 002 -0700", %q) = %v, want %v`, f, dt1, dt)
+ }
+ }
+ }
+}
+
+var durationTests = []struct {
+ str string
+ d Duration
+}{
+ {"0s", 0},
+ {"1ns", 1 * Nanosecond},
+ {"1.1µs", 1100 * Nanosecond},
+ {"2.2ms", 2200 * Microsecond},
+ {"3.3s", 3300 * Millisecond},
+ {"4m5s", 4*Minute + 5*Second},
+ {"4m5.001s", 4*Minute + 5001*Millisecond},
+ {"5h6m7.001s", 5*Hour + 6*Minute + 7001*Millisecond},
+ {"8m0.000000001s", 8*Minute + 1*Nanosecond},
+ {"2562047h47m16.854775807s", 1<<63 - 1},
+ {"-2562047h47m16.854775808s", -1 << 63},
+}
+
+func TestDurationString(t *testing.T) {
+ for _, tt := range durationTests {
+ if str := tt.d.String(); str != tt.str {
+ t.Errorf("Duration(%d).String() = %s, want %s", int64(tt.d), str, tt.str)
+ }
+ if tt.d > 0 {
+ if str := (-tt.d).String(); str != "-"+tt.str {
+ t.Errorf("Duration(%d).String() = %s, want %s", int64(-tt.d), str, "-"+tt.str)
+ }
+ }
+ }
+}
+
+var dateTests = []struct {
+ year, month, day, hour, min, sec, nsec int
+ z *Location
+ unix int64
+}{
+ {2011, 11, 6, 1, 0, 0, 0, Local, 1320566400}, // 1:00:00 PDT
+ {2011, 11, 6, 1, 59, 59, 0, Local, 1320569999}, // 1:59:59 PDT
+ {2011, 11, 6, 2, 0, 0, 0, Local, 1320573600}, // 2:00:00 PST
+
+ {2011, 3, 13, 1, 0, 0, 0, Local, 1300006800}, // 1:00:00 PST
+ {2011, 3, 13, 1, 59, 59, 0, Local, 1300010399}, // 1:59:59 PST
+ {2011, 3, 13, 3, 0, 0, 0, Local, 1300010400}, // 3:00:00 PDT
+ {2011, 3, 13, 2, 30, 0, 0, Local, 1300008600}, // 2:30:00 PDT ≡ 1:30 PST
+ {2012, 12, 24, 0, 0, 0, 0, Local, 1356336000}, // Leap year
+
+ // Many names for Fri Nov 18 7:56:35 PST 2011
+ {2011, 11, 18, 7, 56, 35, 0, Local, 1321631795}, // Nov 18 7:56:35
+ {2011, 11, 19, -17, 56, 35, 0, Local, 1321631795}, // Nov 19 -17:56:35
+ {2011, 11, 17, 31, 56, 35, 0, Local, 1321631795}, // Nov 17 31:56:35
+ {2011, 11, 18, 6, 116, 35, 0, Local, 1321631795}, // Nov 18 6:116:35
+ {2011, 10, 49, 7, 56, 35, 0, Local, 1321631795}, // Oct 49 7:56:35
+ {2011, 11, 18, 7, 55, 95, 0, Local, 1321631795}, // Nov 18 7:55:95
+ {2011, 11, 18, 7, 56, 34, 1e9, Local, 1321631795}, // Nov 18 7:56:34 + 10⁹ns
+ {2011, 12, -12, 7, 56, 35, 0, Local, 1321631795}, // Dec -21 7:56:35
+ {2012, 1, -43, 7, 56, 35, 0, Local, 1321631795}, // Jan -52 7:56:35 2012
+ {2012, int(January - 2), 18, 7, 56, 35, 0, Local, 1321631795}, // (Jan-2) 18 7:56:35 2012
+ {2010, int(December + 11), 18, 7, 56, 35, 0, Local, 1321631795}, // (Dec+11) 18 7:56:35 2010
+}
+
+func TestDate(t *testing.T) {
+ for _, tt := range dateTests {
+ time := Date(tt.year, Month(tt.month), tt.day, tt.hour, tt.min, tt.sec, tt.nsec, tt.z)
+ want := Unix(tt.unix, 0)
+ if !time.Equal(want) {
+ t.Errorf("Date(%d, %d, %d, %d, %d, %d, %d, %s) = %v, want %v",
+ tt.year, tt.month, tt.day, tt.hour, tt.min, tt.sec, tt.nsec, tt.z,
+ time, want)
+ }
+ }
+}
+
+// Several ways of getting from
+// Fri Nov 18 7:56:35 PST 2011
+// to
+// Thu Mar 19 7:56:35 PST 2016
+var addDateTests = []struct {
+ years, months, days int
+}{
+ {4, 4, 1},
+ {3, 16, 1},
+ {3, 15, 30},
+ {5, -6, -18 - 30 - 12},
+}
+
+func TestAddDate(t *testing.T) {
+ t0 := Date(2011, 11, 18, 7, 56, 35, 0, UTC)
+ t1 := Date(2016, 3, 19, 7, 56, 35, 0, UTC)
+ for _, at := range addDateTests {
+ time := t0.AddDate(at.years, at.months, at.days)
+ if !time.Equal(t1) {
+ t.Errorf("AddDate(%d, %d, %d) = %v, want %v",
+ at.years, at.months, at.days,
+ time, t1)
+ }
+ }
+}
+
+var daysInTests = []struct {
+ year, month, di int
+}{
+ {2011, 1, 31}, // January, first month, 31 days
+ {2011, 2, 28}, // February, non-leap year, 28 days
+ {2012, 2, 29}, // February, leap year, 29 days
+ {2011, 6, 30}, // June, 30 days
+ {2011, 12, 31}, // December, last month, 31 days
+}
+
+func TestDaysIn(t *testing.T) {
+ // The daysIn function is not exported.
+ // Test the daysIn function via the `var DaysIn = daysIn`
+ // statement in the internal_test.go file.
+ for _, tt := range daysInTests {
+ di := DaysIn(Month(tt.month), tt.year)
+ if di != tt.di {
+ t.Errorf("got %d; expected %d for %d-%02d",
+ di, tt.di, tt.year, tt.month)
+ }
+ }
+}
+
+func TestAddToExactSecond(t *testing.T) {
+ // Add an amount to the current time to round it up to the next exact second.
+ // This test checks that the nsec field still lies within the range [0, 999999999].
+ t1 := Now()
+ t2 := t1.Add(Second - Duration(t1.Nanosecond()))
+ sec := (t1.Second() + 1) % 60
+ if t2.Second() != sec || t2.Nanosecond() != 0 {
+ t.Errorf("sec = %d, nsec = %d, want sec = %d, nsec = 0", t2.Second(), t2.Nanosecond(), sec)
+ }
+}
+
+func equalTimeAndZone(a, b Time) bool {
+ aname, aoffset := a.Zone()
+ bname, boffset := b.Zone()
+ return a.Equal(b) && aoffset == boffset && aname == bname
+}
+
+var gobTests = []Time{
+ Date(0, 1, 2, 3, 4, 5, 6, UTC),
+ Date(7, 8, 9, 10, 11, 12, 13, FixedZone("", 0)),
+ Unix(81985467080890095, 0x76543210), // Time.sec: 0x0123456789ABCDEF
+ {}, // nil location
+ Date(1, 2, 3, 4, 5, 6, 7, FixedZone("", 32767*60)),
+ Date(1, 2, 3, 4, 5, 6, 7, FixedZone("", -32768*60)),
+}
+
+func TestTimeGob(t *testing.T) {
+ var b bytes.Buffer
+ enc := gob.NewEncoder(&b)
+ dec := gob.NewDecoder(&b)
+ for _, tt := range gobTests {
+ var gobtt Time
+ if err := enc.Encode(&tt); err != nil {
+ t.Errorf("%v gob Encode error = %q, want nil", tt, err)
+ } else if err := dec.Decode(&gobtt); err != nil {
+ t.Errorf("%v gob Decode error = %q, want nil", tt, err)
+ } else if !equalTimeAndZone(gobtt, tt) {
+ t.Errorf("Decoded time = %v, want %v", gobtt, tt)
+ }
+ b.Reset()
+ }
+}
+
+var invalidEncodingTests = []struct {
+ bytes []byte
+ want string
+}{
+ {[]byte{}, "Time.UnmarshalBinary: no data"},
+ {[]byte{0, 2, 3}, "Time.UnmarshalBinary: unsupported version"},
+ {[]byte{1, 2, 3}, "Time.UnmarshalBinary: invalid length"},
+}
+
+func TestInvalidTimeGob(t *testing.T) {
+ for _, tt := range invalidEncodingTests {
+ var ignored Time
+ err := ignored.GobDecode(tt.bytes)
+ if err == nil || err.Error() != tt.want {
+ t.Errorf("time.GobDecode(%#v) error = %v, want %v", tt.bytes, err, tt.want)
+ }
+ err = ignored.UnmarshalBinary(tt.bytes)
+ if err == nil || err.Error() != tt.want {
+ t.Errorf("time.UnmarshalBinary(%#v) error = %v, want %v", tt.bytes, err, tt.want)
+ }
+ }
+}
+
+var notEncodableTimes = []struct {
+ time Time
+ want string
+}{
+ {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", -1*60)), "Time.MarshalBinary: unexpected zone offset"},
+ {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", -32769*60)), "Time.MarshalBinary: unexpected zone offset"},
+ {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", 32768*60)), "Time.MarshalBinary: unexpected zone offset"},
+}
+
+func TestNotGobEncodableTime(t *testing.T) {
+ for _, tt := range notEncodableTimes {
+ _, err := tt.time.GobEncode()
+ if err == nil || err.Error() != tt.want {
+ t.Errorf("%v GobEncode error = %v, want %v", tt.time, err, tt.want)
+ }
+ _, err = tt.time.MarshalBinary()
+ if err == nil || err.Error() != tt.want {
+ t.Errorf("%v MarshalBinary error = %v, want %v", tt.time, err, tt.want)
+ }
+ }
+}
+
+var jsonTests = []struct {
+ time Time
+ json string
+}{
+ {Date(9999, 4, 12, 23, 20, 50, 520*1e6, UTC), `"9999-04-12T23:20:50.52Z"`},
+ {Date(1996, 12, 19, 16, 39, 57, 0, Local), `"1996-12-19T16:39:57-08:00"`},
+ {Date(0, 1, 1, 0, 0, 0, 1, FixedZone("", 1*60)), `"0000-01-01T00:00:00.000000001+00:01"`},
+ {Date(2020, 1, 1, 0, 0, 0, 0, FixedZone("", 23*60*60+59*60)), `"2020-01-01T00:00:00+23:59"`},
+}
+
+func TestTimeJSON(t *testing.T) {
+ for _, tt := range jsonTests {
+ var jsonTime Time
+
+ if jsonBytes, err := json.Marshal(tt.time); err != nil {
+ t.Errorf("%v json.Marshal error = %v, want nil", tt.time, err)
+ } else if string(jsonBytes) != tt.json {
+ t.Errorf("%v JSON = %#q, want %#q", tt.time, string(jsonBytes), tt.json)
+ } else if err = json.Unmarshal(jsonBytes, &jsonTime); err != nil {
+ t.Errorf("%v json.Unmarshal error = %v, want nil", tt.time, err)
+ } else if !equalTimeAndZone(jsonTime, tt.time) {
+ t.Errorf("Unmarshaled time = %v, want %v", jsonTime, tt.time)
+ }
+ }
+}
+
+func TestUnmarshalInvalidTimes(t *testing.T) {
+ tests := []struct {
+ in string
+ want string
+ }{
+ {`{}`, "Time.UnmarshalJSON: input is not a JSON string"},
+ {`[]`, "Time.UnmarshalJSON: input is not a JSON string"},
+ {`"2000-01-01T1:12:34Z"`, `<nil>`},
+ {`"2000-01-01T00:00:00,000Z"`, `<nil>`},
+ {`"2000-01-01T00:00:00+24:00"`, `<nil>`},
+ {`"2000-01-01T00:00:00+00:60"`, `<nil>`},
+ {`"2000-01-01T00:00:00+123:45"`, `parsing time "2000-01-01T00:00:00+123:45" as "2006-01-02T15:04:05Z07:00": cannot parse "+123:45" as "Z07:00"`},
+ }
+
+ for _, tt := range tests {
+ var ts Time
+
+ want := tt.want
+ err := json.Unmarshal([]byte(tt.in), &ts)
+ if fmt.Sprint(err) != want {
+ t.Errorf("Time.UnmarshalJSON(%s) = %v, want %v", tt.in, err, want)
+ }
+
+ if strings.HasPrefix(tt.in, `"`) && strings.HasSuffix(tt.in, `"`) {
+ err = ts.UnmarshalText([]byte(strings.Trim(tt.in, `"`)))
+ if fmt.Sprint(err) != want {
+ t.Errorf("Time.UnmarshalText(%s) = %v, want %v", tt.in, err, want)
+ }
+ }
+ }
+}
+
+func TestMarshalInvalidTimes(t *testing.T) {
+ tests := []struct {
+ time Time
+ want string
+ }{
+ {Date(10000, 1, 1, 0, 0, 0, 0, UTC), "Time.MarshalJSON: year outside of range [0,9999]"},
+ {Date(-998, 1, 1, 0, 0, 0, 0, UTC).Add(-Second), "Time.MarshalJSON: year outside of range [0,9999]"},
+ {Date(0, 1, 1, 0, 0, 0, 0, UTC).Add(-Nanosecond), "Time.MarshalJSON: year outside of range [0,9999]"},
+ {Date(2020, 1, 1, 0, 0, 0, 0, FixedZone("", 24*60*60)), "Time.MarshalJSON: timezone hour outside of range [0,23]"},
+ {Date(2020, 1, 1, 0, 0, 0, 0, FixedZone("", 123*60*60)), "Time.MarshalJSON: timezone hour outside of range [0,23]"},
+ }
+
+ for _, tt := range tests {
+ want := tt.want
+ b, err := tt.time.MarshalJSON()
+ switch {
+ case b != nil:
+ t.Errorf("(%v).MarshalText() = %q, want nil", tt.time, b)
+ case err == nil || err.Error() != want:
+ t.Errorf("(%v).MarshalJSON() error = %v, want %v", tt.time, err, want)
+ }
+
+ want = strings.ReplaceAll(tt.want, "JSON", "Text")
+ b, err = tt.time.MarshalText()
+ switch {
+ case b != nil:
+ t.Errorf("(%v).MarshalText() = %q, want nil", tt.time, b)
+ case err == nil || err.Error() != want:
+ t.Errorf("(%v).MarshalText() error = %v, want %v", tt.time, err, want)
+ }
+ }
+}
+
+var parseDurationTests = []struct {
+ in string
+ want Duration
+}{
+ // simple
+ {"0", 0},
+ {"5s", 5 * Second},
+ {"30s", 30 * Second},
+ {"1478s", 1478 * Second},
+ // sign
+ {"-5s", -5 * Second},
+ {"+5s", 5 * Second},
+ {"-0", 0},
+ {"+0", 0},
+ // decimal
+ {"5.0s", 5 * Second},
+ {"5.6s", 5*Second + 600*Millisecond},
+ {"5.s", 5 * Second},
+ {".5s", 500 * Millisecond},
+ {"1.0s", 1 * Second},
+ {"1.00s", 1 * Second},
+ {"1.004s", 1*Second + 4*Millisecond},
+ {"1.0040s", 1*Second + 4*Millisecond},
+ {"100.00100s", 100*Second + 1*Millisecond},
+ // different units
+ {"10ns", 10 * Nanosecond},
+ {"11us", 11 * Microsecond},
+ {"12µs", 12 * Microsecond}, // U+00B5
+ {"12μs", 12 * Microsecond}, // U+03BC
+ {"13ms", 13 * Millisecond},
+ {"14s", 14 * Second},
+ {"15m", 15 * Minute},
+ {"16h", 16 * Hour},
+ // composite durations
+ {"3h30m", 3*Hour + 30*Minute},
+ {"10.5s4m", 4*Minute + 10*Second + 500*Millisecond},
+ {"-2m3.4s", -(2*Minute + 3*Second + 400*Millisecond)},
+ {"1h2m3s4ms5us6ns", 1*Hour + 2*Minute + 3*Second + 4*Millisecond + 5*Microsecond + 6*Nanosecond},
+ {"39h9m14.425s", 39*Hour + 9*Minute + 14*Second + 425*Millisecond},
+ // large value
+ {"52763797000ns", 52763797000 * Nanosecond},
+ // more than 9 digits after decimal point, see https://golang.org/issue/6617
+ {"0.3333333333333333333h", 20 * Minute},
+ // 9007199254740993 = 1<<53+1 cannot be stored precisely in a float64
+ {"9007199254740993ns", (1<<53 + 1) * Nanosecond},
+ // largest duration that can be represented by int64 in nanoseconds
+ {"9223372036854775807ns", (1<<63 - 1) * Nanosecond},
+ {"9223372036854775.807us", (1<<63 - 1) * Nanosecond},
+ {"9223372036s854ms775us807ns", (1<<63 - 1) * Nanosecond},
+ {"-9223372036854775808ns", -1 << 63 * Nanosecond},
+ {"-9223372036854775.808us", -1 << 63 * Nanosecond},
+ {"-9223372036s854ms775us808ns", -1 << 63 * Nanosecond},
+ // largest negative value
+ {"-9223372036854775808ns", -1 << 63 * Nanosecond},
+ // largest negative round trip value, see https://golang.org/issue/48629
+ {"-2562047h47m16.854775808s", -1 << 63 * Nanosecond},
+ // huge string; issue 15011.
+ {"0.100000000000000000000h", 6 * Minute},
+ // This value tests the first overflow check in leadingFraction.
+ {"0.830103483285477580700h", 49*Minute + 48*Second + 372539827*Nanosecond},
+}
+
+func TestParseDuration(t *testing.T) {
+ for _, tc := range parseDurationTests {
+ d, err := ParseDuration(tc.in)
+ if err != nil || d != tc.want {
+ t.Errorf("ParseDuration(%q) = %v, %v, want %v, nil", tc.in, d, err, tc.want)
+ }
+ }
+}
+
+var parseDurationErrorTests = []struct {
+ in string
+ expect string
+}{
+ // invalid
+ {"", `""`},
+ {"3", `"3"`},
+ {"-", `"-"`},
+ {"s", `"s"`},
+ {".", `"."`},
+ {"-.", `"-."`},
+ {".s", `".s"`},
+ {"+.s", `"+.s"`},
+ {"1d", `"1d"`},
+ {"\x85\x85", `"\x85\x85"`},
+ {"\xffff", `"\xffff"`},
+ {"hello \xffff world", `"hello \xffff world"`},
+ {"\uFFFD", `"\xef\xbf\xbd"`}, // utf8.RuneError
+ {"\uFFFD hello \uFFFD world", `"\xef\xbf\xbd hello \xef\xbf\xbd world"`}, // utf8.RuneError
+ // overflow
+ {"9223372036854775810ns", `"9223372036854775810ns"`},
+ {"9223372036854775808ns", `"9223372036854775808ns"`},
+ {"-9223372036854775809ns", `"-9223372036854775809ns"`},
+ {"9223372036854776us", `"9223372036854776us"`},
+ {"3000000h", `"3000000h"`},
+ {"9223372036854775.808us", `"9223372036854775.808us"`},
+ {"9223372036854ms775us808ns", `"9223372036854ms775us808ns"`},
+}
+
+func TestParseDurationErrors(t *testing.T) {
+ for _, tc := range parseDurationErrorTests {
+ _, err := ParseDuration(tc.in)
+ if err == nil {
+ t.Errorf("ParseDuration(%q) = _, nil, want _, non-nil", tc.in)
+ } else if !strings.Contains(err.Error(), tc.expect) {
+ t.Errorf("ParseDuration(%q) = _, %q, error does not contain %q", tc.in, err, tc.expect)
+ }
+ }
+}
+
+func TestParseDurationRoundTrip(t *testing.T) {
+ // https://golang.org/issue/48629
+ max0 := Duration(math.MaxInt64)
+ max1, err := ParseDuration(max0.String())
+ if err != nil || max0 != max1 {
+ t.Errorf("round-trip failed: %d => %q => %d, %v", max0, max0.String(), max1, err)
+ }
+
+ min0 := Duration(math.MinInt64)
+ min1, err := ParseDuration(min0.String())
+ if err != nil || min0 != min1 {
+ t.Errorf("round-trip failed: %d => %q => %d, %v", min0, min0.String(), min1, err)
+ }
+
+ for i := 0; i < 100; i++ {
+ // Resolutions finer than milliseconds will result in
+ // imprecise round-trips.
+ d0 := Duration(rand.Int31()) * Millisecond
+ s := d0.String()
+ d1, err := ParseDuration(s)
+ if err != nil || d0 != d1 {
+ t.Errorf("round-trip failed: %d => %q => %d, %v", d0, s, d1, err)
+ }
+ }
+}
+
+// golang.org/issue/4622
+func TestLocationRace(t *testing.T) {
+ ResetLocalOnceForTest() // reset the Once to trigger the race
+
+ c := make(chan string, 1)
+ go func() {
+ c <- Now().String()
+ }()
+ _ = Now().String()
+ <-c
+ Sleep(100 * Millisecond)
+
+ // Back to Los Angeles for subsequent tests:
+ ForceUSPacificForTesting()
+}
+
+var (
+ t Time
+ u int64
+)
+
+var mallocTest = []struct {
+ count int
+ desc string
+ fn func()
+}{
+ {0, `time.Now()`, func() { t = Now() }},
+ {0, `time.Now().UnixNano()`, func() { u = Now().UnixNano() }},
+ {0, `time.Now().UnixMilli()`, func() { u = Now().UnixMilli() }},
+ {0, `time.Now().UnixMicro()`, func() { u = Now().UnixMicro() }},
+}
+
+func TestCountMallocs(t *testing.T) {
+ if testing.Short() {
+ t.Skip("skipping malloc count in short mode")
+ }
+ if runtime.GOMAXPROCS(0) > 1 {
+ t.Skip("skipping; GOMAXPROCS>1")
+ }
+ for _, mt := range mallocTest {
+ allocs := int(testing.AllocsPerRun(100, mt.fn))
+ if allocs > mt.count {
+ t.Errorf("%s: %d allocs, want %d", mt.desc, allocs, mt.count)
+ }
+ }
+}
+
+func TestLoadFixed(t *testing.T) {
+ // Issue 4064: handle locations without any zone transitions.
+ loc, err := LoadLocation("Etc/GMT+1")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // The tzdata name Etc/GMT+1 uses "east is negative",
+ // but Go and most other systems use "east is positive".
+ // So GMT+1 corresponds to -3600 in the Go zone, not +3600.
+ name, offset := Now().In(loc).Zone()
+ // The zone abbreviation is "-01" since tzdata-2016g, and "GMT+1"
+ // on earlier versions; we accept both. (Issue #17276).
+ if !(name == "GMT+1" || name == "-01") || offset != -1*60*60 {
+ t.Errorf("Now().In(loc).Zone() = %q, %d, want %q or %q, %d",
+ name, offset, "GMT+1", "-01", -1*60*60)
+ }
+}
+
+const (
+ minDuration Duration = -1 << 63
+ maxDuration Duration = 1<<63 - 1
+)
+
+var subTests = []struct {
+ t Time
+ u Time
+ d Duration
+}{
+ {Time{}, Time{}, Duration(0)},
+ {Date(2009, 11, 23, 0, 0, 0, 1, UTC), Date(2009, 11, 23, 0, 0, 0, 0, UTC), Duration(1)},
+ {Date(2009, 11, 23, 0, 0, 0, 0, UTC), Date(2009, 11, 24, 0, 0, 0, 0, UTC), -24 * Hour},
+ {Date(2009, 11, 24, 0, 0, 0, 0, UTC), Date(2009, 11, 23, 0, 0, 0, 0, UTC), 24 * Hour},
+ {Date(-2009, 11, 24, 0, 0, 0, 0, UTC), Date(-2009, 11, 23, 0, 0, 0, 0, UTC), 24 * Hour},
+ {Time{}, Date(2109, 11, 23, 0, 0, 0, 0, UTC), minDuration},
+ {Date(2109, 11, 23, 0, 0, 0, 0, UTC), Time{}, maxDuration},
+ {Time{}, Date(-2109, 11, 23, 0, 0, 0, 0, UTC), maxDuration},
+ {Date(-2109, 11, 23, 0, 0, 0, 0, UTC), Time{}, minDuration},
+ {Date(2290, 1, 1, 0, 0, 0, 0, UTC), Date(2000, 1, 1, 0, 0, 0, 0, UTC), 290*365*24*Hour + 71*24*Hour},
+ {Date(2300, 1, 1, 0, 0, 0, 0, UTC), Date(2000, 1, 1, 0, 0, 0, 0, UTC), maxDuration},
+ {Date(2000, 1, 1, 0, 0, 0, 0, UTC), Date(2290, 1, 1, 0, 0, 0, 0, UTC), -290*365*24*Hour - 71*24*Hour},
+ {Date(2000, 1, 1, 0, 0, 0, 0, UTC), Date(2300, 1, 1, 0, 0, 0, 0, UTC), minDuration},
+ {Date(2311, 11, 26, 02, 16, 47, 63535996, UTC), Date(2019, 8, 16, 2, 29, 30, 268436582, UTC), 9223372036795099414},
+ {MinMonoTime, MaxMonoTime, minDuration},
+ {MaxMonoTime, MinMonoTime, maxDuration},
+}
+
+func TestSub(t *testing.T) {
+ for i, st := range subTests {
+ got := st.t.Sub(st.u)
+ if got != st.d {
+ t.Errorf("#%d: Sub(%v, %v): got %v; want %v", i, st.t, st.u, got, st.d)
+ }
+ }
+}
+
+var nsDurationTests = []struct {
+ d Duration
+ want int64
+}{
+ {Duration(-1000), -1000},
+ {Duration(-1), -1},
+ {Duration(1), 1},
+ {Duration(1000), 1000},
+}
+
+func TestDurationNanoseconds(t *testing.T) {
+ for _, tt := range nsDurationTests {
+ if got := tt.d.Nanoseconds(); got != tt.want {
+ t.Errorf("Duration(%s).Nanoseconds() = %d; want: %d", tt.d, got, tt.want)
+ }
+ }
+}
+
+var usDurationTests = []struct {
+ d Duration
+ want int64
+}{
+ {Duration(-1000), -1},
+ {Duration(1000), 1},
+}
+
+func TestDurationMicroseconds(t *testing.T) {
+ for _, tt := range usDurationTests {
+ if got := tt.d.Microseconds(); got != tt.want {
+ t.Errorf("Duration(%s).Microseconds() = %d; want: %d", tt.d, got, tt.want)
+ }
+ }
+}
+
+var msDurationTests = []struct {
+ d Duration
+ want int64
+}{
+ {Duration(-1000000), -1},
+ {Duration(1000000), 1},
+}
+
+func TestDurationMilliseconds(t *testing.T) {
+ for _, tt := range msDurationTests {
+ if got := tt.d.Milliseconds(); got != tt.want {
+ t.Errorf("Duration(%s).Milliseconds() = %d; want: %d", tt.d, got, tt.want)
+ }
+ }
+}
+
+var secDurationTests = []struct {
+ d Duration
+ want float64
+}{
+ {Duration(300000000), 0.3},
+}
+
+func TestDurationSeconds(t *testing.T) {
+ for _, tt := range secDurationTests {
+ if got := tt.d.Seconds(); got != tt.want {
+ t.Errorf("Duration(%s).Seconds() = %g; want: %g", tt.d, got, tt.want)
+ }
+ }
+}
+
+var minDurationTests = []struct {
+ d Duration
+ want float64
+}{
+ {Duration(-60000000000), -1},
+ {Duration(-1), -1 / 60e9},
+ {Duration(1), 1 / 60e9},
+ {Duration(60000000000), 1},
+ {Duration(3000), 5e-8},
+}
+
+func TestDurationMinutes(t *testing.T) {
+ for _, tt := range minDurationTests {
+ if got := tt.d.Minutes(); got != tt.want {
+ t.Errorf("Duration(%s).Minutes() = %g; want: %g", tt.d, got, tt.want)
+ }
+ }
+}
+
+var hourDurationTests = []struct {
+ d Duration
+ want float64
+}{
+ {Duration(-3600000000000), -1},
+ {Duration(-1), -1 / 3600e9},
+ {Duration(1), 1 / 3600e9},
+ {Duration(3600000000000), 1},
+ {Duration(36), 1e-11},
+}
+
+func TestDurationHours(t *testing.T) {
+ for _, tt := range hourDurationTests {
+ if got := tt.d.Hours(); got != tt.want {
+ t.Errorf("Duration(%s).Hours() = %g; want: %g", tt.d, got, tt.want)
+ }
+ }
+}
+
+var durationTruncateTests = []struct {
+ d Duration
+ m Duration
+ want Duration
+}{
+ {0, Second, 0},
+ {Minute, -7 * Second, Minute},
+ {Minute, 0, Minute},
+ {Minute, 1, Minute},
+ {Minute + 10*Second, 10 * Second, Minute + 10*Second},
+ {2*Minute + 10*Second, Minute, 2 * Minute},
+ {10*Minute + 10*Second, 3 * Minute, 9 * Minute},
+ {Minute + 10*Second, Minute + 10*Second + 1, 0},
+ {Minute + 10*Second, Hour, 0},
+ {-Minute, Second, -Minute},
+ {-10 * Minute, 3 * Minute, -9 * Minute},
+ {-10 * Minute, Hour, 0},
+}
+
+func TestDurationTruncate(t *testing.T) {
+ for _, tt := range durationTruncateTests {
+ if got := tt.d.Truncate(tt.m); got != tt.want {
+ t.Errorf("Duration(%s).Truncate(%s) = %s; want: %s", tt.d, tt.m, got, tt.want)
+ }
+ }
+}
+
+var durationRoundTests = []struct {
+ d Duration
+ m Duration
+ want Duration
+}{
+ {0, Second, 0},
+ {Minute, -11 * Second, Minute},
+ {Minute, 0, Minute},
+ {Minute, 1, Minute},
+ {2 * Minute, Minute, 2 * Minute},
+ {2*Minute + 10*Second, Minute, 2 * Minute},
+ {2*Minute + 30*Second, Minute, 3 * Minute},
+ {2*Minute + 50*Second, Minute, 3 * Minute},
+ {-Minute, 1, -Minute},
+ {-2 * Minute, Minute, -2 * Minute},
+ {-2*Minute - 10*Second, Minute, -2 * Minute},
+ {-2*Minute - 30*Second, Minute, -3 * Minute},
+ {-2*Minute - 50*Second, Minute, -3 * Minute},
+ {8e18, 3e18, 9e18},
+ {9e18, 5e18, 1<<63 - 1},
+ {-8e18, 3e18, -9e18},
+ {-9e18, 5e18, -1 << 63},
+ {3<<61 - 1, 3 << 61, 3 << 61},
+}
+
+func TestDurationRound(t *testing.T) {
+ for _, tt := range durationRoundTests {
+ if got := tt.d.Round(tt.m); got != tt.want {
+ t.Errorf("Duration(%s).Round(%s) = %s; want: %s", tt.d, tt.m, got, tt.want)
+ }
+ }
+}
+
+var durationAbsTests = []struct {
+ d Duration
+ want Duration
+}{
+ {0, 0},
+ {1, 1},
+ {-1, 1},
+ {1 * Minute, 1 * Minute},
+ {-1 * Minute, 1 * Minute},
+ {minDuration, maxDuration},
+ {minDuration + 1, maxDuration},
+ {minDuration + 2, maxDuration - 1},
+ {maxDuration, maxDuration},
+ {maxDuration - 1, maxDuration - 1},
+}
+
+func TestDurationAbs(t *testing.T) {
+ for _, tt := range durationAbsTests {
+ if got := tt.d.Abs(); got != tt.want {
+ t.Errorf("Duration(%s).Abs() = %s; want: %s", tt.d, got, tt.want)
+ }
+ }
+}
+
+var defaultLocTests = []struct {
+ name string
+ f func(t1, t2 Time) bool
+}{
+ {"After", func(t1, t2 Time) bool { return t1.After(t2) == t2.After(t1) }},
+ {"Before", func(t1, t2 Time) bool { return t1.Before(t2) == t2.Before(t1) }},
+ {"Equal", func(t1, t2 Time) bool { return t1.Equal(t2) == t2.Equal(t1) }},
+ {"Compare", func(t1, t2 Time) bool { return t1.Compare(t2) == t2.Compare(t1) }},
+
+ {"IsZero", func(t1, t2 Time) bool { return t1.IsZero() == t2.IsZero() }},
+ {"Date", func(t1, t2 Time) bool {
+ a1, b1, c1 := t1.Date()
+ a2, b2, c2 := t2.Date()
+ return a1 == a2 && b1 == b2 && c1 == c2
+ }},
+ {"Year", func(t1, t2 Time) bool { return t1.Year() == t2.Year() }},
+ {"Month", func(t1, t2 Time) bool { return t1.Month() == t2.Month() }},
+ {"Day", func(t1, t2 Time) bool { return t1.Day() == t2.Day() }},
+ {"Weekday", func(t1, t2 Time) bool { return t1.Weekday() == t2.Weekday() }},
+ {"ISOWeek", func(t1, t2 Time) bool {
+ a1, b1 := t1.ISOWeek()
+ a2, b2 := t2.ISOWeek()
+ return a1 == a2 && b1 == b2
+ }},
+ {"Clock", func(t1, t2 Time) bool {
+ a1, b1, c1 := t1.Clock()
+ a2, b2, c2 := t2.Clock()
+ return a1 == a2 && b1 == b2 && c1 == c2
+ }},
+ {"Hour", func(t1, t2 Time) bool { return t1.Hour() == t2.Hour() }},
+ {"Minute", func(t1, t2 Time) bool { return t1.Minute() == t2.Minute() }},
+ {"Second", func(t1, t2 Time) bool { return t1.Second() == t2.Second() }},
+ {"Nanosecond", func(t1, t2 Time) bool { return t1.Hour() == t2.Hour() }},
+ {"YearDay", func(t1, t2 Time) bool { return t1.YearDay() == t2.YearDay() }},
+
+ // Using Equal since Add don't modify loc using "==" will cause a fail
+ {"Add", func(t1, t2 Time) bool { return t1.Add(Hour).Equal(t2.Add(Hour)) }},
+ {"Sub", func(t1, t2 Time) bool { return t1.Sub(t2) == t2.Sub(t1) }},
+
+ //Original caus for this test case bug 15852
+ {"AddDate", func(t1, t2 Time) bool { return t1.AddDate(1991, 9, 3) == t2.AddDate(1991, 9, 3) }},
+
+ {"UTC", func(t1, t2 Time) bool { return t1.UTC() == t2.UTC() }},
+ {"Local", func(t1, t2 Time) bool { return t1.Local() == t2.Local() }},
+ {"In", func(t1, t2 Time) bool { return t1.In(UTC) == t2.In(UTC) }},
+
+ {"Local", func(t1, t2 Time) bool { return t1.Local() == t2.Local() }},
+ {"Zone", func(t1, t2 Time) bool {
+ a1, b1 := t1.Zone()
+ a2, b2 := t2.Zone()
+ return a1 == a2 && b1 == b2
+ }},
+
+ {"Unix", func(t1, t2 Time) bool { return t1.Unix() == t2.Unix() }},
+ {"UnixNano", func(t1, t2 Time) bool { return t1.UnixNano() == t2.UnixNano() }},
+ {"UnixMilli", func(t1, t2 Time) bool { return t1.UnixMilli() == t2.UnixMilli() }},
+ {"UnixMicro", func(t1, t2 Time) bool { return t1.UnixMicro() == t2.UnixMicro() }},
+
+ {"MarshalBinary", func(t1, t2 Time) bool {
+ a1, b1 := t1.MarshalBinary()
+ a2, b2 := t2.MarshalBinary()
+ return bytes.Equal(a1, a2) && b1 == b2
+ }},
+ {"GobEncode", func(t1, t2 Time) bool {
+ a1, b1 := t1.GobEncode()
+ a2, b2 := t2.GobEncode()
+ return bytes.Equal(a1, a2) && b1 == b2
+ }},
+ {"MarshalJSON", func(t1, t2 Time) bool {
+ a1, b1 := t1.MarshalJSON()
+ a2, b2 := t2.MarshalJSON()
+ return bytes.Equal(a1, a2) && b1 == b2
+ }},
+ {"MarshalText", func(t1, t2 Time) bool {
+ a1, b1 := t1.MarshalText()
+ a2, b2 := t2.MarshalText()
+ return bytes.Equal(a1, a2) && b1 == b2
+ }},
+
+ {"Truncate", func(t1, t2 Time) bool { return t1.Truncate(Hour).Equal(t2.Truncate(Hour)) }},
+ {"Round", func(t1, t2 Time) bool { return t1.Round(Hour).Equal(t2.Round(Hour)) }},
+
+ {"== Time{}", func(t1, t2 Time) bool { return (t1 == Time{}) == (t2 == Time{}) }},
+}
+
+func TestDefaultLoc(t *testing.T) {
+ // Verify that all of Time's methods behave identically if loc is set to
+ // nil or UTC.
+ for _, tt := range defaultLocTests {
+ t1 := Time{}
+ t2 := Time{}.UTC()
+ if !tt.f(t1, t2) {
+ t.Errorf("Time{} and Time{}.UTC() behave differently for %s", tt.name)
+ }
+ }
+}
+
+func BenchmarkNow(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ t = Now()
+ }
+}
+
+func BenchmarkNowUnixNano(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ u = Now().UnixNano()
+ }
+}
+
+func BenchmarkNowUnixMilli(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ u = Now().UnixMilli()
+ }
+}
+
+func BenchmarkNowUnixMicro(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ u = Now().UnixMicro()
+ }
+}
+
+func BenchmarkFormat(b *testing.B) {
+ t := Unix(1265346057, 0)
+ for i := 0; i < b.N; i++ {
+ t.Format("Mon Jan 2 15:04:05 2006")
+ }
+}
+
+func BenchmarkFormatRFC3339(b *testing.B) {
+ t := Unix(1265346057, 0)
+ for i := 0; i < b.N; i++ {
+ t.Format("2006-01-02T15:04:05Z07:00")
+ }
+}
+
+func BenchmarkFormatRFC3339Nano(b *testing.B) {
+ t := Unix(1265346057, 0)
+ for i := 0; i < b.N; i++ {
+ t.Format("2006-01-02T15:04:05.999999999Z07:00")
+ }
+}
+
+func BenchmarkFormatNow(b *testing.B) {
+ // Like BenchmarkFormat, but easier, because the time zone
+ // lookup cache is optimized for the present.
+ t := Now()
+ for i := 0; i < b.N; i++ {
+ t.Format("Mon Jan 2 15:04:05 2006")
+ }
+}
+
+func BenchmarkMarshalJSON(b *testing.B) {
+ t := Now()
+ for i := 0; i < b.N; i++ {
+ t.MarshalJSON()
+ }
+}
+
+func BenchmarkMarshalText(b *testing.B) {
+ t := Now()
+ for i := 0; i < b.N; i++ {
+ t.MarshalText()
+ }
+}
+
+func BenchmarkParse(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ Parse(ANSIC, "Mon Jan 2 15:04:05 2006")
+ }
+}
+
+const testdataRFC3339UTC = "2020-08-22T11:27:43.123456789Z"
+
+func BenchmarkParseRFC3339UTC(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ Parse(RFC3339, testdataRFC3339UTC)
+ }
+}
+
+var testdataRFC3339UTCBytes = []byte(testdataRFC3339UTC)
+
+func BenchmarkParseRFC3339UTCBytes(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ Parse(RFC3339, string(testdataRFC3339UTCBytes))
+ }
+}
+
+const testdataRFC3339TZ = "2020-08-22T11:27:43.123456789-02:00"
+
+func BenchmarkParseRFC3339TZ(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ Parse(RFC3339, testdataRFC3339TZ)
+ }
+}
+
+var testdataRFC3339TZBytes = []byte(testdataRFC3339TZ)
+
+func BenchmarkParseRFC3339TZBytes(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ Parse(RFC3339, string(testdataRFC3339TZBytes))
+ }
+}
+
+func BenchmarkParseDuration(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ ParseDuration("9007199254.740993ms")
+ ParseDuration("9007199254740993ns")
+ }
+}
+
+func BenchmarkHour(b *testing.B) {
+ t := Now()
+ for i := 0; i < b.N; i++ {
+ _ = t.Hour()
+ }
+}
+
+func BenchmarkSecond(b *testing.B) {
+ t := Now()
+ for i := 0; i < b.N; i++ {
+ _ = t.Second()
+ }
+}
+
+func BenchmarkYear(b *testing.B) {
+ t := Now()
+ for i := 0; i < b.N; i++ {
+ _ = t.Year()
+ }
+}
+
+func BenchmarkDay(b *testing.B) {
+ t := Now()
+ for i := 0; i < b.N; i++ {
+ _ = t.Day()
+ }
+}
+
+func BenchmarkISOWeek(b *testing.B) {
+ t := Now()
+ for i := 0; i < b.N; i++ {
+ _, _ = t.ISOWeek()
+ }
+}
+
+func BenchmarkGoString(b *testing.B) {
+ t := Now()
+ for i := 0; i < b.N; i++ {
+ _ = t.GoString()
+ }
+}
+
+func BenchmarkUnmarshalText(b *testing.B) {
+ var t Time
+ in := []byte("2020-08-22T11:27:43.123456789-02:00")
+ for i := 0; i < b.N; i++ {
+ t.UnmarshalText(in)
+ }
+}
+
+func TestMarshalBinaryZeroTime(t *testing.T) {
+ t0 := Time{}
+ enc, err := t0.MarshalBinary()
+ if err != nil {
+ t.Fatal(err)
+ }
+ t1 := Now() // not zero
+ if err := t1.UnmarshalBinary(enc); err != nil {
+ t.Fatal(err)
+ }
+ if t1 != t0 {
+ t.Errorf("t0=%#v\nt1=%#v\nwant identical structures", t0, t1)
+ }
+}
+
+func TestMarshalBinaryVersion2(t *testing.T) {
+ t0, err := Parse(RFC3339, "1880-01-01T00:00:00Z")
+ if err != nil {
+ t.Errorf("Failed to parse time, error = %v", err)
+ }
+ loc, err := LoadLocation("US/Eastern")
+ if err != nil {
+ t.Errorf("Failed to load location, error = %v", err)
+ }
+ t1 := t0.In(loc)
+ b, err := t1.MarshalBinary()
+ if err != nil {
+ t.Errorf("Failed to Marshal, error = %v", err)
+ }
+
+ t2 := Time{}
+ err = t2.UnmarshalBinary(b)
+ if err != nil {
+ t.Errorf("Failed to Unmarshal, error = %v", err)
+ }
+
+ if !(t0.Equal(t1) && t1.Equal(t2)) {
+ if !t0.Equal(t1) {
+ t.Errorf("The result t1: %+v after Marshal is not matched original t0: %+v", t1, t0)
+ }
+ if !t1.Equal(t2) {
+ t.Errorf("The result t2: %+v after Unmarshal is not matched original t1: %+v", t2, t1)
+ }
+ }
+}
+
+func TestUnmarshalTextAllocations(t *testing.T) {
+ in := []byte(testdataRFC3339UTC) // short enough to be stack allocated
+ if allocs := testing.AllocsPerRun(100, func() {
+ var t Time
+ t.UnmarshalText(in)
+ }); allocs != 0 {
+ t.Errorf("got %v allocs, want 0 allocs", allocs)
+ }
+}
+
+// Issue 17720: Zero value of time.Month fails to print
+func TestZeroMonthString(t *testing.T) {
+ if got, want := Month(0).String(), "%!Month(0)"; got != want {
+ t.Errorf("zero month = %q; want %q", got, want)
+ }
+}
+
+// Issue 24692: Out of range weekday panics
+func TestWeekdayString(t *testing.T) {
+ if got, want := Tuesday.String(), "Tuesday"; got != want {
+ t.Errorf("Tuesday weekday = %q; want %q", got, want)
+ }
+ if got, want := Weekday(14).String(), "%!Weekday(14)"; got != want {
+ t.Errorf("14th weekday = %q; want %q", got, want)
+ }
+}
+
+func TestReadFileLimit(t *testing.T) {
+ const zero = "/dev/zero"
+ if _, err := os.Stat(zero); err != nil {
+ t.Skip("skipping test without a /dev/zero")
+ }
+ _, err := ReadFile(zero)
+ if err == nil || !strings.Contains(err.Error(), "is too large") {
+ t.Errorf("readFile(%q) error = %v; want error containing 'is too large'", zero, err)
+ }
+}
+
+// Issue 25686: hard crash on concurrent timer access.
+// Issue 37400: panic with "racy use of timers"
+// This test deliberately invokes a race condition.
+// We are testing that we don't crash with "fatal error: panic holding locks",
+// and that we also don't panic.
+func TestConcurrentTimerReset(t *testing.T) {
+ const goroutines = 8
+ const tries = 1000
+ var wg sync.WaitGroup
+ wg.Add(goroutines)
+ timer := NewTimer(Hour)
+ for i := 0; i < goroutines; i++ {
+ go func(i int) {
+ defer wg.Done()
+ for j := 0; j < tries; j++ {
+ timer.Reset(Hour + Duration(i*j))
+ }
+ }(i)
+ }
+ wg.Wait()
+}
+
+// Issue 37400: panic with "racy use of timers".
+func TestConcurrentTimerResetStop(t *testing.T) {
+ const goroutines = 8
+ const tries = 1000
+ var wg sync.WaitGroup
+ wg.Add(goroutines * 2)
+ timer := NewTimer(Hour)
+ for i := 0; i < goroutines; i++ {
+ go func(i int) {
+ defer wg.Done()
+ for j := 0; j < tries; j++ {
+ timer.Reset(Hour + Duration(i*j))
+ }
+ }(i)
+ go func(i int) {
+ defer wg.Done()
+ timer.Stop()
+ }(i)
+ }
+ wg.Wait()
+}
+
+func TestTimeIsDST(t *testing.T) {
+ undo := DisablePlatformSources()
+ defer undo()
+
+ tzWithDST, err := LoadLocation("Australia/Sydney")
+ if err != nil {
+ t.Fatalf("could not load tz 'Australia/Sydney': %v", err)
+ }
+ tzWithoutDST, err := LoadLocation("Australia/Brisbane")
+ if err != nil {
+ t.Fatalf("could not load tz 'Australia/Brisbane': %v", err)
+ }
+ tzFixed := FixedZone("FIXED_TIME", 12345)
+
+ tests := [...]struct {
+ time Time
+ want bool
+ }{
+ 0: {Date(2009, 1, 1, 12, 0, 0, 0, UTC), false},
+ 1: {Date(2009, 6, 1, 12, 0, 0, 0, UTC), false},
+ 2: {Date(2009, 1, 1, 12, 0, 0, 0, tzWithDST), true},
+ 3: {Date(2009, 6, 1, 12, 0, 0, 0, tzWithDST), false},
+ 4: {Date(2009, 1, 1, 12, 0, 0, 0, tzWithoutDST), false},
+ 5: {Date(2009, 6, 1, 12, 0, 0, 0, tzWithoutDST), false},
+ 6: {Date(2009, 1, 1, 12, 0, 0, 0, tzFixed), false},
+ 7: {Date(2009, 6, 1, 12, 0, 0, 0, tzFixed), false},
+ }
+
+ for i, tt := range tests {
+ got := tt.time.IsDST()
+ if got != tt.want {
+ t.Errorf("#%d:: (%#v).IsDST()=%t, want %t", i, tt.time.Format(RFC3339), got, tt.want)
+ }
+ }
+}
+
+func TestTimeAddSecOverflow(t *testing.T) {
+ // Test it with positive delta.
+ var maxInt64 int64 = 1<<63 - 1
+ timeExt := maxInt64 - UnixToInternal - 50
+ notMonoTime := Unix(timeExt, 0)
+ for i := int64(0); i < 100; i++ {
+ sec := notMonoTime.Unix()
+ notMonoTime = notMonoTime.Add(Duration(i * 1e9))
+ if newSec := notMonoTime.Unix(); newSec != sec+i && newSec+UnixToInternal != maxInt64 {
+ t.Fatalf("time ext: %d overflows with positive delta, overflow threshold: %d", newSec, maxInt64)
+ }
+ }
+
+ // Test it with negative delta.
+ maxInt64 = -maxInt64
+ notMonoTime = NotMonoNegativeTime
+ for i := int64(0); i > -100; i-- {
+ sec := notMonoTime.Unix()
+ notMonoTime = notMonoTime.Add(Duration(i * 1e9))
+ if newSec := notMonoTime.Unix(); newSec != sec+i && newSec+UnixToInternal != maxInt64 {
+ t.Fatalf("time ext: %d overflows with positive delta, overflow threshold: %d", newSec, maxInt64)
+ }
+ }
+}
+
+// Issue 49284: time: ParseInLocation incorrectly because of Daylight Saving Time
+func TestTimeWithZoneTransition(t *testing.T) {
+ undo := DisablePlatformSources()
+ defer undo()
+
+ loc, err := LoadLocation("Asia/Shanghai")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ tests := [...]struct {
+ give Time
+ want Time
+ }{
+ // 14 Apr 1991 - Daylight Saving Time Started
+ // When time of "Asia/Shanghai" was about to reach
+ // Sunday, 14 April 1991, 02:00:00 clocks were turned forward 1 hour to
+ // Sunday, 14 April 1991, 03:00:00 local daylight time instead.
+ // The UTC time was 13 April 1991, 18:00:00
+ 0: {Date(1991, April, 13, 17, 50, 0, 0, loc), Date(1991, April, 13, 9, 50, 0, 0, UTC)},
+ 1: {Date(1991, April, 13, 18, 0, 0, 0, loc), Date(1991, April, 13, 10, 0, 0, 0, UTC)},
+ 2: {Date(1991, April, 14, 1, 50, 0, 0, loc), Date(1991, April, 13, 17, 50, 0, 0, UTC)},
+ 3: {Date(1991, April, 14, 3, 0, 0, 0, loc), Date(1991, April, 13, 18, 0, 0, 0, UTC)},
+
+ // 15 Sep 1991 - Daylight Saving Time Ended
+ // When local daylight time of "Asia/Shanghai" was about to reach
+ // Sunday, 15 September 1991, 02:00:00 clocks were turned backward 1 hour to
+ // Sunday, 15 September 1991, 01:00:00 local standard time instead.
+ // The UTC time was 14 September 1991, 17:00:00
+ 4: {Date(1991, September, 14, 16, 50, 0, 0, loc), Date(1991, September, 14, 7, 50, 0, 0, UTC)},
+ 5: {Date(1991, September, 14, 17, 0, 0, 0, loc), Date(1991, September, 14, 8, 0, 0, 0, UTC)},
+ 6: {Date(1991, September, 15, 0, 50, 0, 0, loc), Date(1991, September, 14, 15, 50, 0, 0, UTC)},
+ 7: {Date(1991, September, 15, 2, 00, 0, 0, loc), Date(1991, September, 14, 18, 00, 0, 0, UTC)},
+ }
+
+ for i, tt := range tests {
+ if !tt.give.Equal(tt.want) {
+ t.Errorf("#%d:: %#v is not equal to %#v", i, tt.give.Format(RFC3339), tt.want.Format(RFC3339))
+ }
+ }
+}
+
+func TestZoneBounds(t *testing.T) {
+ undo := DisablePlatformSources()
+ defer undo()
+ loc, err := LoadLocation("Asia/Shanghai")
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ // The ZoneBounds of a UTC location would just return two zero Time.
+ for _, test := range utctests {
+ sec := test.seconds
+ golden := &test.golden
+ tm := Unix(sec, 0).UTC()
+ start, end := tm.ZoneBounds()
+ if !(start.IsZero() && end.IsZero()) {
+ t.Errorf("ZoneBounds of %+v expects two zero Time, got:\n start=%v\n end=%v", *golden, start, end)
+ }
+ }
+
+ // If the zone begins at the beginning of time, start will be returned as a zero Time.
+ // Use math.MinInt32 to avoid overflow of int arguments on 32-bit systems.
+ beginTime := Date(math.MinInt32, January, 1, 0, 0, 0, 0, loc)
+ start, end := beginTime.ZoneBounds()
+ if !start.IsZero() || end.IsZero() {
+ t.Errorf("ZoneBounds of %v expects start is zero Time, got:\n start=%v\n end=%v", beginTime, start, end)
+ }
+
+ // If the zone goes on forever, end will be returned as a zero Time.
+ // Use math.MaxInt32 to avoid overflow of int arguments on 32-bit systems.
+ foreverTime := Date(math.MaxInt32, January, 1, 0, 0, 0, 0, loc)
+ start, end = foreverTime.ZoneBounds()
+ if start.IsZero() || !end.IsZero() {
+ t.Errorf("ZoneBounds of %v expects end is zero Time, got:\n start=%v\n end=%v", foreverTime, start, end)
+ }
+
+ // Check some real-world cases to make sure we're getting the right bounds.
+ boundOne := Date(1990, September, 16, 1, 0, 0, 0, loc)
+ boundTwo := Date(1991, April, 14, 3, 0, 0, 0, loc)
+ boundThree := Date(1991, September, 15, 1, 0, 0, 0, loc)
+ makeLocalTime := func(sec int64) Time { return Unix(sec, 0) }
+ realTests := [...]struct {
+ giveTime Time
+ wantStart Time
+ wantEnd Time
+ }{
+ // The ZoneBounds of "Asia/Shanghai" Daylight Saving Time
+ 0: {Date(1991, April, 13, 17, 50, 0, 0, loc), boundOne, boundTwo},
+ 1: {Date(1991, April, 13, 18, 0, 0, 0, loc), boundOne, boundTwo},
+ 2: {Date(1991, April, 14, 1, 50, 0, 0, loc), boundOne, boundTwo},
+ 3: {boundTwo, boundTwo, boundThree},
+ 4: {Date(1991, September, 14, 16, 50, 0, 0, loc), boundTwo, boundThree},
+ 5: {Date(1991, September, 14, 17, 0, 0, 0, loc), boundTwo, boundThree},
+ 6: {Date(1991, September, 15, 0, 50, 0, 0, loc), boundTwo, boundThree},
+
+ // The ZoneBounds of a "Asia/Shanghai" after the last transition (Standard Time)
+ 7: {boundThree, boundThree, Time{}},
+ 8: {Date(1991, December, 15, 1, 50, 0, 0, loc), boundThree, Time{}},
+ 9: {Date(1992, April, 13, 17, 50, 0, 0, loc), boundThree, Time{}},
+ 10: {Date(1992, April, 13, 18, 0, 0, 0, loc), boundThree, Time{}},
+ 11: {Date(1992, April, 14, 1, 50, 0, 0, loc), boundThree, Time{}},
+ 12: {Date(1992, September, 14, 16, 50, 0, 0, loc), boundThree, Time{}},
+ 13: {Date(1992, September, 14, 17, 0, 0, 0, loc), boundThree, Time{}},
+ 14: {Date(1992, September, 15, 0, 50, 0, 0, loc), boundThree, Time{}},
+
+ // The ZoneBounds of a local time would return two local Time.
+ // Note: We preloaded "America/Los_Angeles" as time.Local for testing
+ 15: {makeLocalTime(0), makeLocalTime(-5756400), makeLocalTime(9972000)},
+ 16: {makeLocalTime(1221681866), makeLocalTime(1205056800), makeLocalTime(1225616400)},
+ 17: {makeLocalTime(2152173599), makeLocalTime(2145916800), makeLocalTime(2152173600)},
+ 18: {makeLocalTime(2152173600), makeLocalTime(2152173600), makeLocalTime(2172733200)},
+ 19: {makeLocalTime(2152173601), makeLocalTime(2152173600), makeLocalTime(2172733200)},
+ 20: {makeLocalTime(2159200800), makeLocalTime(2152173600), makeLocalTime(2172733200)},
+ 21: {makeLocalTime(2172733199), makeLocalTime(2152173600), makeLocalTime(2172733200)},
+ 22: {makeLocalTime(2172733200), makeLocalTime(2172733200), makeLocalTime(2177452800)},
+ }
+ for i, tt := range realTests {
+ start, end := tt.giveTime.ZoneBounds()
+ if !start.Equal(tt.wantStart) || !end.Equal(tt.wantEnd) {
+ t.Errorf("#%d:: ZoneBounds of %v expects right bounds:\n got start=%v\n want start=%v\n got end=%v\n want end=%v",
+ i, tt.giveTime, start, tt.wantStart, end, tt.wantEnd)
+ }
+ }
+}
diff --git a/src/time/tzdata/tzdata.go b/src/time/tzdata/tzdata.go
new file mode 100644
index 0000000..95c759b
--- /dev/null
+++ b/src/time/tzdata/tzdata.go
@@ -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
+}
diff --git a/src/time/tzdata_test.go b/src/time/tzdata_test.go
new file mode 100644
index 0000000..33c6589
--- /dev/null
+++ b/src/time/tzdata_test.go
@@ -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
+ }
+}
diff --git a/src/time/zoneinfo.go b/src/time/zoneinfo.go
new file mode 100644
index 0000000..c8d1762
--- /dev/null
+++ b/src/time/zoneinfo.go
@@ -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
+}
diff --git a/src/time/zoneinfo_abbrs_windows.go b/src/time/zoneinfo_abbrs_windows.go
new file mode 100644
index 0000000..2783174
--- /dev/null
+++ b/src/time/zoneinfo_abbrs_windows.go
@@ -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
+}
diff --git a/src/time/zoneinfo_android.go b/src/time/zoneinfo_android.go
new file mode 100644
index 0000000..e4f688d
--- /dev/null
+++ b/src/time/zoneinfo_android.go
@@ -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
+}
diff --git a/src/time/zoneinfo_android_test.go b/src/time/zoneinfo_android_test.go
new file mode 100644
index 0000000..f8bd7f7
--- /dev/null
+++ b/src/time/zoneinfo_android_test.go
@@ -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)
+ }
+}
diff --git a/src/time/zoneinfo_goroot.go b/src/time/zoneinfo_goroot.go
new file mode 100644
index 0000000..92bdcf4
--- /dev/null
+++ b/src/time/zoneinfo_goroot.go
@@ -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
+}
diff --git a/src/time/zoneinfo_ios.go b/src/time/zoneinfo_ios.go
new file mode 100644
index 0000000..d6ad073
--- /dev/null
+++ b/src/time/zoneinfo_ios.go
@@ -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
+}
diff --git a/src/time/zoneinfo_js.go b/src/time/zoneinfo_js.go
new file mode 100644
index 0000000..8da34a2
--- /dev/null
+++ b/src/time/zoneinfo_js.go
@@ -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}
+}
diff --git a/src/time/zoneinfo_plan9.go b/src/time/zoneinfo_plan9.go
new file mode 100644
index 0000000..d13b623
--- /dev/null
+++ b/src/time/zoneinfo_plan9.go
@@ -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"
+}
diff --git a/src/time/zoneinfo_read.go b/src/time/zoneinfo_read.go
new file mode 100644
index 0000000..707dd11
--- /dev/null
+++ b/src/time/zoneinfo_read.go
@@ -0,0 +1,597 @@
+// 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"
+ "runtime"
+ "syscall"
+)
+
+// registerLoadFromEmbeddedTZData is called by the time/tzdata package,
+// if it is imported.
+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 {
+ for i := 0; i < len(p); i++ {
+ if p[i] == 0 {
+ return string(p[0: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
+}
diff --git a/src/time/zoneinfo_test.go b/src/time/zoneinfo_test.go
new file mode 100644
index 0000000..8cd37b5
--- /dev/null
+++ b/src/time/zoneinfo_test.go
@@ -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)
+ }
+ }
+}
diff --git a/src/time/zoneinfo_unix.go b/src/time/zoneinfo_unix.go
new file mode 100644
index 0000000..b52c67d
--- /dev/null
+++ b/src/time/zoneinfo_unix.go
@@ -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"
+}
diff --git a/src/time/zoneinfo_unix_test.go b/src/time/zoneinfo_unix_test.go
new file mode 100644
index 0000000..92680c4
--- /dev/null
+++ b/src/time/zoneinfo_unix_test.go
@@ -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)
+ }
+}
diff --git a/src/time/zoneinfo_wasip1.go b/src/time/zoneinfo_wasip1.go
new file mode 100644
index 0000000..b6c8bbc
--- /dev/null
+++ b/src/time/zoneinfo_wasip1.go
@@ -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"
+}
diff --git a/src/time/zoneinfo_windows.go b/src/time/zoneinfo_windows.go
new file mode 100644
index 0000000..c9f38ea
--- /dev/null
+++ b/src/time/zoneinfo_windows.go
@@ -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)
+}
diff --git a/src/time/zoneinfo_windows_test.go b/src/time/zoneinfo_windows_test.go
new file mode 100644
index 0000000..5196b8e
--- /dev/null
+++ b/src/time/zoneinfo_windows_test.go
@@ -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)
+ }
+}