diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:19:13 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-16 19:19:13 +0000 |
commit | ccd992355df7192993c666236047820244914598 (patch) | |
tree | f00fea65147227b7743083c6148396f74cd66935 /src/time | |
parent | Initial commit. (diff) | |
download | golang-1.21-ccd992355df7192993c666236047820244914598.tar.xz golang-1.21-ccd992355df7192993c666236047820244914598.zip |
Adding upstream version 1.21.8.upstream/1.21.8
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/time')
41 files changed, 12160 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..059c631 --- /dev/null +++ b/src/time/example_test.go @@ -0,0 +1,725 @@ +// 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(2009, 1, 1, 0, 0, 0, 0, time.UTC) + oneDayLater := start.AddDate(0, 0, 1) + oneMonthLater := start.AddDate(0, 1, 0) + oneYearLater := start.AddDate(1, 0, 0) + + 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) + + // Output: + // oneDayLater: start.AddDate(0, 0, 1) = 2009-01-02 00:00:00 +0000 UTC + // oneMonthLater: start.AddDate(0, 1, 0) = 2009-02-01 00:00:00 +0000 UTC + // oneYearLater: start.AddDate(1, 0, 0) = 2010-01-01 00:00:00 +0000 UTC +} + +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..cdab478 --- /dev/null +++ b/src/time/sleep.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 + +// 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. +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 Binary files differnew file mode 100644 index 0000000..465546b --- /dev/null +++ b/src/time/testdata/2020b_Europe_Berlin diff --git a/src/time/testdata/2021a_America_Nuuk b/src/time/testdata/2021a_America_Nuuk Binary files differnew file mode 100644 index 0000000..4ddc99d --- /dev/null +++ b/src/time/testdata/2021a_America_Nuuk diff --git a/src/time/testdata/2021a_Asia_Gaza b/src/time/testdata/2021a_Asia_Gaza Binary files differnew file mode 100644 index 0000000..58e9fdf --- /dev/null +++ b/src/time/testdata/2021a_Asia_Gaza diff --git a/src/time/testdata/2021a_Europe_Dublin b/src/time/testdata/2021a_Europe_Dublin Binary files differnew file mode 100644 index 0000000..4a45ea8 --- /dev/null +++ b/src/time/testdata/2021a_Europe_Dublin 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..e8aac59 --- /dev/null +++ b/src/time/time.go @@ -0,0 +1,1646 @@ +// 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(). +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 associated with it a Location, consulted when computing the +// presentation form of the time, such as in the Format, Hour, and Year methods. +// The methods Local, UTC, and In return a Time with a specific location. +// Changing the location in this way changes only the presentation; it does not +// change the instant in time being denoted and therefore does not affect the +// computations described in earlier paragraphs. +// +// 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 { + // Largest time is 2540400h10m10.000000000s + var buf [32]byte + 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: + return "0s" + 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 string(buf[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 { + te := t.ext + ue := u.ext + d := Duration(te - ue) + if d < 0 && te > ue { + return maxDuration // t - u is positive out of range + } + if d > 0 && te < ue { + return minDuration // t - u is negative out of range + } + return d + } + 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 + } +} + +// Since returns the time elapsed since t. +// It is shorthand for time.Now().Sub(t). +func Since(t Time) Duration { + var now Time + if t.wall&hasMonotonic != 0 { + // Common case optimization: if t has monotonic time, then Sub will use only it. + now = Time{hasMonotonic, runtimeNano() - startNano, nil} + } else { + now = Now() + } + return now.Sub(t) +} + +// Until returns the duration until t. +// It is shorthand for t.Sub(time.Now()). +func Until(t Time) Duration { + var now Time + if t.wall&hasMonotonic != 0 { + // Common case optimization: if t has monotonic time, then Sub will use only it. + now = Time{hasMonotonic, runtimeNano() - startNano, nil} + } else { + now = Now() + } + 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. +// +// 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..3b30f80 --- /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, int64(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(Duration(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), Duration(minDuration)}, + {Date(2109, 11, 23, 0, 0, 0, 0, UTC), Time{}, Duration(maxDuration)}, + {Time{}, Date(-2109, 11, 23, 0, 0, 0, 0, UTC), Duration(maxDuration)}, + {Date(-2109, 11, 23, 0, 0, 0, 0, UTC), Time{}, Duration(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), Duration(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), Duration(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 := Weekday(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..4edcf3d --- /dev/null +++ b/src/time/zoneinfo.go @@ -0,0 +1,708 @@ +// 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. +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 := lo + (hi-lo)/2 + 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..4d0e47d --- /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 +} + +// read returns the read 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..76d7975 --- /dev/null +++ b/src/time/zoneinfo_windows.go @@ -0,0 +1,239 @@ +// 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" (the latter down-level +// from Vista) 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 + if err = registry.LoadRegLoadMUIString(); err == nil { + // 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..f23d9dc --- /dev/null +++ b/src/time/zoneinfo_windows_test.go @@ -0,0 +1,71 @@ +// 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 + if err = registry.LoadRegLoadMUIString(); err == nil { + // 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) + } +} |