diff options
Diffstat (limited to 'src/pkg/time')
-rw-r--r-- | src/pkg/time/Makefile | 46 | ||||
-rw-r--r-- | src/pkg/time/example_test.go | 58 | ||||
-rw-r--r-- | src/pkg/time/format.go | 712 | ||||
-rw-r--r-- | src/pkg/time/internal_test.go | 13 | ||||
-rw-r--r-- | src/pkg/time/sleep.go | 212 | ||||
-rw-r--r-- | src/pkg/time/sleep_test.go | 130 | ||||
-rw-r--r-- | src/pkg/time/sys.go | 51 | ||||
-rw-r--r-- | src/pkg/time/sys_plan9.go | 66 | ||||
-rw-r--r-- | src/pkg/time/sys_posix.go | 18 | ||||
-rw-r--r-- | src/pkg/time/sys_unix.go | 76 | ||||
-rw-r--r-- | src/pkg/time/sys_windows.go | 73 | ||||
-rw-r--r-- | src/pkg/time/tick.go | 188 | ||||
-rw-r--r-- | src/pkg/time/tick_test.go | 24 | ||||
-rw-r--r-- | src/pkg/time/time.go | 1069 | ||||
-rw-r--r-- | src/pkg/time/time_test.go | 695 | ||||
-rw-r--r-- | src/pkg/time/zoneinfo.go | 204 | ||||
-rw-r--r-- | src/pkg/time/zoneinfo_plan9.go | 145 | ||||
-rw-r--r-- | src/pkg/time/zoneinfo_posix.go | 62 | ||||
-rw-r--r-- | src/pkg/time/zoneinfo_read.go | 341 | ||||
-rw-r--r-- | src/pkg/time/zoneinfo_unix.go | 227 | ||||
-rw-r--r-- | src/pkg/time/zoneinfo_windows.go | 275 |
21 files changed, 3309 insertions, 1376 deletions
diff --git a/src/pkg/time/Makefile b/src/pkg/time/Makefile deleted file mode 100644 index a6fce3fa1..000000000 --- a/src/pkg/time/Makefile +++ /dev/null @@ -1,46 +0,0 @@ -# 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. - -include ../../Make.inc - -TARG=time -GOFILES=\ - format.go\ - sleep.go\ - sys.go\ - tick.go\ - time.go\ - -GOFILES_freebsd=\ - sys_posix.go\ - zoneinfo_posix.go\ - zoneinfo_unix.go\ - -GOFILES_darwin=\ - sys_posix.go\ - zoneinfo_posix.go\ - zoneinfo_unix.go\ - -GOFILES_linux=\ - sys_posix.go\ - zoneinfo_posix.go\ - zoneinfo_unix.go\ - -GOFILES_openbsd=\ - sys_posix.go\ - zoneinfo_posix.go\ - zoneinfo_unix.go\ - -GOFILES_windows=\ - sys_posix.go\ - zoneinfo_windows.go\ - -GOFILES_plan9=\ - sys_plan9.go\ - zoneinfo_posix.go\ - zoneinfo_plan9.go\ - -GOFILES+=$(GOFILES_$(GOOS)) - -include ../../Make.pkg diff --git a/src/pkg/time/example_test.go b/src/pkg/time/example_test.go new file mode 100644 index 000000000..944cc789c --- /dev/null +++ b/src/pkg/time/example_test.go @@ -0,0 +1,58 @@ +// 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)) +} + +var c chan int + +func handle(int) {} + +func ExampleAfter() { + select { + case m := <-c: + handle(m) + case <-time.After(5 * time.Minute): + fmt.Println("timed out") + } +} + +func ExampleSleep() { + time.Sleep(100 * time.Millisecond) +} + +func statusUpdate() string { return "" } + +func ExampleTick() { + c := time.Tick(1 * time.Minute) + for now := range c { + fmt.Printf("%v %s\n", now, 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 +} diff --git a/src/pkg/time/format.go b/src/pkg/time/format.go index 5ddd54812..ad52bab21 100644 --- a/src/pkg/time/format.go +++ b/src/pkg/time/format.go @@ -1,33 +1,30 @@ -package time +// 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. -import ( - "bytes" - "os" - "strconv" -) +package time -const ( - numeric = iota - alphabetic - separator - plus - minus -) +import "errors" // These are predefined layouts for use in Time.Format. // The standard time used in the layouts is: -// Mon Jan 2 15:04:05 MST 2006 (MST is GMT-0700) -// which is Unix time 1136243045. -// (Think of it as 01/02 03:04:05PM '06 -0700.) -// To define your own format, write down what the standard -// time would look like formatted your way. +// Mon Jan 2 15:04:05 MST 2006 +// which is Unix time 1136243045. Since MST is GMT-0700, +// the standard time can be thought of as +// 01/02 03:04:05PM '06 -0700 +// To define your own format, write down what the standard time would look +// like formatted your way; see the values of constants like ANSIC, +// StampMicro or Kitchen for examples. // // Within the format string, an underscore _ represents a space that may be // replaced by a digit if the following number (a day) has two digits; for // compatibility with fixed-width Unix time formats. // // A decimal point followed by one or more zeros represents a fractional -// second. When parsing (only), the input may contain a fractional second +// second, printed to the given number of decimal places. A decimal point +// followed by one or more nines represents a fractional second, printed to +// the given number of decimal places, with trailing zeros removed. +// 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 a decimal point followed by a maximal // series of digits is parsed as a fractional second. @@ -41,16 +38,17 @@ const ( // Z0700 Z or ±hhmm // Z07:00 Z or ±hh:mm const ( - 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 1504 MST" - // RFC822 with Zulu time. - RFC822Z = "02 Jan 06 1504 -0700" - RFC850 = "Monday, 02-Jan-06 15:04:05 MST" - RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" - RFC3339 = "2006-01-02T15:04:05Z07:00" - Kitchen = "3:04PM" + 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 1504 MST" + RFC822Z = "02 Jan 06 1504 -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" @@ -165,15 +163,17 @@ func nextStdChunk(layout string) (prefix, std, suffix string) { if len(layout) >= i+6 && layout[i:i+6] == stdISO8601ColonTZ { return layout[0:i], layout[i : i+6], layout[i+6:] } - case '.': // .000 - multiple digits of zeros (only) for fractional seconds. - numZeros := 0 - var j int - for j = i + 1; j < len(layout) && layout[j] == '0'; j++ { - numZeros++ - } - // String of digits must end here - only fractional second is all zeros. - if numZeros > 0 && !isDigit(layout, j) { - return layout[0:i], layout[i : i+1+numZeros], layout[i+1+numZeros:] + case '.': // .000 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) { + return layout[0:i], layout[i:j], layout[j:] + } } } } @@ -232,17 +232,78 @@ var longMonthNames = []string{ "December", } -func lookup(tab []string, val string) (int, string, os.Error) { +// match returns true if 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) && val[0:len(v)] == v { + if len(val) >= len(v) && match(val[0:len(v)], v) { return i, val[len(v):], nil } } return -1, val, errBad } +// Duplicates functionality in strconv, but avoids dependency. +func itoa(x int) string { + var buf [32]byte + n := len(buf) + if x == 0 { + return "0" + } + u := uint(x) + if x < 0 { + u = -u + } + for u > 0 { + n-- + buf[n] = byte(u%10 + '0') + u /= 10 + } + if x < 0 { + n-- + buf[n] = '-' + } + return string(buf[n:]) +} + +// Never printed, just needs to be non-nil for return by atoi. +var atoiError = errors.New("time: invalid number") + +// Duplicates functionality in strconv, but avoids dependency. +func atoi(s string) (x int, err error) { + neg := false + if s != "" && s[0] == '-' { + neg = true + s = s[1:] + } + x, rem, err := leadingInt(s) + if err != nil || rem != "" { + return 0, atoiError + } + if neg { + x = -x + } + return x, nil +} + func pad(i int, padding string) string { - s := strconv.Itoa(i) + s := itoa(i) if i < 10 { s = padding + s } @@ -252,10 +313,10 @@ func pad(i int, padding string) string { func zeroPad(i int) string { return pad(i, "0") } // formatNano formats a fractional second, as nanoseconds. -func formatNano(nanosec, n int) string { +func formatNano(nanosec, n int, trim bool) string { // User might give us bad data. Make sure it's positive and in range. // They'll get nonsense output but it will have the right format. - s := strconv.Uitoa(uint(nanosec) % 1e9) + s := itoa(int(uint(nanosec) % 1e9)) // Zero pad left without fmt. if len(s) < 9 { s = "000000000"[:9-len(s)] + s @@ -263,17 +324,51 @@ func formatNano(nanosec, n int) string { if n > 9 { n = 9 } + if trim { + for n > 0 && s[n-1] == '0' { + n-- + } + if n == 0 { + return "" + } + } return "." + s[:n] } +// String returns the time formatted using the format string +// "2006-01-02 15:04:05.999999999 -0700 MST" +func (t Time) String() string { + return t.Format("2006-01-02 15:04:05.999999999 -0700 MST") +} + +type buffer []byte + +func (b *buffer) WriteString(s string) { + *b = append(*b, s...) +} + +func (b *buffer) String() string { + return string([]byte(*b)) +} + // Format returns a textual representation of the time value formatted // according to layout. The layout defines the format by showing the -// representation of a standard time, which is then used to describe -// the time to be formatted. Predefined layouts ANSIC, UnixDate, -// RFC3339 and others describe standard representations. For more -// information about the formats, see the documentation for ANSIC. -func (t *Time) Format(layout string) string { - b := new(bytes.Buffer) +// representation of the standard time, +// Mon Jan 2 15:04:05 -0700 MST 2006 +// which is then used to describe the time to be formatted. Predefined +// layouts ANSIC, UnixDate, RFC3339 and others describe standard +// representations. For more information about the formats and the +// definition of the standard time, see the documentation for ANSIC. +func (t Time) Format(layout string) string { + var ( + year int = -1 + month Month + day int + hour int = -1 + min int + sec int + b buffer + ) // Each iteration generates one std value. for { prefix, std, suffix := nextStdChunk(layout) @@ -281,62 +376,109 @@ func (t *Time) Format(layout string) string { if std == "" { break } + + // Compute year, month, day if needed. + if year < 0 { + // Jan 01 02 2006 + if a, z := std[0], std[len(std)-1]; a == 'J' || a == 'j' || z == '1' || z == '2' || z == '6' { + year, month, day = t.Date() + } + } + + // Compute hour, minute, second if needed. + if hour < 0 { + // 03 04 05 15 pm + if z := std[len(std)-1]; z == '3' || z == '4' || z == '5' || z == 'm' || z == 'M' { + hour, min, sec = t.Clock() + } + } + var p string switch std { case stdYear: - p = zeroPad(int(t.Year % 100)) + p = zeroPad(year % 100) case stdLongYear: - p = strconv.Itoa64(t.Year) + // Pad year to at least 4 digits. + p = itoa(year) + switch { + case year <= -1000: + // ok + case year <= -100: + p = p[:1] + "0" + p[1:] + case year <= -10: + p = p[:1] + "00" + p[1:] + case year < 0: + p = p[:1] + "000" + p[1:] + case year < 10: + p = "000" + p + case year < 100: + p = "00" + p + case year < 1000: + p = "0" + p + } case stdMonth: - p = shortMonthNames[t.Month] + p = month.String()[:3] case stdLongMonth: - p = longMonthNames[t.Month] + p = month.String() case stdNumMonth: - p = strconv.Itoa(t.Month) + p = itoa(int(month)) case stdZeroMonth: - p = zeroPad(t.Month) + p = zeroPad(int(month)) case stdWeekDay: - p = shortDayNames[t.Weekday] + p = t.Weekday().String()[:3] case stdLongWeekDay: - p = longDayNames[t.Weekday] + p = t.Weekday().String() case stdDay: - p = strconv.Itoa(t.Day) + p = itoa(day) case stdUnderDay: - p = pad(t.Day, " ") + p = pad(day, " ") case stdZeroDay: - p = zeroPad(t.Day) + p = zeroPad(day) case stdHour: - p = zeroPad(t.Hour) + p = zeroPad(hour) case stdHour12: // Noon is 12PM, midnight is 12AM. - hr := t.Hour % 12 + hr := hour % 12 if hr == 0 { hr = 12 } - p = strconv.Itoa(hr) + p = itoa(hr) case stdZeroHour12: // Noon is 12PM, midnight is 12AM. - hr := t.Hour % 12 + hr := hour % 12 if hr == 0 { hr = 12 } p = zeroPad(hr) case stdMinute: - p = strconv.Itoa(t.Minute) + p = itoa(min) case stdZeroMinute: - p = zeroPad(t.Minute) + p = zeroPad(min) case stdSecond: - p = strconv.Itoa(t.Second) + p = itoa(sec) case stdZeroSecond: - p = zeroPad(t.Second) + p = zeroPad(sec) + case stdPM: + if hour >= 12 { + p = "PM" + } else { + p = "AM" + } + case stdpm: + if hour >= 12 { + p = "pm" + } else { + p = "am" + } case stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumColonTZ: // Ugly special case. We cheat and take the "Z" variants // to mean "the time zone as formatted for ISO 8601". - if t.ZoneOffset == 0 && std[0] == 'Z' { + _, offset := t.Zone() + if offset == 0 && std[0] == 'Z' { p = "Z" break } - zone := t.ZoneOffset / 60 // convert to minutes + zone := offset / 60 // convert to minutes if zone < 0 { p = "-" zone = -zone @@ -348,25 +490,14 @@ func (t *Time) Format(layout string) string { p += ":" } p += zeroPad(zone % 60) - case stdPM: - if t.Hour >= 12 { - p = "PM" - } else { - p = "AM" - } - case stdpm: - if t.Hour >= 12 { - p = "pm" - } else { - p = "am" - } case stdTZ: - if t.Zone != "" { - p = t.Zone + name, offset := t.Zone() + if name != "" { + p = name } else { // No time zone known for this time, but we must print one. // Use the -0700 format. - zone := t.ZoneOffset / 60 // convert to minutes + zone := offset / 60 // convert to minutes if zone < 0 { p = "-" zone = -zone @@ -377,8 +508,8 @@ func (t *Time) Format(layout string) string { p += zeroPad(zone % 60) } default: - if len(std) >= 2 && std[0:2] == ".0" { - p = formatNano(t.Nanosecond, len(std)-1) + if len(std) >= 2 && (std[0:2] == ".0" || std[0:2] == ".9") { + p = formatNano(t.Nanosecond(), len(std)-1, std[1] == '9') } } b.WriteString(p) @@ -387,15 +518,7 @@ func (t *Time) Format(layout string) string { return b.String() } -// String returns a Unix-style representation of the time value. -func (t *Time) String() string { - if t == nil { - return "<nil>" - } - return t.Format(UnixDate) -} - -var errBad = os.NewError("bad value for field") // placeholder not passed to user +var errBad = errors.New("bad value for field") // placeholder not passed to user // ParseError describes a problem parsing a time string. type ParseError struct { @@ -406,17 +529,21 @@ type ParseError struct { Message string } -// String is the string representation of a ParseError. -func (e *ParseError) String() string { +func quote(s string) string { + return "\"" + s + "\"" +} + +// Error returns the string representation of a ParseError. +func (e *ParseError) Error() string { if e.Message == "" { return "parsing time " + - strconv.Quote(e.Value) + " as " + - strconv.Quote(e.Layout) + ": cannot parse " + - strconv.Quote(e.ValueElem) + " as " + - strconv.Quote(e.LayoutElem) + quote(e.Value) + " as " + + quote(e.Layout) + ": cannot parse " + + quote(e.ValueElem) + " as " + + quote(e.LayoutElem) } return "parsing time " + - strconv.Quote(e.Value) + e.Message + quote(e.Value) + e.Message } // isDigit returns true if s[i] is a decimal digit, false if not or @@ -432,7 +559,7 @@ func isDigit(s string, i int) bool { // getnum parses s[0:1] or s[0:2] (fixed forces the latter) // as a decimal integer and returns the integer and the // remainder of the string. -func getnum(s string, fixed bool) (int, string, os.Error) { +func getnum(s string, fixed bool) (int, string, error) { if !isDigit(s, 0) { return 0, s, errBad } @@ -454,7 +581,7 @@ func cutspace(s string) string { // skip removes the given prefix from value, // treating runs of space characters as equivalent. -func skip(value, prefix string) (string, os.Error) { +func skip(value, prefix string) (string, error) { for len(prefix) > 0 { if prefix[0] == ' ' { if len(value) > 0 && value[0] != ' ' { @@ -474,35 +601,50 @@ func skip(value, prefix string) (string, os.Error) { } // Parse parses a formatted string and returns the time value it represents. -// The layout defines the format by showing the representation of a standard -// time, which is then used to describe the string to be parsed. Predefined -// layouts ANSIC, UnixDate, RFC3339 and others describe standard -// representations.For more information about the formats, see the -// documentation for ANSIC. +// The layout defines the format by showing the representation of the +// standard time, +// Mon Jan 2 15:04:05 -0700 MST 2006 +// which is then used to describe the string to be parsed. Predefined layouts +// ANSIC, UnixDate, RFC3339 and others describe standard representations. For +// more information about the formats and the definition of the standard +// time, see the documentation for ANSIC. // -// Only those elements present in the value will be set in the returned time -// structure. Also, if the input string represents an inconsistent time -// (such as having the wrong day of the week), the returned value will also -// be inconsistent. In any case, the elements of the returned time will be -// sane: hours in 0..23, minutes in 0..59, day of month in 1..31, etc. -// Years must be in the range 0000..9999. -func Parse(alayout, avalue string) (*Time, os.Error) { - var t Time +// Elements omitted from the value 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. +// Years must be in the range 0000..9999. The day of the week is checked +// for syntax but it is otherwise ignored. +func Parse(layout, value string) (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? - layout, value := alayout, avalue + + // Time being constructed. + var ( + year int + month int = 1 // January + day 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 os.Error + var err error prefix, std, suffix := nextStdChunk(layout) value, err = skip(value, prefix) if err != nil { - return nil, &ParseError{alayout, avalue, prefix, value, ""} + return Time{}, &ParseError{alayout, avalue, prefix, value, ""} } if len(std) == 0 { if len(value) != 0 { - return nil, &ParseError{alayout, avalue, "", value, ": extra text: " + value} + return Time{}, &ParseError{alayout, avalue, "", value, ": extra text: " + value} } break } @@ -515,11 +657,11 @@ func Parse(alayout, avalue string) (*Time, os.Error) { break } p, value = value[0:2], value[2:] - t.Year, err = strconv.Atoi64(p) - if t.Year >= 69 { // Unix time starts Dec 31 1969 in some time zones - t.Year += 1900 + year, err = atoi(p) + if year >= 69 { // Unix time starts Dec 31 1969 in some time zones + year += 1900 } else { - t.Year += 2000 + year += 2000 } case stdLongYear: if len(value) < 4 || !isDigit(value, 0) { @@ -527,47 +669,47 @@ func Parse(alayout, avalue string) (*Time, os.Error) { break } p, value = value[0:4], value[4:] - t.Year, err = strconv.Atoi64(p) + year, err = atoi(p) case stdMonth: - t.Month, value, err = lookup(shortMonthNames, value) + month, value, err = lookup(shortMonthNames, value) case stdLongMonth: - t.Month, value, err = lookup(longMonthNames, value) + month, value, err = lookup(longMonthNames, value) case stdNumMonth, stdZeroMonth: - t.Month, value, err = getnum(value, std == stdZeroMonth) - if t.Month <= 0 || 12 < t.Month { + month, value, err = getnum(value, std == stdZeroMonth) + if month <= 0 || 12 < month { rangeErrString = "month" } case stdWeekDay: - t.Weekday, value, err = lookup(shortDayNames, value) + // Ignore weekday except for error checking. + _, value, err = lookup(shortDayNames, value) case stdLongWeekDay: - t.Weekday, value, err = lookup(longDayNames, value) + _, value, err = lookup(longDayNames, value) case stdDay, stdUnderDay, stdZeroDay: if std == stdUnderDay && len(value) > 0 && value[0] == ' ' { value = value[1:] } - t.Day, value, err = getnum(value, std == stdZeroDay) - if t.Day < 0 || 31 < t.Day { - // TODO: be more thorough in date check? + day, value, err = getnum(value, std == stdZeroDay) + if day < 0 || 31 < day { rangeErrString = "day" } case stdHour: - t.Hour, value, err = getnum(value, false) - if t.Hour < 0 || 24 <= t.Hour { + hour, value, err = getnum(value, false) + if hour < 0 || 24 <= hour { rangeErrString = "hour" } case stdHour12, stdZeroHour12: - t.Hour, value, err = getnum(value, std == stdZeroHour12) - if t.Hour < 0 || 12 < t.Hour { + hour, value, err = getnum(value, std == stdZeroHour12) + if hour < 0 || 12 < hour { rangeErrString = "hour" } case stdMinute, stdZeroMinute: - t.Minute, value, err = getnum(value, std == stdZeroMinute) - if t.Minute < 0 || 60 <= t.Minute { + min, value, err = getnum(value, std == stdZeroMinute) + if min < 0 || 60 <= min { rangeErrString = "minute" } case stdSecond, stdZeroSecond: - t.Second, value, err = getnum(value, std == stdZeroSecond) - if t.Second < 0 || 60 <= t.Second { + sec, value, err = getnum(value, std == stdZeroSecond) + if sec < 0 || 60 <= sec { rangeErrString = "second" } // Special case: do we have a fractional second but no @@ -582,16 +724,44 @@ func Parse(alayout, avalue string) (*Time, os.Error) { n := 2 for ; n < len(value) && isDigit(value, n); n++ { } - rangeErrString, err = t.parseNanoseconds(value, 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, stdNumTZ, stdNumShortTZ, stdNumColonTZ: if std[0] == 'Z' && len(value) >= 1 && value[0] == 'Z' { value = value[1:] - t.Zone = "UTC" + z = UTC break } - var sign, hh, mm string + var sign, hour, min string if std == stdISO8601ColonTZ || std == stdNumColonTZ { if len(value) < 6 { err = errBad @@ -601,65 +771,38 @@ func Parse(alayout, avalue string) (*Time, os.Error) { err = errBad break } - sign, hh, mm, value = value[0:1], value[1:3], value[4:6], value[6:] + sign, hour, min, value = value[0:1], value[1:3], value[4:6], value[6:] } else if std == stdNumShortTZ { if len(value) < 3 { err = errBad break } - sign, hh, mm, value = value[0:1], value[1:3], "00", value[3:] + sign, hour, min, value = value[0:1], value[1:3], "00", value[3:] } else { if len(value) < 5 { err = errBad break } - sign, hh, mm, value = value[0:1], value[1:3], value[3:5], value[5:] + sign, hour, min, value = value[0:1], value[1:3], value[3:5], value[5:] } - var hr, min int - hr, err = strconv.Atoi(hh) + var hr, mm int + hr, err = atoi(hour) if err == nil { - min, err = strconv.Atoi(mm) + mm, err = atoi(min) } - t.ZoneOffset = (hr*60 + min) * 60 // offset is in seconds + zoneOffset = (hr*60 + mm) * 60 // offset is in seconds switch sign[0] { case '+': case '-': - t.ZoneOffset = -t.ZoneOffset - 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 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 + zoneOffset = -zoneOffset default: err = errBad } case stdTZ: // Does it look like a time zone? if len(value) >= 3 && value[0:3] == "UTC" { - t.Zone, value = value[0:3], value[3:] + z = UTC + value = value[3:] break } @@ -680,47 +823,86 @@ func Parse(alayout, avalue string) (*Time, os.Error) { break } // It's a valid format. - t.Zone = p - // Can we find its offset? - if offset, found := lookupByName(p); found { - t.ZoneOffset = offset - } + zoneName = p default: if len(value) < len(std) { err = errBad break } if len(std) >= 2 && std[0:2] == ".0" { - rangeErrString, err = t.parseNanoseconds(value, len(std)) + nsec, rangeErrString, err = parseNanoseconds(value, len(std)) value = value[len(std):] } } if rangeErrString != "" { - return nil, &ParseError{alayout, avalue, std, value, ": " + rangeErrString + " out of range"} + return Time{}, &ParseError{alayout, avalue, std, value, ": " + rangeErrString + " out of range"} } if err != nil { - return nil, &ParseError{alayout, avalue, std, value, ""} + return Time{}, &ParseError{alayout, avalue, std, value, ""} + } + } + if pmSet && hour < 12 { + hour += 12 + } else if amSet && hour == 12 { + hour = 0 + } + + // TODO: be more aggressive checking day? + if z != nil { + return Date(year, Month(month), day, hour, min, sec, nsec, z), nil + } + + t := Date(year, Month(month), day, hour, min, sec, nsec, UTC) + if zoneOffset != -1 { + t.sec -= 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.sec + internalToUnix) + if offset == zoneOffset && (zoneName == "" || name == zoneName) { + t.loc = Local + return t, nil } + + // Otherwise create fake zone to record offset. + t.loc = FixedZone(zoneName, zoneOffset) + return t, nil } - if pmSet && t.Hour < 12 { - t.Hour += 12 - } else if amSet && t.Hour == 12 { - t.Hour = 0 + + if zoneName != "" { + // 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) + if ok { + name, off, _, _, _ := Local.lookup(t.sec + internalToUnix - int64(offset)) + if name == zoneName && off == offset { + t.sec -= int64(offset) + t.loc = Local + return t, nil + } + } + + // Otherwise, create fake zone with unknown offset. + t.loc = FixedZone(zoneName, 0) + return t, nil } - return &t, nil + + // Otherwise, fall back to UTC. + return t, nil } -func (t *Time) parseNanoseconds(value string, nbytes int) (rangErrString string, err os.Error) { +func parseNanoseconds(value string, nbytes int) (ns int, rangeErrString string, err error) { if value[0] != '.' { - return "", errBad + err = errBad + return } - var ns int - ns, err = strconv.Atoi(value[1:nbytes]) + ns, err = atoi(value[1:nbytes]) if err != nil { - return "", err + return } if ns < 0 || 1e9 <= ns { - return "fractional second", nil + rangeErrString = "fractional second" + return } // We need nanoseconds, which means scaling by the number // of missing digits in the format, maximum length 10. If it's @@ -729,6 +911,128 @@ func (t *Time) parseNanoseconds(value string, nbytes int) (rangErrString string, for i := 0; i < scaleDigits; i++ { ns *= 10 } - t.Nanosecond = ns return } + +var errLeadingInt = errors.New("time: bad [0-9]*") // never printed + +// leadingInt consumes the leading [0-9]* from s. +func leadingInt(s string) (x int, rem string, err error) { + i := 0 + for ; i < len(s); i++ { + c := s[i] + if c < '0' || c > '9' { + break + } + if x >= (1<<31-10)/10 { + // overflow + return 0, "", errLeadingInt + } + x = x*10 + int(c) - '0' + } + return x, s[i:], nil +} + +var unitMap = map[string]float64{ + "ns": float64(Nanosecond), + "us": float64(Microsecond), + "µs": float64(Microsecond), // U+00B5 = micro symbol + "μs": float64(Microsecond), // U+03BC = Greek letter mu + "ms": float64(Millisecond), + "s": float64(Second), + "m": float64(Minute), + "h": float64(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 + f := float64(0) + 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 " + orig) + } + for s != "" { + g := float64(0) // this element of the sequence + + var x int + 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 " + orig) + } + // Consume [0-9]* + pl := len(s) + x, s, err = leadingInt(s) + if err != nil { + return 0, errors.New("time: invalid duration " + orig) + } + g = float64(x) + 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) + x, s, err = leadingInt(s) + if err != nil { + return 0, errors.New("time: invalid duration " + orig) + } + scale := 1 + for n := pl - len(s); n > 0; n-- { + scale *= 10 + } + g += float64(x) / float64(scale) + post = pl != len(s) + } + if !pre && !post { + // no digits (e.g. ".s" or "-.s") + return 0, errors.New("time: invalid duration " + 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 " + orig) + } + u := s[:i] + s = s[i:] + unit, ok := unitMap[u] + if !ok { + return 0, errors.New("time: unknown unit " + u + " in duration " + orig) + } + + f += g * unit + } + + if neg { + f = -f + } + return Duration(f), nil +} diff --git a/src/pkg/time/internal_test.go b/src/pkg/time/internal_test.go new file mode 100644 index 000000000..b753896d7 --- /dev/null +++ b/src/pkg/time/internal_test.go @@ -0,0 +1,13 @@ +// 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 + localOnce.Do(initTestingZone) +} + +var Interrupt = interrupt +var DaysIn = daysIn diff --git a/src/pkg/time/sleep.go b/src/pkg/time/sleep.go index 314622d0d..27820b0ea 100644 --- a/src/pkg/time/sleep.go +++ b/src/pkg/time/sleep.go @@ -4,174 +4,92 @@ package time -import ( - "container/heap" - "sync" -) +// Sleep pauses the current goroutine for the duration d. +func Sleep(d Duration) -// The Timer type represents a single event. -// When the Timer expires, the current time will be sent on C -// unless the Timer represents an AfterFunc event. -type Timer struct { - C <-chan int64 - t int64 // The absolute time that the event should fire. - f func(int64) // The function to call when the event fires. - i int // The event's index inside eventHeap. -} - -type timerHeap []*Timer - -// forever is the absolute time (in ns) of an event that is forever away. -const forever = 1 << 62 - -// maxSleepTime is the maximum length of time that a sleeper -// sleeps for before checking if it is defunct. -const maxSleepTime = 1e9 - -var ( - // timerMutex guards the variables inside this var group. - timerMutex sync.Mutex - - // timers holds a binary heap of pending events, terminated with a sentinel. - timers timerHeap - - // currentSleeper is an ever-incrementing counter which represents - // the current sleeper. It allows older sleepers to detect that they are - // defunct and exit. - currentSleeper int64 -) - -func init() { - timers.Push(&Timer{t: forever}) // sentinel +func nano() int64 { + sec, nsec := now() + return sec*1e9 + int64(nsec) } -// NewTimer creates a new Timer that will send -// the current time on its channel after at least ns nanoseconds. -func NewTimer(ns int64) *Timer { - c := make(chan int64, 1) - e := after(ns, func(t int64) { c <- t }) - e.C = c - return e +// Interface to timers implemented in package runtime. +// Must be in sync with ../runtime/runtime.h:/^struct.Timer$ +type runtimeTimer struct { + i int32 + when int64 + period int64 + f func(int64, interface{}) + arg interface{} } -// After waits at least ns nanoseconds before sending the current time -// on the returned channel. -// It is equivalent to NewTimer(ns).C. -func After(ns int64) <-chan int64 { - return NewTimer(ns).C -} +func startTimer(*runtimeTimer) +func stopTimer(*runtimeTimer) bool -// AfterFunc waits at least ns nanoseconds before calling f -// in its own goroutine. It returns a Timer that can -// be used to cancel the call using its Stop method. -func AfterFunc(ns int64, f func()) *Timer { - return after(ns, func(_ int64) { - go f() - }) +// 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. +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 stopped. -func (e *Timer) Stop() (ok bool) { - timerMutex.Lock() - // Avoid removing the first event in the queue so that - // we don't start a new sleeper unnecessarily. - if e.i > 0 { - heap.Remove(timers, e.i) - } - ok = e.f != nil - e.f = nil - timerMutex.Unlock() - return +func (t *Timer) Stop() (ok bool) { + return stopTimer(&t.r) } -// after is the implementation of After and AfterFunc. -// When the current time is after ns, it calls f with the current time. -// It assumes that f will not block. -func after(ns int64, f func(int64)) (e *Timer) { - now := Nanoseconds() - t := now + ns - if ns > 0 && t < now { - panic("time: time overflow") - } - timerMutex.Lock() - t0 := timers[0].t - e = &Timer{nil, t, f, -1} - heap.Push(timers, e) - // Start a new sleeper if the new event is before - // the first event in the queue. If the length of time - // until the new event is at least maxSleepTime, - // then we're guaranteed that the sleeper will wake up - // in time to service it, so no new sleeper is needed. - if t0 > t && (t0 == forever || ns < maxSleepTime) { - currentSleeper++ - go sleeper(currentSleeper) +// 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: nano() + int64(d), + f: sendTime, + arg: c, + }, } - timerMutex.Unlock() - return + startTimer(&t.r) + return t } -// sleeper continually looks at the earliest event in the queue, waits until it happens, -// then removes any events in the queue that are due. It stops when the queue -// is empty or when another sleeper has been started. -func sleeper(sleeperId int64) { - timerMutex.Lock() - e := timers[0] - t := Nanoseconds() - for e.t != forever { - if dt := e.t - t; dt > 0 { - if dt > maxSleepTime { - dt = maxSleepTime - } - timerMutex.Unlock() - sysSleep(dt) - timerMutex.Lock() - if currentSleeper != sleeperId { - // Another sleeper has been started, making this one redundant. - break - } - } - e = timers[0] - t = Nanoseconds() - for t >= e.t { - if e.f != nil { - e.f(t) - e.f = nil - } - heap.Pop(timers) - e = timers[0] - } +func sendTime(now int64, c interface{}) { + // Non-blocking send of time on c. + // Used in NewTimer, it cannot block anyway (buffer). + // Used in NewTicker, dropping sends on the floor is + // the desired behavior when the reader gets behind, + // because the sends are periodic. + select { + case c.(chan Time) <- Unix(0, now): + default: } - timerMutex.Unlock() -} - -func (timerHeap) Len() int { - return len(timers) -} - -func (timerHeap) Less(i, j int) bool { - return timers[i].t < timers[j].t } -func (timerHeap) Swap(i, j int) { - timers[i], timers[j] = timers[j], timers[i] - timers[i].i = i - timers[j].i = j +// After waits for the duration to elapse and then sends the current time +// on the returned channel. +// It is equivalent to NewTimer(d).C. +func After(d Duration) <-chan Time { + return NewTimer(d).C } -func (timerHeap) Push(x interface{}) { - e := x.(*Timer) - e.i = len(timers) - timers = append(timers, e) +// 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: nano() + int64(d), + f: goFunc, + arg: f, + }, + } + startTimer(&t.r) + return t } -func (timerHeap) Pop() interface{} { - // TODO: possibly shrink array. - n := len(timers) - 1 - e := timers[n] - timers[n] = nil - timers = timers[0:n] - e.i = -1 - return e +func goFunc(now int64, arg interface{}) { + go arg.(func())() } diff --git a/src/pkg/time/sleep_test.go b/src/pkg/time/sleep_test.go index a4a1a429f..526d58d75 100644 --- a/src/pkg/time/sleep_test.go +++ b/src/pkg/time/sleep_test.go @@ -5,25 +5,26 @@ package time_test import ( + "errors" "fmt" - "os" - "syscall" - "testing" + "runtime" "sort" + "sync/atomic" + "testing" . "time" ) func TestSleep(t *testing.T) { - const delay = int64(100e6) + const delay = 100 * Millisecond go func() { Sleep(delay / 2) - syscall.Kill(os.Getpid(), syscall.SIGCHLD) + Interrupt() }() - start := Nanoseconds() + start := Now() Sleep(delay) - duration := Nanoseconds() - start + duration := Now().Sub(start) if duration < delay { - t.Fatalf("Sleep(%d) slept for only %d ns", delay, duration) + t.Fatalf("Sleep(%s) slept for only %s", delay, duration) } } @@ -38,7 +39,7 @@ func TestAfterFunc(t *testing.T) { i-- if i >= 0 { AfterFunc(0, f) - Sleep(1e9) + Sleep(1 * Second) } else { c <- true } @@ -48,6 +49,23 @@ func TestAfterFunc(t *testing.T) { <-c } +func TestAfterStress(t *testing.T) { + stop := uint32(0) + go func() { + for atomic.LoadUint32(&stop) == 0 { + runtime.GC() + // Need to yield, because otherwise + // the main goroutine will never set the stop flag. + runtime.Gosched() + } + }() + c := Tick(1) + for i := 0; i < 100; i++ { + <-c + } + atomic.StoreUint32(&stop, 1) +} + func BenchmarkAfterFunc(b *testing.B) { i := b.N c := make(chan bool) @@ -73,47 +91,49 @@ func BenchmarkAfter(b *testing.B) { func BenchmarkStop(b *testing.B) { for i := 0; i < b.N; i++ { - NewTimer(1e9).Stop() + NewTimer(1 * Second).Stop() } } func TestAfter(t *testing.T) { - const delay = int64(100e6) - start := Nanoseconds() + const delay = 100 * Millisecond + start := Now() end := <-After(delay) - if duration := Nanoseconds() - start; duration < delay { - t.Fatalf("After(%d) slept for only %d ns", delay, duration) + if duration := Now().Sub(start); duration < delay { + t.Fatalf("After(%s) slept for only %d ns", delay, duration) } - if min := start + delay; end < min { - t.Fatalf("After(%d) expect >= %d, got %d", delay, min, end) + if min := start.Add(delay); end.Before(min) { + t.Fatalf("After(%s) expect >= %s, got %s", delay, min, end) } } func TestAfterTick(t *testing.T) { - const ( - Delta = 100 * 1e6 - Count = 10 - ) - t0 := Nanoseconds() + const Count = 10 + Delta := 100 * Millisecond + if testing.Short() { + Delta = 10 * Millisecond + } + t0 := Now() for i := 0; i < Count; i++ { <-After(Delta) } - t1 := Nanoseconds() - ns := t1 - t0 - target := int64(Delta * Count) - slop := target * 2 / 10 - if ns < target-slop || ns > target+slop { - t.Fatalf("%d ticks of %g ns took %g ns, expected %g", Count, float64(Delta), float64(ns), float64(target)) + 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) { - const msec = 1e6 - AfterFunc(100*msec, func() {}) - t0 := NewTimer(50 * msec) + AfterFunc(100*Millisecond, func() {}) + t0 := NewTimer(50 * Millisecond) c1 := make(chan bool, 1) - t1 := AfterFunc(150*msec, func() { c1 <- true }) - c2 := After(200 * msec) + t1 := AfterFunc(150*Millisecond, func() { c1 <- true }) + c2 := After(200 * Millisecond) if !t0.Stop() { t.Fatalf("failed to stop event 0") } @@ -137,7 +157,7 @@ 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 = 3 - err := os.NewError("!=nil") + err := errors.New("!=nil") for i := 0; i < attempts && err != nil; i++ { if err = testAfterQueuing(t); err != nil { t.Logf("attempt %v failed: %v", i, err) @@ -152,38 +172,54 @@ var slots = []int{5, 3, 6, 6, 6, 1, 1, 2, 7, 9, 4, 8, 0} type afterResult struct { slot int - t int64 + t Time } -func await(slot int, result chan<- afterResult, ac <-chan int64) { +func await(slot int, result chan<- afterResult, ac <-chan Time) { result <- afterResult{slot, <-ac} } -func testAfterQueuing(t *testing.T) os.Error { - const ( - Delta = 100 * 1e6 - ) +func testAfterQueuing(t *testing.T) error { + Delta := 100 * Millisecond + if testing.Short() { + Delta = 20 * Millisecond + } // 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 := Nanoseconds() + t0 := Now() for _, slot := range slots { - go await(slot, result, After(int64(slot)*Delta)) + go await(slot, result, After(Duration(slot)*Delta)) } sort.Ints(slots) for _, slot := range slots { r := <-result if r.slot != slot { - return fmt.Errorf("after queue got slot %d, expected %d", r.slot, slot) + return fmt.Errorf("after slot %d, expected %d", r.slot, slot) } - ns := r.t - t0 - target := int64(slot * Delta) - slop := int64(Delta) / 4 - if ns < target-slop || ns > target+slop { - return fmt.Errorf("after queue slot %d arrived at %g, expected [%g,%g]", slot, float64(ns), float64(target-slop), float64(target+slop)) + dt := r.t.Sub(t0) + target := Duration(slot) * 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.Fatalf("timer %d was not stopped", i) + }) + Sleep(1 * Second) + timer.Stop() + }(i) + } + Sleep(3 * Second) +} diff --git a/src/pkg/time/sys.go b/src/pkg/time/sys.go deleted file mode 100644 index 9fde3b3b6..000000000 --- a/src/pkg/time/sys.go +++ /dev/null @@ -1,51 +0,0 @@ -// 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 "os" - -// Seconds reports the number of seconds since the Unix epoch, -// January 1, 1970 00:00:00 UTC. -func Seconds() int64 { - sec, _, err := os.Time() - if err != nil { - panic(err) - } - return sec -} - -// Nanoseconds reports the number of nanoseconds since the Unix epoch, -// January 1, 1970 00:00:00 UTC. -func Nanoseconds() int64 { - sec, nsec, err := os.Time() - if err != nil { - panic(err) - } - return sec*1e9 + nsec -} - -// Sleep pauses the current goroutine for at least ns nanoseconds. -// Higher resolution sleeping may be provided by syscall.Nanosleep -// on some operating systems. -func Sleep(ns int64) os.Error { - _, err := sleep(Nanoseconds(), ns) - return err -} - -// sleep takes the current time and a duration, -// pauses for at least ns nanoseconds, and -// returns the current time and an error. -func sleep(t, ns int64) (int64, os.Error) { - // TODO(cw): use monotonic-time once it's available - end := t + ns - for t < end { - err := sysSleep(end - t) - if err != nil { - return 0, err - } - t = Nanoseconds() - } - return t, nil -} diff --git a/src/pkg/time/sys_plan9.go b/src/pkg/time/sys_plan9.go index abe8649a2..848472944 100644 --- a/src/pkg/time/sys_plan9.go +++ b/src/pkg/time/sys_plan9.go @@ -2,17 +2,75 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build plan9 + package time import ( - "os" + "errors" "syscall" ) -func sysSleep(t int64) os.Error { - err := syscall.Sleep(t) +// for testing: whatever interrupts a sleep +func interrupt() { + // cannot predict pid, don't want to kill group +} + +// readFile reads and returns the content of the named file. +// It is a trivial implementation of ioutil.ReadFile, reimplemented +// here to avoid depending on io/ioutil or os. +func readFile(name string) ([]byte, error) { + f, err := syscall.Open(name, syscall.O_RDONLY) + if err != nil { + return nil, err + } + defer syscall.Close(f) + var ( + buf [4096]byte + ret []byte + n int + ) + for { + n, err = syscall.Read(f, buf[:]) + if n > 0 { + ret = append(ret, buf[:n]...) + } + if n == 0 || err != nil { + break + } + } + return ret, err +} + +func open(name string) (uintptr, error) { + fd, err := syscall.Open(name, syscall.O_RDONLY) if err != nil { - return os.NewSyscallError("sleep", err) + return 0, err + } + return uintptr(fd), nil +} + +func closefd(fd uintptr) { + syscall.Close(int(fd)) +} + +func preadn(fd uintptr, buf []byte, off int) error { + whence := 0 + if off < 0 { + whence = 2 + } + 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/pkg/time/sys_posix.go b/src/pkg/time/sys_posix.go deleted file mode 100644 index 0d1eb72fc..000000000 --- a/src/pkg/time/sys_posix.go +++ /dev/null @@ -1,18 +0,0 @@ -// 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 ( - "os" - "syscall" -) - -func sysSleep(t int64) os.Error { - errno := syscall.Sleep(t) - if errno != 0 && errno != syscall.EINTR { - return os.NewSyscallError("sleep", errno) - } - return nil -} diff --git a/src/pkg/time/sys_unix.go b/src/pkg/time/sys_unix.go new file mode 100644 index 000000000..7f69b492c --- /dev/null +++ b/src/pkg/time/sys_unix.go @@ -0,0 +1,76 @@ +// 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. + +// +build darwin freebsd linux netbsd openbsd + +package time + +import ( + "errors" + "syscall" +) + +// for testing: whatever interrupts a sleep +func interrupt() { + syscall.Kill(syscall.Getpid(), syscall.SIGCHLD) +} + +// readFile reads and returns the content of the named file. +// It is a trivial implementation of ioutil.ReadFile, reimplemented +// here to avoid depending on io/ioutil or os. +func readFile(name string) ([]byte, error) { + f, err := syscall.Open(name, syscall.O_RDONLY, 0) + if err != nil { + return nil, err + } + defer syscall.Close(f) + var ( + buf [4096]byte + ret []byte + n int + ) + for { + n, err = syscall.Read(f, buf[:]) + if n > 0 { + ret = append(ret, buf[:n]...) + } + if n == 0 || err != nil { + break + } + } + return ret, err +} + +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 closefd(fd uintptr) { + syscall.Close(int(fd)) +} + +func preadn(fd uintptr, buf []byte, off int) error { + whence := 0 + if off < 0 { + whence = 2 + } + 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/pkg/time/sys_windows.go b/src/pkg/time/sys_windows.go new file mode 100644 index 000000000..de63b4bf4 --- /dev/null +++ b/src/pkg/time/sys_windows.go @@ -0,0 +1,73 @@ +// 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() { +} + +// readFile reads and returns the content of the named file. +// It is a trivial implementation of ioutil.ReadFile, reimplemented +// here to avoid depending on io/ioutil or os. +func readFile(name string) ([]byte, error) { + f, err := syscall.Open(name, syscall.O_RDONLY, 0) + if err != nil { + return nil, err + } + defer syscall.Close(f) + var ( + buf [4096]byte + ret []byte + n int + ) + for { + n, err = syscall.Read(f, buf[:]) + if n > 0 { + ret = append(ret, buf[:n]...) + } + if n == 0 || err != nil { + break + } + } + return ret, err +} + +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 closefd(fd uintptr) { + syscall.Close(syscall.Handle(fd)) +} + +func preadn(fd uintptr, buf []byte, off int) error { + whence := 0 + if off < 0 { + whence = 2 + } + 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/pkg/time/tick.go b/src/pkg/time/tick.go index 852bae9c9..8c6b9bc3b 100644 --- a/src/pkg/time/tick.go +++ b/src/pkg/time/tick.go @@ -4,174 +4,50 @@ package time -import ( - "os" - "sync" -) +import "errors" // A Ticker holds a synchronous channel that delivers `ticks' of a clock // at intervals. type Ticker struct { - C <-chan int64 // The channel on which the ticks are delivered. - c chan<- int64 // The same channel, but the end we use. - ns int64 - shutdown chan bool // Buffered channel used to signal shutdown. - nextTick int64 - next *Ticker + 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 +// time with a period specified by the duration argument. +// It adjusts the intervals or drops ticks to make up for slow receivers. +// The duration d must be greater than zero; if not, NewTicker will panic. +func NewTicker(d Duration) *Ticker { + if d <= 0 { + panic(errors.New("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: nano() + int64(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. func (t *Ticker) Stop() { - select { - case t.shutdown <- true: - // ok - default: - // Stop in progress already - } + stopTimer(&t.r) } // Tick is a convenience wrapper for NewTicker providing access to the ticking // channel only. Useful for clients that have no need to shut down the ticker. -func Tick(ns int64) <-chan int64 { - if ns <= 0 { +func Tick(d Duration) <-chan Time { + if d <= 0 { return nil } - return NewTicker(ns).C -} - -type alarmer struct { - wakeUp chan bool // wakeup signals sent/received here - wakeMeAt chan int64 - wakeTime int64 -} - -// Set alarm to go off at time ns, if not already set earlier. -func (a *alarmer) set(ns int64) { - switch { - case a.wakeTime > ns: - // Next tick we expect is too late; shut down the late runner - // and (after fallthrough) start a new wakeLoop. - close(a.wakeMeAt) - fallthrough - case a.wakeMeAt == nil: - // There's no wakeLoop, start one. - a.wakeMeAt = make(chan int64) - a.wakeUp = make(chan bool, 1) - go wakeLoop(a.wakeMeAt, a.wakeUp) - fallthrough - case a.wakeTime == 0: - // Nobody else is waiting; it's just us. - a.wakeTime = ns - a.wakeMeAt <- ns - default: - // There's already someone scheduled. - } -} - -// Channel to notify tickerLoop of new Tickers being created. -var newTicker chan *Ticker - -func startTickerLoop() { - newTicker = make(chan *Ticker) - go tickerLoop() -} - -// wakeLoop delivers ticks at scheduled times, sleeping until the right moment. -// If another, earlier Ticker is created while it sleeps, tickerLoop() will start a new -// wakeLoop and signal that this one is done by closing the wakeMeAt channel. -func wakeLoop(wakeMeAt chan int64, wakeUp chan bool) { - for wakeAt := range wakeMeAt { - Sleep(wakeAt - Nanoseconds()) - wakeUp <- true - } -} - -// A single tickerLoop serves all ticks to Tickers. It waits for two events: -// either the creation of a new Ticker or a tick from the alarm, -// signaling a time to wake up one or more Tickers. -func tickerLoop() { - // Represents the next alarm to be delivered. - var alarm alarmer - var now, wakeTime int64 - var tickers *Ticker - for { - select { - case t := <-newTicker: - // Add Ticker to list - t.next = tickers - tickers = t - // Arrange for a new alarm if this one precedes the existing one. - alarm.set(t.nextTick) - case <-alarm.wakeUp: - now = Nanoseconds() - wakeTime = now + 1e15 // very long in the future - var prev *Ticker = nil - // Scan list of tickers, delivering updates to those - // that need it and determining the next wake time. - // TODO(r): list should be sorted in time order. - for t := tickers; t != nil; t = t.next { - select { - case <-t.shutdown: - // Ticker is done; remove it from list. - if prev == nil { - tickers = t.next - } else { - prev.next = t.next - } - continue - default: - } - if t.nextTick <= now { - if len(t.c) == 0 { - // Only send if there's room. We must not block. - // The channel is allocated with a one-element - // buffer, which is sufficient: if he hasn't picked - // up the last tick, no point in sending more. - t.c <- now - } - t.nextTick += t.ns - if t.nextTick <= now { - // Still behind; advance in one big step. - t.nextTick += (now - t.nextTick + t.ns) / t.ns * t.ns - } - } - if t.nextTick < wakeTime { - wakeTime = t.nextTick - } - prev = t - } - if tickers != nil { - // Please send wakeup at earliest required time. - // If there are no tickers, don't bother. - alarm.wakeTime = wakeTime - alarm.wakeMeAt <- wakeTime - } else { - alarm.wakeTime = 0 - } - } - } -} - -var onceStartTickerLoop sync.Once - -// NewTicker returns a new Ticker containing a channel that will -// send the time, in nanoseconds, every ns nanoseconds. It adjusts the -// intervals to make up for pauses in delivery of the ticks. The value of -// ns must be greater than zero; if not, NewTicker will panic. -func NewTicker(ns int64) *Ticker { - if ns <= 0 { - panic(os.NewError("non-positive interval for NewTicker")) - } - c := make(chan int64, 1) // See comment on send in tickerLoop - t := &Ticker{ - C: c, - c: c, - ns: ns, - shutdown: make(chan bool, 1), - nextTick: Nanoseconds() + ns, - } - onceStartTickerLoop.Do(startTickerLoop) - // must be run in background so global Tickers can be created - go func() { newTicker <- t }() - return t + return NewTicker(d).C } diff --git a/src/pkg/time/tick_test.go b/src/pkg/time/tick_test.go index 4dcb63956..d8a086ceb 100644 --- a/src/pkg/time/tick_test.go +++ b/src/pkg/time/tick_test.go @@ -10,22 +10,20 @@ import ( ) func TestTicker(t *testing.T) { - const ( - Delta = 100 * 1e6 - Count = 10 - ) + const Count = 10 + Delta := 100 * Millisecond ticker := NewTicker(Delta) - t0 := Nanoseconds() + t0 := Now() for i := 0; i < Count; i++ { <-ticker.C } ticker.Stop() - t1 := Nanoseconds() - ns := t1 - t0 - target := int64(Delta * Count) + t1 := Now() + dt := t1.Sub(t0) + target := Delta * Count slop := target * 2 / 10 - if ns < target-slop || ns > target+slop { - t.Fatalf("%d ticks of %g ns took %g ns, expected %g", Count, float64(Delta), float64(ns), float64(target)) + if dt < target-slop || (!testing.Short() && dt > target+slop) { + t.Fatalf("%d %s ticks took %s, expected [%s,%s]", Count, Delta, dt, target-slop, target+slop) } // Now test that the ticker stopped Sleep(2 * Delta) @@ -39,8 +37,12 @@ func TestTicker(t *testing.T) { // 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(1e8) + ticker := NewTicker(Delta) <-ticker.C ticker.Stop() } diff --git a/src/pkg/time/time.go b/src/pkg/time/time.go index 0e05da484..473bc2a45 100644 --- a/src/pkg/time/time.go +++ b/src/pkg/time/time.go @@ -3,11 +3,112 @@ // 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. package time -// Days of the week. +import "errors" + +// 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. +// +// 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. +// +type Time struct { + // sec gives the number of seconds elapsed since + // January 1, year 1 00:00:00 UTC. + sec int64 + + // nsec specifies a non-negative nanosecond + // offset within the second named by Seconds. + // It must be in the range [0, 999999999]. + nsec int32 + + // loc specifies the Location that should be used to + // determine the minute, hour, month, day, and year + // that correspond to this Time. + // Only the zero Time has a nil Location. + // In that case it is interpreted to mean UTC. + loc *Location +} + +// After reports whether the time instant t is after u. +func (t Time) After(u Time) bool { + return t.sec > u.sec || t.sec == u.sec && t.nsec > u.nsec +} + +// Before reports whether the time instant t is before u. +func (t Time) Before(u Time) bool { + return t.sec < u.sec || t.sec == u.sec && t.nsec < u.nsec +} + +// 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 CEST and 4:00 UTC are Equal. +// This comparison is different from using t == u, which also compares +// the locations. +func (t Time) Equal(u Time) bool { + return t.sec == u.sec && t.nsec == u.nsec +} + +// A Month specifies a month of the year (January = 1, ...). +type Month int + const ( - Sunday = iota + January Month = 1 + iota + February + March + April + May + June + July + August + September + October + November + December +) + +var months = [...]string{ + "January", + "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 { return months[m-1] } + +// A Weekday specifies a day of the week (Sunday = 0, ...). +type Weekday int + +const ( + Sunday Weekday = iota Monday Tuesday Wednesday @@ -16,215 +117,871 @@ const ( Saturday ) -// Time is the struct representing a parsed time value. -type Time struct { - Year int64 // 2006 is 2006 - Month, Day int // Jan-2 is 1, 2 - Hour, Minute, Second int // 15:04:05 is 15, 4, 5. - Nanosecond int // Fractional second. - Weekday int // Sunday, Monday, ... - ZoneOffset int // seconds east of UTC, e.g. -7*60*60 for -0700 - Zone string // e.g., "MST" +var days = [...]string{ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", } -var nonleapyear = []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} -var leapyear = []int{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} +// String returns the English name of the day ("Sunday", "Monday", ...). +func (d Weekday) String() string { return days[d] } + +// 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-2469 will apply as well to 2470-2869. 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. -func months(year int64) []int { - if year%4 == 0 && (year%100 != 0 || year%400 == 0) { - return leapyear +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 + + // The year of the zero Unix time. + unixYear = 1970 + + // 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 +) + +// 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 + if l == nil { + l = &utcLoc } - return nonleapyear + // Avoid function call if we hit the local time cache. + sec := t.sec + internalToUnix + 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)) +} + +// 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 { + // January 1 of the absolute year, like January 1 of 2001, was a Monday. + sec := (t.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) { + year, month, day, yday := t.date(true) + wday := int(t.Weekday()+6) % 7 // weekday but Monday = 0. + const ( + Mon int = iota + Tue + Wed + Thu + Fri + Sat + Sun + ) + + // Calculate week as number of Mondays in year up to + // and including today, plus 1 because the first week is week 0. + // Putting the + 1 inside the numerator as a + 7 keeps the + // numerator from being negative, which would cause it to + // round incorrectly. + week = (yday - wday + 7) / 7 + + // The week number is now correct under the assumption + // that the first Monday of the year is in week 1. + // If Jan 1 is a Tuesday, Wednesday, or Thursday, the first Monday + // is actually in week 2. + jan1wday := (wday - yday + 7*53) % 7 + if Tue <= jan1wday && jan1wday <= Thu { + week++ + } + + // If the week number is still 0, we're in early January but in + // the last week of last year. + if week == 0 { + year-- + week = 52 + // A year has 53 weeks when Jan 1 or Dec 31 is a Thursday, + // meaning Jan 1 of the next year is a Friday + // or it was a leap year and Jan 1 of the next year is a Saturday. + if jan1wday == Fri || (jan1wday == Sat && isLeap(year)) { + week++ + } + } + + // December 29 to 31 are in week 1 of next year if + // they are after the last Thursday of the year and + // December 31 is a Monday, Tuesday, or Wednesday. + if month == December && day >= 29 && wday < Thu { + if dec31wday := (wday + 31 - day) % 7; Mon <= dec31wday && dec31wday <= Wed { + year++ + week = 1 + } + } + + return +} + +// Clock returns the hour, minute, and second within the day specified by t. +func (t Time) Clock() (hour, min, sec int) { + sec = int(t.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) +} + +// 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 + +// 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 ( - secondsPerDay = 24 * 60 * 60 - daysPer400Years = 365*400 + 97 - daysPer100Years = 365*100 + 24 - daysPer4Years = 365*4 + 1 - days1970To2001 = 31*365 + 8 + Nanosecond Duration = 1 + Microsecond = 1000 * Nanosecond + Millisecond = 1000 * Microsecond + Second = 1000 * Millisecond + Minute = 60 * Second + Hour = 60 * Minute ) -// SecondsToUTC converts sec, in number of seconds since the Unix epoch, -// into a parsed Time value in the UTC time zone. -func SecondsToUTC(sec int64) *Time { - t := new(Time) +// Duration 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 0, +// with no unit. +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 + } - // Split into time and day. - day := sec / secondsPerDay - sec -= day * secondsPerDay - if sec < 0 { - day-- - sec += secondsPerDay + if u < uint64(Second) { + // Special case: if duration is smaller than a second, + // use smaller units, like 1.2ms + var ( + prec int + unit byte + ) + switch { + case u == 0: + return "0" + case u < uint64(Microsecond): + // print nanoseconds + prec = 0 + unit = 'n' + case u < uint64(Millisecond): + // print microseconds + prec = 3 + unit = 'u' + default: + // print milliseconds + prec = 6 + unit = 'm' + } + w -= 2 + buf[w] = unit + buf[w+1] = 's' + 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] = '-' } - // Time - t.Hour = int(sec / 3600) - t.Minute = int((sec / 60) % 60) - t.Second = int(sec % 60) + return string(buf[w:]) +} - // Day 0 = January 1, 1970 was a Thursday - t.Weekday = int((day + Thursday) % 7) - if t.Weekday < 0 { - t.Weekday += 7 +// 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) } + +// 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)*1e-9 +} - // Change day from 0 = 1970 to 0 = 2001, - // to make leap year calculations easier - // (2001 begins 4-, 100-, and 400-year cycles ending in a leap year.) - day -= days1970To2001 +// 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)*(1e-9/60) +} - year := int64(2001) - if day < 0 { - // Go back enough 400 year cycles to make day positive. - n := -day/daysPer400Years + 1 - year -= 400 * n - day += daysPer400Years * n +// 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)*(1e-9/60/60) +} + +// Add returns the time t+d. +func (t Time) Add(d Duration) Time { + t.sec += int64(d / 1e9) + t.nsec += int32(d % 1e9) + if t.nsec >= 1e9 { + t.sec++ + t.nsec -= 1e9 + } else if t.nsec < 0 { + t.sec-- + t.nsec += 1e9 } + return t +} - // Cut off 400 year cycles. - n := day / daysPer400Years - year += 400 * n - day -= daysPer400Years * n +// Sub returns the duration t-u. +// To compute t-d for a duration d, use t.Add(-d). +func (t Time) Sub(u Time) Duration { + return Duration(t.sec-u.sec)*Second + Duration(t.nsec-u.nsec) +} + +// Since returns the time elapsed since t. +// It is shorthand for time.Now().Sub(t). +func Since(t Time) Duration { + return Now().Sub(t) +} + +// 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.loc) +} - // Cut off 100-year cycles - n = day / daysPer100Years - if n > 3 { // happens on last day of 400th year - n = 3 +const ( + secondsPerMinute = 60 + secondsPerHour = 60 * 60 + secondsPerDay = 24 * secondsPerHour + secondsPerWeek = 7 * secondsPerDay + daysPer400Years = 365*400 + 97 + daysPer100Years = 365*100 + 24 + daysPer4Years = 365*4 + 1 + days1970To2001 = 31*365 + 8 +) + +// date computes the year and, only 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) { + // Split into time and day. + d := t.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 } - year += 100 * n - day -= daysPer100Years * n - // Cut off 4-year cycles - n = day / daysPer4Years - if n > 24 { // happens on last day of 100th year - n = 24 + 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 + } } - year += 4 * n - day -= daysPer4Years * n - // Cut off non-leap years. - n = day / 365 - if n > 3 { // happens on last day of 4th year - n = 3 + // 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]) } - year += n - day -= 365 * n - t.Year = year + month++ // because January is 1 + day = day - begin + 1 + return +} - // If someone ever needs yearday, - // tyearday = day (+1?) +// 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, +} - months := months(year) - var m int - yday := int(day) - for m = 0; m < 12 && yday >= months[m]; m++ { - yday -= months[m] +func daysIn(m Month, year int) int { + if m == February && isLeap(year) { + return 29 } - t.Month = m + 1 - t.Day = yday + 1 - t.Zone = "UTC" + return int(daysBefore[m] - daysBefore[m-1]) +} - return t +// Provided by package runtime. +func now() (sec int64, nsec int32) + +// Now returns the current local time. +func Now() Time { + sec, nsec := now() + return Time{sec + unixToInternal, nsec, Local} } -// NanosecondsToUTC converts nsec, in number of nanoseconds since the Unix epoch, -// into a parsed Time value in the UTC time zone. -func NanosecondsToUTC(nsec int64) *Time { - // This one calls SecondsToUTC rather than the other way around because - // that admits a much larger span of time; NanosecondsToUTC is limited - // to a few hundred years only. - t := SecondsToUTC(nsec / 1e9) - t.Nanosecond = int(nsec % 1e9) +// UTC returns t with the location set to UTC. +func (t Time) UTC() Time { + t.loc = UTC return t } -// UTC returns the current time as a parsed Time value in the UTC time zone. -func UTC() *Time { return NanosecondsToUTC(Nanoseconds()) } - -// SecondsToLocalTime converts sec, in number of seconds since the Unix epoch, -// into a parsed Time value in the local time zone. -func SecondsToLocalTime(sec int64) *Time { - z, offset := lookupTimezone(sec) - t := SecondsToUTC(sec + int64(offset)) - t.Zone = z - t.ZoneOffset = offset +// Local returns t with the location set to local time. +func (t Time) Local() Time { + t.loc = Local return t } -// NanosecondsToLocalTime converts nsec, in number of nanoseconds since the Unix epoch, -// into a parsed Time value in the local time zone. -func NanosecondsToLocalTime(nsec int64) *Time { - t := SecondsToLocalTime(nsec / 1e9) - t.Nanosecond = int(nsec % 1e9) +// In returns t with the location information set to loc. +// +// 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.loc = loc return t } -// LocalTime returns the current time as a parsed Time value in the local time zone. -func LocalTime() *Time { return NanosecondsToLocalTime(Nanoseconds()) } +// 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.sec + internalToUnix) + return +} + +// Unix returns t as a Unix time, the number of seconds elapsed +// since January 1, 1970 UTC. +func (t Time) Unix() int64 { + return t.sec + internalToUnix +} + +// UnixNano returns t as a Unix time, the number of nanoseconds elapsed +// since January 1, 1970 UTC. +func (t Time) UnixNano() int64 { + return (t.sec+internalToUnix)*1e9 + int64(t.nsec) +} + +const timeGobVersion byte = 1 + +// GobEncode implements the gob.GobEncoder interface. +func (t Time) GobEncode() ([]byte, error) { + var offsetMin int16 // minutes east of UTC. -1 is UTC. + + if t.Location() == &utcLoc { + offsetMin = -1 + } else { + _, offset := t.Zone() + if offset%60 != 0 { + return nil, errors.New("Time.GobEncode: zone offset has fractional minute") + } + offset /= 60 + if offset < -32768 || offset == -1 || offset > 32767 { + return nil, errors.New("Time.GobEncode: unexpected zone offset") + } + offsetMin = int16(offset) + } + + enc := []byte{ + timeGobVersion, // byte 0 : version + byte(t.sec >> 56), // bytes 1-8: seconds + byte(t.sec >> 48), + byte(t.sec >> 40), + byte(t.sec >> 32), + byte(t.sec >> 24), + byte(t.sec >> 16), + byte(t.sec >> 8), + byte(t.sec), + byte(t.nsec >> 24), // bytes 9-12: nanoseconds + byte(t.nsec >> 16), + byte(t.nsec >> 8), + byte(t.nsec), + byte(offsetMin >> 8), // bytes 13-14: zone offset in minutes + byte(offsetMin), + } + + return enc, nil +} + +// GobDecode implements the gob.GobDecoder interface. +func (t *Time) GobDecode(buf []byte) error { + if len(buf) == 0 { + return errors.New("Time.GobDecode: no data") + } + + if buf[0] != timeGobVersion { + return errors.New("Time.GobDecode: unsupported version") + } + + if len(buf) != /*version*/ 1+ /*sec*/ 8+ /*nsec*/ 4+ /*zone offset*/ 2 { + return errors.New("Time.GobDecode: invalid length") + } + + buf = buf[1:] + t.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:] + t.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 offset == -1*60 { + t.loc = &utcLoc + } else if _, localoff, _, _, _ := Local.lookup(t.sec + internalToUnix); offset == localoff { + t.loc = Local + } else { + t.loc = FixedZone("", offset) + } + + return nil +} + +// MarshalJSON implements the json.Marshaler interface. +// Time is formatted as RFC3339. +func (t Time) MarshalJSON() ([]byte, error) { + if y := t.Year(); y < 0 || y >= 10000 { + return nil, errors.New("Time.MarshalJSON: year outside of range [0,9999]") + } + return []byte(t.Format(`"` + RFC3339Nano + `"`)), nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. +// Time is expected in RFC3339 format. +func (t *Time) UnmarshalJSON(data []byte) (err error) { + // Fractional seconds are handled implicitly by Parse. + *t, err = Parse(`"`+RFC3339+`"`, string(data)) + return +} -// Seconds returns the number of seconds since January 1, 1970 represented by the -// parsed Time value. -func (t *Time) Seconds() int64 { - // First, accumulate days since January 1, 2001. - // Using 2001 instead of 1970 makes the leap-year - // handling easier (see SecondsToUTC), because - // it is at the beginning of the 4-, 100-, and 400-year cycles. - day := int64(0) +// 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]. +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 Time{sec + unixToInternal, int32(nsec), Local} +} + +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 +} - // Rewrite year to be >= 2001. - year := t.Year - if year < 2001 { - n := (2001-year)/400 + 1 - year += 400 * n - day -= daysPer400Years * n +// 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) + + y := uint64(int64(year) - absoluteZeroYear) + + // Compute days since the absolute epoch. + // Add in days from 400-year cycles. - n := (year - 2001) / 400 - year -= 400 * n - day += daysPer400Years * n + n := y / 400 + y -= 400 * n + d := daysPer400Years * n // Add in 100-year cycles. - n = (year - 2001) / 100 - year -= 100 * n - day += daysPer100Years * n + n = y / 100 + y -= 100 * n + d += daysPer100Years * n // Add in 4-year cycles. - n = (year - 2001) / 4 - year -= 4 * n - day += daysPer4Years * n + n = y / 4 + y -= 4 * n + d += daysPer4Years * n // Add in non-leap years. - n = year - 2001 - day += 365 * n + n = y + d += 365 * n - // Add in days this year. - months := months(t.Year) - for m := 0; m < t.Month-1; m++ { - day += int64(months[m]) + // Add in days before this month. + d += uint64(daysBefore[month-1]) + if isLeap(year) && month >= March { + d++ // February 29 } - day += int64(t.Day - 1) - // Convert days to seconds since January 1, 2001. - sec := day * secondsPerDay + // Add in days before today. + d += uint64(day - 1) // Add in time elapsed today. - sec += int64(t.Hour) * 3600 - sec += int64(t.Minute) * 60 - sec += int64(t.Second) - - // Convert from seconds since 2001 to seconds since 1970. - sec += days1970To2001 * secondsPerDay - - // Account for local time zone. - sec -= int64(t.ZoneOffset) - return sec -} + abs := d * secondsPerDay + abs += uint64(hour*secondsPerHour + min*secondsPerMinute + sec) + + unix := int64(abs) + (absoluteToInternal + internalToUnix) + + // Look for zone offset for t, so we can adjust to UTC. + // The lookup function expects UTC, so we pass t 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 { + switch utc := unix - int64(offset); { + case utc < start: + _, offset, _, _, _ = loc.lookup(start - 1) + case utc >= end: + _, offset, _, _, _ = loc.lookup(end) + } + unix -= int64(offset) + } -// Nanoseconds returns the number of nanoseconds since January 1, 1970 represented by the -// parsed Time value. -func (t *Time) Nanoseconds() int64 { - return t.Seconds()*1e9 + int64(t.Nanosecond) + return Time{unix + unixToInternal, int32(nsec), loc} } diff --git a/src/pkg/time/time_test.go b/src/pkg/time/time_test.go index dceed491a..3430526b8 100644 --- a/src/pkg/time/time_test.go +++ b/src/pkg/time/time_test.go @@ -5,7 +5,11 @@ package time_test import ( - "os" + "bytes" + "encoding/gob" + "encoding/json" + "fmt" + "math/rand" "strconv" "strings" "testing" @@ -13,84 +17,93 @@ import ( . "time" ) -func init() { - // Force US Pacific time for daylight-savings - // tests below (localtests). Needs to be set - // before the first call into the time library. - os.Setenv("TZ", "America/Los_Angeles") -} - // 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 := LocalTime() + lt := Now() // PST is 8 hours west, PDT is 7 hours west. We could use the name but it's not unique. - if off := lt.ZoneOffset; 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", lt.Zone, off) + 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 Time + golden parsedTime } var utctests = []TimeTest{ - {0, Time{1970, 1, 1, 0, 0, 0, 0, Thursday, 0, "UTC"}}, - {1221681866, Time{2008, 9, 17, 20, 4, 26, 0, Wednesday, 0, "UTC"}}, - {-1221681866, Time{1931, 4, 16, 3, 55, 34, 0, Thursday, 0, "UTC"}}, - {-11644473600, Time{1601, 1, 1, 0, 0, 0, 0, Monday, 0, "UTC"}}, - {599529660, Time{1988, 12, 31, 0, 1, 0, 0, Saturday, 0, "UTC"}}, - {978220860, Time{2000, 12, 31, 0, 1, 0, 0, Sunday, 0, "UTC"}}, - {1e18, Time{31688740476, 10, 23, 1, 46, 40, 0, Friday, 0, "UTC"}}, - {-1e18, Time{-31688736537, 3, 10, 22, 13, 20, 0, Tuesday, 0, "UTC"}}, - {0x7fffffffffffffff, Time{292277026596, 12, 4, 15, 30, 7, 0, Sunday, 0, "UTC"}}, - {-0x8000000000000000, Time{-292277022657, 1, 27, 8, 29, 52, 0, Sunday, 0, "UTC"}}, + {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, Time{1970, 1, 1, 0, 0, 0, 1e8, Thursday, 0, "UTC"}}, - {1221681866, Time{2008, 9, 17, 20, 4, 26, 2e8, Wednesday, 0, "UTC"}}, + {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, Time{1969, 12, 31, 16, 0, 0, 0, Wednesday, -8 * 60 * 60, "PST"}}, - {1221681866, Time{2008, 9, 17, 13, 4, 26, 0, Wednesday, -7 * 60 * 60, "PDT"}}, + {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"}}, } var nanolocaltests = []TimeTest{ - {0, Time{1969, 12, 31, 16, 0, 0, 1e8, Wednesday, -8 * 60 * 60, "PST"}}, - {1221681866, Time{2008, 9, 17, 13, 4, 26, 3e8, Wednesday, -7 * 60 * 60, "PDT"}}, -} - -func same(t, u *Time) bool { - 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 && - t.ZoneOffset == u.ZoneOffset && - t.Zone == u.Zone + {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 := SecondsToUTC(sec) - newsec := tm.Seconds() + 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):", sec) + t.Errorf("SecondsToUTC(%d): // %#v", sec, tm) t.Errorf(" want=%+v", *golden) - t.Errorf(" have=%+v", *tm) + t.Errorf(" have=%v", tm.Format(RFC3339+" MST")) } } } @@ -99,15 +112,15 @@ func TestNanosecondsToUTC(t *testing.T) { for _, test := range nanoutctests { golden := &test.golden nsec := test.seconds*1e9 + int64(golden.Nanosecond) - tm := NanosecondsToUTC(nsec) - newnsec := tm.Nanoseconds() + 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) + t.Errorf(" have=%+v", tm.Format(RFC3339+" MST")) } } } @@ -116,38 +129,38 @@ func TestSecondsToLocalTime(t *testing.T) { for _, test := range localtests { sec := test.seconds golden := &test.golden - tm := SecondsToLocalTime(sec) - newsec := tm.Seconds() + 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) + t.Errorf(" have=%+v", tm.Format(RFC3339+" MST")) } } } -func TestNanoecondsToLocalTime(t *testing.T) { +func TestNanosecondsToLocalTime(t *testing.T) { for _, test := range nanolocaltests { golden := &test.golden nsec := test.seconds*1e9 + int64(golden.Nanosecond) - tm := NanosecondsToLocalTime(nsec) - newnsec := tm.Nanoseconds() + 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) + t.Errorf(" have=%+v", tm.Format(RFC3339+" MST")) } } } func TestSecondsToUTCAndBack(t *testing.T) { - f := func(sec int64) bool { return SecondsToUTC(sec).Seconds() == sec } + 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} @@ -161,7 +174,11 @@ func TestSecondsToUTCAndBack(t *testing.T) { } func TestNanosecondsToUTCAndBack(t *testing.T) { - f := func(nsec int64) bool { return NanosecondsToUTC(nsec).Nanoseconds() == nsec } + 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} @@ -181,9 +198,9 @@ type TimeFormatTest struct { } var rfc3339Formats = []TimeFormatTest{ - {Time{2008, 9, 17, 20, 4, 26, 0, Wednesday, 0, "UTC"}, "2008-09-17T20:04:26Z"}, - {Time{1994, 9, 17, 20, 4, 26, 0, Wednesday, -18000, "EST"}, "1994-09-17T20:04:26-05:00"}, - {Time{2000, 12, 26, 1, 15, 6, 0, Wednesday, 15600, "OTO"}, "2000-12-26T01:15:06+04:20"}, + {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) { @@ -209,7 +226,9 @@ var formatTests = []FormatTest{ {"RFC822", RFC822, "04 Feb 09 2100 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"}, @@ -218,12 +237,12 @@ var formatTests = []FormatTest{ {"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.012345678"}, + {"StampNano", StampNano, "Feb 4 21:00:57.012345600"}, } func TestFormat(t *testing.T) { - // The numeric time represents Thu Feb 4 21:00:57.012345678 PST 2010 - time := NanosecondsToLocalTime(1233810057012345678) + // The numeric time represents Thu Feb 4 21:00:57.012345600 PST 2010 + time := Unix(0, 1233810057012345600) for _, test := range formatTests { result := time.Format(test.format) if result != test.result { @@ -232,14 +251,46 @@ func TestFormat(t *testing.T) { } } +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 int64 // sign of year - fracDigits int // number of digits of fractional second + hasTZ bool // contains a time zone + hasWD bool // contains a weekday + yearSign int // sign of year + fracDigits int // number of digits of fractional second } var parseTests = []ParseTest{ @@ -248,6 +299,7 @@ var parseTests = []ParseTest{ {"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}, + {"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. @@ -256,10 +308,14 @@ var parseTests = []ParseTest{ {"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}, // 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", "Mon Jan _2 15:04:05.000 2006", "Thu Feb 4 21:00:57.012 2010", false, true, 1, 3}, {"microsecond", "Mon Jan _2 15:04:05.000000 2006", "Thu Feb 4 21:00:57.012345 2010", false, true, 1, 6}, @@ -300,47 +356,48 @@ func TestRubyParse(t *testing.T) { } } -func checkTime(time *Time, test *ParseTest, t *testing.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*time.Year != 2010 { - t.Errorf("%s: bad year: %d not %d", test.name, time.Year, 2010) + if test.yearSign*time.Year() != 2010 { + t.Errorf("%s: bad year: %d not %d", test.name, time.Year(), 2010) } - if time.Month != 2 { - t.Errorf("%s: bad month: %d not %d", test.name, time.Month, 2) + 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.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.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.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) + 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.Atoui("012345678"[:test.fracDigits] + "000000000"[:9-test.fracDigits]) + 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) + if time.Nanosecond() != int(nanosec) { + t.Errorf("%s: bad nanosecond: %d not %d", test.name, time.Nanosecond(), nanosec) } - if test.hasTZ && time.ZoneOffset != -28800 { - t.Errorf("%s: bad tz offset: %d not %d", test.name, time.ZoneOffset, -28800) + 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 != 4 { - t.Errorf("%s: bad weekday: %d not %d", test.name, time.Weekday, 4) + 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 := SecondsToLocalTime(sec) - if t1.Year < 1000 || t1.Year > 9999 { + t1 := Unix(sec, 0) + if t1.Year() < 1000 || t1.Year() > 9999 { // not required to work return true } @@ -349,8 +406,8 @@ func TestFormatAndParse(t *testing.T) { t.Errorf("error: %s", err) return false } - if !same(t1, t2) { - t.Errorf("different: %q %q", t1, t2) + 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 @@ -389,14 +446,14 @@ func TestParseErrors(t *testing.T) { _, err := Parse(test.format, test.value) if err == nil { t.Errorf("expected error for %q %q", test.format, test.value) - } else if strings.Index(err.String(), test.expect) < 0 { + } else if strings.Index(err.Error(), test.expect) < 0 { t.Errorf("expected error with %q for %q %q; got %s", test.expect, test.format, test.value, err) } } } func TestNoonIs12PM(t *testing.T) { - noon := Time{Hour: 12} + noon := Date(0, January, 1, 12, 0, 0, 0, UTC) const expect = "12:00PM" got := noon.Format("3:04PM") if got != expect { @@ -409,7 +466,7 @@ func TestNoonIs12PM(t *testing.T) { } func TestMidnightIs12AM(t *testing.T) { - midnight := Time{Hour: 0} + midnight := Date(0, January, 1, 0, 0, 0, 0, UTC) expect := "12:00AM" got := midnight.Format("3:04PM") if got != expect { @@ -426,15 +483,15 @@ func Test12PMIsNoon(t *testing.T) { if err != nil { t.Fatal("error parsing date:", err) } - if noon.Hour != 12 { - t.Errorf("got %d; expect 12", noon.Hour) + 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) + if noon.Hour() != 12 { + t.Errorf("got %d; expect 12", noon.Hour()) } } @@ -443,29 +500,29 @@ func Test12AMIsMidnight(t *testing.T) { if err != nil { t.Fatal("error parsing date:", err) } - if midnight.Hour != 0 { - t.Errorf("got %d; expect 0", midnight.Hour) + 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) + 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, "Tue Feb 02 16:10:03 -0500 2006") + time, err := Parse(RubyDate, "Thu Feb 02 16:10:03 -0500 2006") if err != nil { t.Fatal("error parsing date:", err) } - expect := "Tue Feb 2 16:10:03 -0500 2006" // -0500 not EST + 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("expected %q got %q", expect, str) + t.Errorf("got %s; expect %s", str, expect) } } @@ -475,25 +532,409 @@ func TestMinutesInTimeZone(t *testing.T) { t.Fatal("error parsing date:", err) } expected := (1*60 + 23) * 60 - if time.ZoneOffset != expected { - t.Errorf("ZoneOffset incorrect, expected %d got %d", expected, time.ZoneOffset) + _, offset := time.Zone() + if offset != expected { + t.Errorf("ZoneOffset = %d, want %d", offset, expected) + } +} + +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) + } } } -func BenchmarkSeconds(b *testing.B) { - for i := 0; i < b.N; i++ { - Seconds() +var durationTests = []struct { + str string + d Duration +}{ + {"0", 0}, + {"1ns", 1 * Nanosecond}, + {"1.1us", 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 + + // 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) + } } } -func BenchmarkNanoseconds(b *testing.B) { +// 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 + Time{}, // 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.GobDecode: no data"}, + {[]byte{0, 2, 3}, "Time.GobDecode: unsupported version"}, + {[]byte{1, 2, 3}, "Time.GobDecode: 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) + } + } +} + +var notEncodableTimes = []struct { + time Time + want string +}{ + {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", 1)), "Time.GobEncode: zone offset has fractional minute"}, + {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", -1*60)), "Time.GobEncode: unexpected zone offset"}, + {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", -32769*60)), "Time.GobEncode: unexpected zone offset"}, + {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", 32768*60)), "Time.GobEncode: 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) + } + } +} + +var jsonTests = []struct { + time Time + json string +}{ + {Date(9999, 4, 12, 23, 20, 50, .52*1e9, 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"`}, +} + +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 TestInvalidTimeJSON(t *testing.T) { + var tt Time + err := json.Unmarshal([]byte(`{"now is the time":"buddy"}`), &tt) + _, isParseErr := err.(*ParseError) + if !isParseErr { + t.Errorf("expected *time.ParseError unmarshaling JSON, got %v", err) + } +} + +var notJSONEncodableTimes = []struct { + time Time + want string +}{ + {Date(10000, 1, 1, 0, 0, 0, 0, UTC), "Time.MarshalJSON: year outside of range [0,9999]"}, + {Date(-1, 1, 1, 0, 0, 0, 0, UTC), "Time.MarshalJSON: year outside of range [0,9999]"}, +} + +func TestNotJSONEncodableTime(t *testing.T) { + for _, tt := range notJSONEncodableTimes { + _, err := tt.time.MarshalJSON() + if err == nil || err.Error() != tt.want { + t.Errorf("%v MarshalJSON error = %v, want %v", tt.time, err, tt.want) + } + } +} + +var parseDurationTests = []struct { + in string + ok bool + want Duration +}{ + // simple + {"0", true, 0}, + {"5s", true, 5 * Second}, + {"30s", true, 30 * Second}, + {"1478s", true, 1478 * Second}, + // sign + {"-5s", true, -5 * Second}, + {"+5s", true, 5 * Second}, + {"-0", true, 0}, + {"+0", true, 0}, + // decimal + {"5.0s", true, 5 * Second}, + {"5.6s", true, 5*Second + 600*Millisecond}, + {"5.s", true, 5 * Second}, + {".5s", true, 500 * Millisecond}, + {"1.0s", true, 1 * Second}, + {"1.00s", true, 1 * Second}, + {"1.004s", true, 1*Second + 4*Millisecond}, + {"1.0040s", true, 1*Second + 4*Millisecond}, + {"100.00100s", true, 100*Second + 1*Millisecond}, + // different units + {"10ns", true, 10 * Nanosecond}, + {"11us", true, 11 * Microsecond}, + {"12µs", true, 12 * Microsecond}, // U+00B5 + {"12μs", true, 12 * Microsecond}, // U+03BC + {"13ms", true, 13 * Millisecond}, + {"14s", true, 14 * Second}, + {"15m", true, 15 * Minute}, + {"16h", true, 16 * Hour}, + // composite durations + {"3h30m", true, 3*Hour + 30*Minute}, + {"10.5s4m", true, 4*Minute + 10*Second + 500*Millisecond}, + {"-2m3.4s", true, -(2*Minute + 3*Second + 400*Millisecond)}, + {"1h2m3s4ms5us6ns", true, 1*Hour + 2*Minute + 3*Second + 4*Millisecond + 5*Microsecond + 6*Nanosecond}, + {"39h9m14.425s", true, 39*Hour + 9*Minute + 14*Second + 425*Millisecond}, + + // errors + {"", false, 0}, + {"3", false, 0}, + {"-", false, 0}, + {"s", false, 0}, + {".", false, 0}, + {"-.", false, 0}, + {".s", false, 0}, + {"+.s", false, 0}, +} + +func TestParseDuration(t *testing.T) { + for _, tc := range parseDurationTests { + d, err := ParseDuration(tc.in) + if tc.ok && (err != nil || d != tc.want) { + t.Errorf("ParseDuration(%q) = %v, %v, want %v, nil", tc.in, d, err, tc.want) + } else if !tc.ok && err == nil { + t.Errorf("ParseDuration(%q) = _, nil, want _, non-nil", tc.in) + } + } +} + +func TestParseDurationRoundTrip(t *testing.T) { + 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) + } + } +} + +func BenchmarkNow(b *testing.B) { for i := 0; i < b.N; i++ { - Nanoseconds() + Now() } } func BenchmarkFormat(b *testing.B) { - time := SecondsToLocalTime(1265346057) + time := Unix(1265346057, 0) for i := 0; i < b.N; i++ { time.Format("Mon Jan 2 15:04:05 2006") } @@ -504,3 +945,31 @@ func BenchmarkParse(b *testing.B) { Parse(ANSIC, "Mon Jan 2 15:04:05 2006") } } + +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() + } +} diff --git a/src/pkg/time/zoneinfo.go b/src/pkg/time/zoneinfo.go new file mode 100644 index 000000000..3c5774404 --- /dev/null +++ b/src/pkg/time/zoneinfo.go @@ -0,0 +1,204 @@ +// 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 ( + "sync" + "syscall" +) + +// 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, such as CEST and CET for central Europe. +type Location struct { + name string + zone []zone + tx []zoneTrans + + // 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 CEST or 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 +} + +// 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. +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 argument to LoadLocation. +func (l *Location) String() string { + return l.get().name +} + +// FixedZone returns a Location that always uses +// the given zone name and offset (seconds east of UTC). +func FixedZone(name string, offset int) *Location { + l := &Location{ + name: name, + zone: []zone{{name, offset, false}}, + tx: []zoneTrans{{-1 << 63, 0, false, false}}, + cacheStart: -1 << 63, + cacheEnd: 1<<63 - 1, + } + 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, isDST bool, start, end int64) { + l = l.get() + + if len(l.tx) == 0 { + name = "UTC" + offset = 0 + isDST = false + start = -1 << 63 + end = 1<<63 - 1 + return + } + + if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd { + name = zone.name + offset = zone.offset + isDST = zone.isDST + start = l.cacheStart + end = l.cacheEnd + return + } + + // Binary search for entry with largest time <= sec. + // Not using sort.Search to avoid dependencies. + tx := l.tx + end = 1<<63 - 1 + for len(tx) > 1 { + m := len(tx) / 2 + lim := tx[m].when + if sec < lim { + end = lim + tx = tx[0:m] + } else { + tx = tx[m:] + } + } + zone := &l.zone[tx[0].index] + name = zone.name + offset = zone.offset + isDST = zone.isDST + start = tx[0].when + // end = maintained during the search + return +} + +// lookupName returns information about the time zone with +// the given name (such as "EST"). +func (l *Location) lookupName(name string) (offset int, isDST bool, ok bool) { + l = l.get() + for i := range l.zone { + zone := &l.zone[i] + if zone.name == name { + return zone.offset, zone.isDST, true + } + } + return +} + +// lookupOffset returns information about the time zone with +// the given offset (such as -5*60*60). +func (l *Location) lookupOffset(offset int) (name string, isDST bool, ok bool) { + l = l.get() + for i := range l.zone { + zone := &l.zone[i] + if zone.offset == offset { + return zone.name, zone.isDST, true + } + } + 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 zoneinfo, _ = syscall.Getenv("ZONEINFO") + +// 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". +// +// The time zone database needed by LoadLocation may not be +// present on all systems, especially non-Unix systems. +// LoadLocation looks in the directory or uncompressed zip file +// named by the ZONEINFO environment variable, if any, then looks in +// known installation locations on Unix systems, +// and finally looks in $GOROOT/lib/time/zoneinfo.zip. +func LoadLocation(name string) (*Location, error) { + if name == "" || name == "UTC" { + return UTC, nil + } + if name == "Local" { + return Local, nil + } + if zoneinfo != "" { + if z, err := loadZoneFile(zoneinfo, name); err == nil { + z.name = name + return z, nil + } + } + return loadLocation(name) +} diff --git a/src/pkg/time/zoneinfo_plan9.go b/src/pkg/time/zoneinfo_plan9.go index 3c3e7c424..6855238dc 100644 --- a/src/pkg/time/zoneinfo_plan9.go +++ b/src/pkg/time/zoneinfo_plan9.go @@ -7,53 +7,150 @@ package time import ( - "os" - "strconv" - "strings" + "errors" + "runtime" + "syscall" ) -func parseZones(s string) (zt []zonetime) { - f := strings.Fields(s) +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 { - return + if len(f) == 2 && f[0] == "GMT" { + return UTC, nil + } + return nil, badData } + var zones [2]zone + // standard timezone offset - o, err := strconv.Atoi(f[1]) + o, err := atoi(f[1]) if err != nil { - return + return nil, badData } - std := &zone{name: f[0], utcoff: o, isdst: false} + zones[0] = zone{name: f[0], offset: o, isDST: false} // alternate timezone offset - o, err = strconv.Atoi(f[3]) + o, err = atoi(f[3]) if err != nil { - return + return nil, badData } - dst := &zone{name: f[2], utcoff: o, isdst: true} + 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++ { - z := std + zi := 0 if i%2 == 0 { - z = dst + zi = 1 } - t, err := strconv.Atoi(f[i]) + t, err := atoi(f[i]) if err != nil { - return nil + return nil, badData + } + 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 = 1<<63 - 1 + if i+1 < len(tx) { + l.cacheEnd = tx[i+1].when + } + l.cacheZone = &l.zone[tx[i].index] } - t -= std.utcoff - zt = append(zt, zonetime{time: int32(t), zone: z}) } - return + + return l, nil +} + +func loadZoneFilePlan9(name string) (*Location, error) { + b, err := readFile(name) + if err != nil { + return nil, err + } + return loadZoneDataPlan9(string(b)) } -func setupZone() { - t, err := os.Getenverror("timezone") +func initTestingZone() { + z, err := loadLocation("America/Los_Angeles") if err != nil { - // do nothing: use UTC - return + panic("cannot load America/Los_Angeles for testing: " + err.Error()) + } + z.name = "Local" + localLoc = *z +} + +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" +} + +func loadLocation(name string) (*Location, error) { + if z, err := loadZoneFile(runtime.GOROOT()+"/lib/time/zoneinfo.zip", name); err == nil { + z.name = name + return z, nil } - zones = parseZones(t) + return nil, errors.New("unknown time zone " + name) } diff --git a/src/pkg/time/zoneinfo_posix.go b/src/pkg/time/zoneinfo_posix.go deleted file mode 100644 index b49216410..000000000 --- a/src/pkg/time/zoneinfo_posix.go +++ /dev/null @@ -1,62 +0,0 @@ -// 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 "sync" - -// Parsed representation -type zone struct { - utcoff int - isdst bool - name string -} - -type zonetime struct { - time int32 // transition time, in seconds since 1970 GMT - zone *zone // the zone that goes into effect at that time - isstd, isutc bool // ignored - no idea what these mean -} - -var zones []zonetime -var onceSetupZone sync.Once - -// Look up the correct time zone (daylight savings or not) for the given unix time, in the current location. -func lookupTimezone(sec int64) (zone string, offset int) { - onceSetupZone.Do(setupZone) - if len(zones) == 0 { - return "UTC", 0 - } - - // Binary search for entry with largest time <= sec - tz := zones - for len(tz) > 1 { - m := len(tz) / 2 - if sec < int64(tz[m].time) { - tz = tz[0:m] - } else { - tz = tz[m:] - } - } - z := tz[0].zone - return z.name, z.utcoff -} - -// lookupByName returns the time offset for the -// time zone with the given abbreviation. It only considers -// time zones that apply to the current system. -// For example, for a system configured as being in New York, -// it only recognizes "EST" and "EDT". -// For a system in San Francisco, "PST" and "PDT". -// For a system in Sydney, "EST" and "EDT", though they have -// different meanings than they do in New York. -func lookupByName(name string) (off int, found bool) { - onceSetupZone.Do(setupZone) - for _, z := range zones { - if name == z.zone.name { - return z.zone.utcoff, true - } - } - return 0, false -} diff --git a/src/pkg/time/zoneinfo_read.go b/src/pkg/time/zoneinfo_read.go new file mode 100644 index 000000000..ebb4205a9 --- /dev/null +++ b/src/pkg/time/zoneinfo_read.go @@ -0,0 +1,341 @@ +// 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), http://en.wikipedia.org/wiki/Zoneinfo, +// and ftp://munnari.oz.au/pub/oldtz/ + +package time + +import "errors" + +const ( + headerSize = 4 + 16 + 4*7 +) + +// Simple I/O interface to binary blob of data. +type data struct { + p []byte + error bool +} + +func (d *data) 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 *data) big4() (n uint32, ok bool) { + p := d.read(4) + if len(p) < 4 { + d.error = true + return 0, false + } + return uint32(p[0])<<24 | uint32(p[1])<<16 | uint32(p[2])<<8 | uint32(p[3]), true +} + +func (d *data) byte() (n byte, ok bool) { + p := d.read(1) + if len(p) < 1 { + d.error = true + return 0, false + } + return p[0], true +} + +// 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 badData = errors.New("malformed time zone information") + +func loadZoneData(bytes []byte) (l *Location, err error) { + d := data{bytes, false} + + // 4-byte magic "TZif" + if magic := d.read(4); string(magic) != "TZif" { + return nil, badData + } + + // 1-byte version, then 15 bytes of padding + var p []byte + if p = d.read(16); len(p) != 16 || p[0] != 0 && p[0] != '2' { + return nil, badData + } + + // 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, badData + } + n[i] = int(nn) + } + + // Transition times. + txtimes := data{d.read(n[NTime] * 4), false} + + // Time zone indices for transition times. + txzones := d.read(n[NTime]) + + // Zone info structures + zonedata := data{d.read(n[NZone] * 6), false} + + // Time zone abbreviations. + abbrev := d.read(n[NChar]) + + // Leap-second time pairs + d.read(n[NLeap] * 8) + + // 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, badData + } + + // If version == 2, the entire file repeats, this time using + // 8-byte ints for txtimes and leap seconds. + // We won't need those until 2106. + + // Now we can build up a useful data structure. + // First the zone information. + // utcoff[4] isdst[1] nameindex[1] + zone := make([]zone, n[NZone]) + for i := range zone { + var ok bool + var n uint32 + if n, ok = zonedata.big4(); !ok { + return nil, badData + } + zone[i].offset = int(n) + var b byte + if b, ok = zonedata.byte(); !ok { + return nil, badData + } + zone[i].isDST = b != 0 + if b, ok = zonedata.byte(); !ok || int(b) >= len(abbrev) { + return nil, badData + } + zone[i].name = byteString(abbrev[b:]) + } + + // Now the transition time info. + tx := make([]zoneTrans, n[NTime]) + for i := range tx { + var ok bool + var n uint32 + if n, ok = txtimes.big4(); !ok { + return nil, badData + } + tx[i].when = int64(int32(n)) + if int(txzones[i]) >= len(zone) { + return nil, badData + } + 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 + } + } + + // Commited to succeed. + l = &Location{zone: zone, 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 = 1<<63 - 1 + if i+1 < len(tx) { + l.cacheEnd = tx[i+1].when + } + l.cacheZone = &l.zone[tx[i].index] + } + } + + return l, nil +} + +func loadZoneFile(dir, name string) (l *Location, err error) { + if len(dir) > 4 && dir[len(dir)-4:] == ".zip" { + return loadZoneZip(dir, name) + } + if dir != "" { + name = dir + "/" + name + } + buf, err := readFile(name) + if err != nil { + return + } + return loadZoneData(buf) +} + +// 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 +} + +func loadZoneZip(zipfile, name string) (l *Location, err error) { + fd, err := open(zipfile) + if err != nil { + return nil, errors.New("open " + zipfile + ": " + err.Error()) + } + 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 loadZoneData(buf) + } + + return nil, errors.New("cannot find " + name + " in zip file " + zipfile) +} diff --git a/src/pkg/time/zoneinfo_unix.go b/src/pkg/time/zoneinfo_unix.go index f3ea7b6fd..2c951a983 100644 --- a/src/pkg/time/zoneinfo_unix.go +++ b/src/pkg/time/zoneinfo_unix.go @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. +// +build darwin freebsd linux netbsd openbsd + // Parse "zoneinfo" time zone file. // This is a fairly standard file format used on OS X, Linux, BSD, Sun, and others. // See tzfile(5), http://en.wikipedia.org/wiki/Zoneinfo, @@ -10,204 +12,61 @@ package time import ( - "io/ioutil" - "os" -) - -const ( - headerSize = 4 + 16 + 4*7 + "errors" + "runtime" + "syscall" ) -// Simple I/O interface to binary blob of data. -type data struct { - p []byte - error bool -} - -func (d *data) 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 *data) big4() (n uint32, ok bool) { - p := d.read(4) - if len(p) < 4 { - d.error = true - return 0, false - } - return uint32(p[0])<<24 | uint32(p[1])<<16 | uint32(p[2])<<8 | uint32(p[3]), true -} - -func (d *data) byte() (n byte, ok bool) { - p := d.read(1) - if len(p) < 1 { - d.error = true - return 0, false +func initTestingZone() { + z, err := loadZoneFile(runtime.GOROOT()+"/lib/time/zoneinfo.zip", "America/Los_Angeles") + if err != nil { + panic("cannot load America/Los_Angeles for testing: " + err.Error()) } - return p[0], true + z.name = "Local" + localLoc = *z } -// 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) +// Many systems use /usr/share/zoneinfo, Solaris 2 has +// /usr/share/lib/zoneinfo, IRIX 6 has /usr/lib/locale/TZ. +var zoneDirs = []string{ + "/usr/share/zoneinfo/", + "/usr/share/lib/zoneinfo/", + "/usr/lib/locale/TZ/", + runtime.GOROOT() + "/lib/time/zoneinfo/", } -func parseinfo(bytes []byte) (zt []zonetime, ok bool) { - d := data{bytes, false} - - // 4-byte magic "TZif" - if magic := d.read(4); string(magic) != "TZif" { - return nil, false - } - - // 1-byte version, then 15 bytes of padding - var p []byte - if p = d.read(16); len(p) != 16 || p[0] != 0 && p[0] != '2' { - return nil, false - } - - // 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, false - } - n[i] = int(nn) - } - - // Transition times. - txtimes := data{d.read(n[NTime] * 4), false} - - // Time zone indices for transition times. - txzones := d.read(n[NTime]) - - // Zone info structures - zonedata := data{d.read(n[NZone] * 6), false} - - // Time zone abbreviations. - abbrev := d.read(n[NChar]) - - // Leap-second time pairs - d.read(n[NLeap] * 8) - - // 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, false - } - - // If version == 2, the entire file repeats, this time using - // 8-byte ints for txtimes and leap seconds. - // We won't need those until 2106. - - // Now we can build up a useful data structure. - // First the zone information. - // utcoff[4] isdst[1] nameindex[1] - z := make([]zone, n[NZone]) - for i := 0; i < len(z); i++ { - var ok bool - var n uint32 - if n, ok = zonedata.big4(); !ok { - return nil, false - } - z[i].utcoff = int(n) - var b byte - if b, ok = zonedata.byte(); !ok { - return nil, false - } - z[i].isdst = b != 0 - if b, ok = zonedata.byte(); !ok || int(b) >= len(abbrev) { - return nil, false - } - z[i].name = byteString(abbrev[b:]) - } +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" means use /usr/share/zoneinfo/foo. - // Now the transition time info. - zt = make([]zonetime, n[NTime]) - for i := 0; i < len(zt); i++ { - var ok bool - var n uint32 - if n, ok = txtimes.big4(); !ok { - return nil, false - } - zt[i].time = int32(n) - if int(txzones[i]) >= len(z) { - return nil, false - } - zt[i].zone = &z[txzones[i]] - if i < len(isstd) { - zt[i].isstd = isstd[i] != 0 + tz, ok := syscall.Getenv("TZ") + switch { + case !ok: + z, err := loadZoneFile("", "/etc/localtime") + if err == nil { + localLoc = *z + localLoc.name = "Local" + return } - if i < len(isutc) { - zt[i].isutc = isutc[i] != 0 + case tz != "" && tz != "UTC": + if z, err := loadLocation(tz); err == nil { + localLoc = *z + return } } - return zt, true -} -func readinfofile(name string) ([]zonetime, bool) { - buf, err := ioutil.ReadFile(name) - if err != nil { - return nil, false - } - return parseinfo(buf) + // Fall back to UTC. + localLoc.name = "UTC" } -func setupZone() { - // consult $TZ to find the time zone to use. - // no $TZ means use the system default /etc/localtime. - // $TZ="" means use UTC. - // $TZ="foo" means use /usr/share/zoneinfo/foo. - // Many systems use /usr/share/zoneinfo, Solaris 2 has - // /usr/share/lib/zoneinfo, IRIX 6 has /usr/lib/locale/TZ. - zoneDirs := []string{"/usr/share/zoneinfo/", - "/usr/share/lib/zoneinfo/", - "/usr/lib/locale/TZ/"} - - tz, err := os.Getenverror("TZ") - switch { - case err == os.ENOENV: - zones, _ = readinfofile("/etc/localtime") - case len(tz) > 0: - for _, zoneDir := range zoneDirs { - var ok bool - if zones, ok = readinfofile(zoneDir + tz); ok { - break - } +func loadLocation(name string) (*Location, error) { + for _, zoneDir := range zoneDirs { + if z, err := loadZoneFile(zoneDir, name); err == nil { + z.name = name + return z, nil } - case len(tz) == 0: - // do nothing: use UTC } + return nil, errors.New("unknown time zone " + name) } diff --git a/src/pkg/time/zoneinfo_windows.go b/src/pkg/time/zoneinfo_windows.go index fabc00601..754e392de 100644 --- a/src/pkg/time/zoneinfo_windows.go +++ b/src/pkg/time/zoneinfo_windows.go @@ -5,188 +5,157 @@ package time import ( + "errors" + "runtime" "syscall" - "sync" - "os" ) -// BUG(brainman): The Windows implementation assumes that -// this year's rules for daylight savings time apply to all previous -// and future years as well. - -// TODO(brainman): use GetDynamicTimeZoneInformation, whenever possible (Vista and up), -// to improve on situation described in the bug above. - -type zone struct { - name string - offset int - year int64 - month, day, dayofweek int - hour, minute, second int - abssec int64 - prev *zone -} +// TODO(rsc): Fall back to copy of zoneinfo files. -// Populate zone struct with Windows supplied information. Returns true, if data is valid. -func (z *zone) populate(bias, biasdelta int32, d *syscall.Systemtime, name []uint16) (dateisgood bool) { - z.name = syscall.UTF16ToString(name) - z.offset = int(bias) - z.year = int64(d.Year) - z.month = int(d.Month) - z.day = int(d.Day) - z.dayofweek = int(d.DayOfWeek) - z.hour = int(d.Hour) - z.minute = int(d.Minute) - z.second = int(d.Second) - dateisgood = d.Month != 0 - if dateisgood { - z.offset += int(biasdelta) - } - z.offset = -z.offset * 60 - return -} +// 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. +// Also, time zone abbreviations are unavailable. The implementation constructs +// them using the capital letters from a longer time zone description. -// Pre-calculate cutoff time in seconds since the Unix epoch, if data is supplied in "absolute" format. -func (z *zone) preCalculateAbsSec() { - if z.year != 0 { - z.abssec = (&Time{z.year, int(z.month), int(z.day), int(z.hour), int(z.minute), int(z.second), 0, 0, 0, ""}).Seconds() - // Time given is in "local" time. Adjust it for "utc". - z.abssec -= int64(z.prev.offset) +// abbrev returns the abbreviation to use for the given zone name. +func abbrev(name []uint16) string { + // name is 'Pacific Standard Time' but we want 'PST'. + // Extract just capital letters. It's not perfect but the + // information we need is not available from the kernel. + // Because time zone abbreviations are not unique, + // Windows refuses to expose them. + // + // http://social.msdn.microsoft.com/Forums/eu/vclanguage/thread/a87e1d25-fb71-4fe0-ae9c-a9578c9753eb + // http://stackoverflow.com/questions/4195948/windows-time-zone-abbreviations-in-asp-net + var short []rune + for _, c := range name { + if 'A' <= c && c <= 'Z' { + short = append(short, rune(c)) + } } + return string(short) } -// Convert zone cutoff time to sec in number of seconds since the Unix epoch, given particular year. -func (z *zone) cutoffSeconds(year int64) int64 { +// 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: - // z.month is month number (1-12) - // z.dayofweek is appropriate weekday (Sunday=0 to Saturday=6) - // z.day is week within the month (1 to 5, where 5 is last week of the month) - // z.hour, z.minute and z.second are absolute time - t := &Time{year, int(z.month), 1, int(z.hour), int(z.minute), int(z.second), 0, 0, 0, ""} - t = SecondsToUTC(t.Seconds()) - i := int(z.dayofweek) - t.Weekday + // 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 } - t.Day += i - if week := int(z.day) - 1; week < 4 { - t.Day += week * 7 + day += i + if week := int(d.Day) - 1; week < 4 { + day += week * 7 } else { // "Last" instance of the day. - t.Day += 4 * 7 - if t.Day > months(year)[t.Month] { - t.Day -= 7 + day += 4 * 7 + if day > daysIn(Month(d.Month), year) { + day -= 7 } } - // Result is in "local" time. Adjust it for "utc". - return t.Seconds() - int64(z.prev.offset) + return t.sec + int64(day-1)*secondsPerDay + internalToUnix } -// Is t before the cutoff for switching to z? -func (z *zone) isBeforeCutoff(t *Time) bool { - var coff int64 - if z.year == 0 { - // "day in month" format used - coff = z.cutoffSeconds(t.Year) - } else { - // "absolute" format used - coff = z.abssec - } - return t.Seconds() < coff -} +func initLocalFromTZI(i *syscall.Timezoneinformation) { + l := &localLoc -type zoneinfo struct { - disabled bool // daylight saving time is not used locally - offsetIfDisabled int - januaryIsStd bool // is january 1 standard time? - std, dst zone -} + nzone := 1 + if i.StandardDate.Month > 0 { + nzone++ + } + l.zone = make([]zone, nzone) -// Pick zone (std or dst) t time belongs to. -func (zi *zoneinfo) pickZone(t *Time) *zone { - z := &zi.std - if tz.januaryIsStd { - if !zi.dst.isBeforeCutoff(t) && zi.std.isBeforeCutoff(t) { - // after switch to daylight time and before the switch back to standard - z = &zi.dst - } - } else { - if zi.std.isBeforeCutoff(t) || !zi.dst.isBeforeCutoff(t) { - // before switch to standard time or after the switch back to daylight - z = &zi.dst - } + std := &l.zone[0] + std.name = abbrev(i.StandardName[0:]) + if nzone == 1 { + // No daylight savings. + std.offset = -int(i.Bias) * 60 + l.cacheStart = -1 << 63 + l.cacheEnd = 1<<63 - 1 + l.cacheZone = std + return } - return z -} -var tz zoneinfo -var initError os.Error -var onceSetupZone sync.Once + // 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 -func setupZone() { - var i syscall.Timezoneinformation - if _, e := syscall.GetTimeZoneInformation(&i); e != 0 { - initError = os.NewSyscallError("GetTimeZoneInformation", e) - return + dst := &l.zone[1] + dst.name = abbrev(i.DaylightName[0:]) + 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 } - if !tz.std.populate(i.Bias, i.StandardBias, &i.StandardDate, i.StandardName[0:]) { - tz.disabled = true - tz.offsetIfDisabled = tz.std.offset - return + + // 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++ } - tz.std.prev = &tz.dst - tz.dst.populate(i.Bias, i.DaylightBias, &i.DaylightDate, i.DaylightName[0:]) - tz.dst.prev = &tz.std - tz.std.preCalculateAbsSec() - tz.dst.preCalculateAbsSec() - // Is january 1 standard time this year? - t := UTC() - tz.januaryIsStd = tz.dst.cutoffSeconds(t.Year) < tz.std.cutoffSeconds(t.Year) } -// Look up the correct time zone (daylight savings or not) for the given unix time, in the current location. -func lookupTimezone(sec int64) (zone string, offset int) { - onceSetupZone.Do(setupZone) - if initError != nil { - return "", 0 - } - if tz.disabled { - return "", tz.offsetIfDisabled - } - t := SecondsToUTC(sec) - z := &tz.std - if tz.std.year == 0 { - // "day in month" format used - z = tz.pickZone(t) - } else { - // "absolute" format used - if tz.std.year == t.Year { - // we have rule for the year in question - z = tz.pickZone(t) - } else { - // we do not have any information for that year, - // will assume standard offset all year around - } - } - return z.name, z.offset +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, } -// lookupByName returns the time offset for the -// time zone with the given abbreviation. It only considers -// time zones that apply to the current system. -func lookupByName(name string) (off int, found bool) { - onceSetupZone.Do(setupZone) - if initError != nil { - return 0, false - } - if tz.disabled { - return tz.offsetIfDisabled, false +func initTestingZone() { + initLocalFromTZI(&usPacific) +} + +func initLocal() { + var i syscall.Timezoneinformation + if _, err := syscall.GetTimeZoneInformation(&i); err != nil { + localLoc.name = "UTC" + return } - switch name { - case tz.std.name: - return tz.std.offset, true - case tz.dst.name: - return tz.dst.offset, true + initLocalFromTZI(&i) +} + +func loadLocation(name string) (*Location, error) { + if z, err := loadZoneFile(runtime.GOROOT()+`\lib\time\zoneinfo.zip`, name); err == nil { + z.name = name + return z, nil } - return 0, false + return nil, errors.New("unknown time zone " + name) } |