diff options
author | Michael Stapelberg <stapelberg@debian.org> | 2013-03-04 21:27:36 +0100 |
---|---|---|
committer | Michael Stapelberg <michael@stapelberg.de> | 2013-03-04 21:27:36 +0100 |
commit | 04b08da9af0c450d645ab7389d1467308cfc2db8 (patch) | |
tree | db247935fa4f2f94408edc3acd5d0d4f997aa0d8 /src/pkg/time | |
parent | 917c5fb8ec48e22459d77e3849e6d388f93d3260 (diff) | |
download | golang-upstream/1.1_hg20130304.tar.gz |
Imported Upstream version 1.1~hg20130304upstream/1.1_hg20130304
Diffstat (limited to 'src/pkg/time')
-rw-r--r-- | src/pkg/time/example_test.go | 97 | ||||
-rw-r--r-- | src/pkg/time/export_test.go | 19 | ||||
-rw-r--r-- | src/pkg/time/format.go | 485 | ||||
-rw-r--r-- | src/pkg/time/internal_test.go | 2 | ||||
-rw-r--r-- | src/pkg/time/sleep.go | 38 | ||||
-rw-r--r-- | src/pkg/time/sleep_test.go | 74 | ||||
-rw-r--r-- | src/pkg/time/tick.go | 4 | ||||
-rw-r--r-- | src/pkg/time/time.go | 173 | ||||
-rw-r--r-- | src/pkg/time/time_test.go | 424 | ||||
-rw-r--r-- | src/pkg/time/zoneinfo.go | 39 | ||||
-rw-r--r-- | src/pkg/time/zoneinfo_read.go | 12 | ||||
-rw-r--r-- | src/pkg/time/zoneinfo_windows.go | 4 |
12 files changed, 1127 insertions, 244 deletions
diff --git a/src/pkg/time/example_test.go b/src/pkg/time/example_test.go index 944cc789c..8928caaba 100644 --- a/src/pkg/time/example_test.go +++ b/src/pkg/time/example_test.go @@ -56,3 +56,100 @@ func ExampleDate() { fmt.Printf("Go launched at %s\n", t.Local()) // Output: Go launched at 2009-11-10 15:00:00 -0800 PST } + +func ExampleTime_Format() { + const format = "Jan 2, 2006 at 3:04pm (MST)" + t := time.Date(2009, time.November, 10, 15, 0, 0, 0, time.Local) + fmt.Println(t.Format(format)) + fmt.Println(t.UTC().Format(format)) + // Output: + // Nov 10, 2009 at 3:00pm (PST) + // Nov 10, 2009 at 11:00pm (UTC) +} + +func ExampleParse() { + const longForm = "Jan 2, 2006 at 3:04pm (MST)" + t, _ := time.Parse(longForm, "Feb 3, 2013 at 7:54pm (PST)") + fmt.Println(t) + + // Note: without explicit zone, returns time in UTC. + const shortForm = "2006-Jan-02" + t, _ = time.Parse(shortForm, "2013-Feb-03") + fmt.Println(t) + + // Output: + // 2013-02-03 19:54:00 -0800 PST + // 2013-02-03 00:00:00 +0000 UTC +} + +func ExampleParseInLocation() { + loc, _ := time.LoadLocation("Europe/Berlin") + + const longForm = "Jan 2, 2006 at 3:04pm (MST)" + t, _ := time.ParseInLocation(longForm, "Jul 9, 2012 at 5:02am (CEST)", loc) + fmt.Println(t) + + // Note: without explicit zone, returns time in given location. + const shortForm = "2006-Jan-02" + t, _ = time.ParseInLocation(shortForm, "2012-Jul-09", loc) + fmt.Println(t) + + // Output: + // 2012-07-09 05:02:00 +0200 CEST + // 2012-07-09 00:00:00 +0200 CEST +} + +func ExampleTime_Round() { + t := time.Date(0, 0, 0, 12, 15, 30, 918273645, time.UTC) + round := []time.Duration{ + time.Nanosecond, + time.Microsecond, + time.Millisecond, + time.Second, + 2 * time.Second, + time.Minute, + 10 * time.Minute, + time.Hour, + } + + for _, d := range round { + fmt.Printf("t.Round(%6s) = %s\n", d, t.Round(d).Format("15:04:05.999999999")) + } + // Output: + // t.Round( 1ns) = 12:15:30.918273645 + // t.Round( 1us) = 12:15:30.918274 + // t.Round( 1ms) = 12:15:30.918 + // t.Round( 1s) = 12:15:31 + // t.Round( 2s) = 12:15:30 + // t.Round( 1m0s) = 12:16:00 + // t.Round( 10m0s) = 12:20:00 + // t.Round(1h0m0s) = 12:00:00 +} + +func ExampleTime_Truncate() { + t, _ := time.Parse("2006 Jan 02 15:04:05", "2012 Dec 07 12:15:30.918273645") + trunc := []time.Duration{ + time.Nanosecond, + time.Microsecond, + time.Millisecond, + time.Second, + 2 * time.Second, + time.Minute, + 10 * time.Minute, + time.Hour, + } + + for _, d := range trunc { + fmt.Printf("t.Truncate(%6s) = %s\n", d, t.Truncate(d).Format("15:04:05.999999999")) + } + + // Output: + // t.Truncate( 1ns) = 12:15:30.918273645 + // t.Truncate( 1us) = 12:15:30.918273 + // t.Truncate( 1ms) = 12:15:30.918 + // t.Truncate( 1s) = 12:15:30 + // t.Truncate( 2s) = 12:15:30 + // t.Truncate( 1m0s) = 12:15:00 + // t.Truncate( 10m0s) = 12:10:00 + // t.Truncate(1h0m0s) = 12:00:00 +} diff --git a/src/pkg/time/export_test.go b/src/pkg/time/export_test.go new file mode 100644 index 000000000..130ca8f7e --- /dev/null +++ b/src/pkg/time/export_test.go @@ -0,0 +1,19 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package time + +import ( + "sync" +) + +func ResetLocalOnceForTest() { + localOnce = sync.Once{} + localLoc = Location{} +} + +func ForceUSPacificForTesting() { + ResetLocalOnceForTest() + localOnce.Do(initTestingZone) +} diff --git a/src/pkg/time/format.go b/src/pkg/time/format.go index efca3a926..817c79a80 100644 --- a/src/pkg/time/format.go +++ b/src/pkg/time/format.go @@ -9,7 +9,7 @@ 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 -// which is Unix time 1136243045. Since MST is GMT-0700, +// which is Unix time 1136239445. 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 @@ -57,63 +57,74 @@ const ( ) const ( - stdLongMonth = "January" - stdMonth = "Jan" - stdNumMonth = "1" - stdZeroMonth = "01" - stdLongWeekDay = "Monday" - stdWeekDay = "Mon" - stdDay = "2" - stdUnderDay = "_2" - stdZeroDay = "02" - stdHour = "15" - stdHour12 = "3" - stdZeroHour12 = "03" - stdMinute = "4" - stdZeroMinute = "04" - stdSecond = "5" - stdZeroSecond = "05" - stdLongYear = "2006" - stdYear = "06" - stdPM = "PM" - stdpm = "pm" - stdTZ = "MST" - stdISO8601TZ = "Z0700" // prints Z for UTC - stdISO8601ColonTZ = "Z07:00" // prints Z for UTC - stdNumTZ = "-0700" // always numeric - stdNumShortTZ = "-07" // always numeric - stdNumColonTZ = "-07:00" // always numeric + _ = iota + stdLongMonth = iota + stdNeedDate // "January" + stdMonth // "Jan" + stdNumMonth // "1" + stdZeroMonth // "01" + stdLongWeekDay // "Monday" + stdWeekDay // "Mon" + stdDay // "2" + stdUnderDay // "_2" + stdZeroDay // "02" + stdHour = iota + stdNeedClock // "15" + stdHour12 // "3" + stdZeroHour12 // "03" + stdMinute // "4" + stdZeroMinute // "04" + stdSecond // "5" + stdZeroSecond // "05" + stdLongYear = iota + stdNeedDate // "2006" + stdYear // "06" + stdPM = iota + stdNeedClock // "PM" + stdpm // "pm" + stdTZ = iota // "MST" + stdISO8601TZ // "Z0700" // prints Z for UTC + stdISO8601ColonTZ // "Z07:00" // prints Z for UTC + stdNumTZ // "-0700" // always numeric + stdNumShortTZ // "-07" // always numeric + stdNumColonTZ // "-07:00" // always numeric + stdFracSecond0 // ".0", ".00", ... , trailing zeros included + stdFracSecond9 // ".9", ".99", ..., trailing zeros omitted + + stdNeedDate = 1 << 8 // need month, day, year + stdNeedClock = 2 << 8 // need hour, minute, second + stdArgShift = 16 // extra argument in high bits, above low stdArgShift + stdMask = 1<<stdArgShift - 1 // mask out argument ) +// std0x records the std values for "01", "02", ..., "06". +var std0x = [...]int{stdZeroMonth, stdZeroDay, stdZeroHour12, stdZeroMinute, stdZeroSecond, stdYear} + // nextStdChunk finds the first occurrence of a std string in // layout and returns the text before, the std string, and the text after. -func nextStdChunk(layout string) (prefix, std, suffix string) { +func nextStdChunk(layout string) (prefix string, std int, suffix string) { for i := 0; i < len(layout); i++ { - switch layout[i] { + switch c := int(layout[i]); c { case 'J': // January, Jan - if len(layout) >= i+7 && layout[i:i+7] == stdLongMonth { - return layout[0:i], stdLongMonth, layout[i+7:] - } - if len(layout) >= i+3 && layout[i:i+3] == stdMonth { + if len(layout) >= i+3 && layout[i:i+3] == "Jan" { + if len(layout) >= i+7 && layout[i:i+7] == "January" { + return layout[0:i], stdLongMonth, layout[i+7:] + } return layout[0:i], stdMonth, layout[i+3:] } case 'M': // Monday, Mon, MST - if len(layout) >= i+6 && layout[i:i+6] == stdLongWeekDay { - return layout[0:i], stdLongWeekDay, layout[i+6:] - } if len(layout) >= i+3 { - if layout[i:i+3] == stdWeekDay { + if layout[i:i+3] == "Mon" { + if len(layout) >= i+6 && layout[i:i+6] == "Monday" { + return layout[0:i], stdLongWeekDay, layout[i+6:] + } return layout[0:i], stdWeekDay, layout[i+3:] } - if layout[i:i+3] == stdTZ { + if layout[i:i+3] == "MST" { return layout[0:i], stdTZ, layout[i+3:] } } case '0': // 01, 02, 03, 04, 05, 06 if len(layout) >= i+2 && '1' <= layout[i+1] && layout[i+1] <= '6' { - return layout[0:i], layout[i : i+2], layout[i+2:] + return layout[0:i], std0x[layout[i+1]-'1'], layout[i+2:] } case '1': // 15, 1 @@ -123,7 +134,7 @@ func nextStdChunk(layout string) (prefix, std, suffix string) { return layout[0:i], stdNumMonth, layout[i+1:] case '2': // 2006, 2 - if len(layout) >= i+4 && layout[i:i+4] == stdLongYear { + if len(layout) >= i+4 && layout[i:i+4] == "2006" { return layout[0:i], stdLongYear, layout[i+4:] } return layout[0:i], stdDay, layout[i+1:] @@ -133,35 +144,41 @@ func nextStdChunk(layout string) (prefix, std, suffix string) { return layout[0:i], stdUnderDay, layout[i+2:] } - case '3', '4', '5': // 3, 4, 5 - return layout[0:i], layout[i : i+1], layout[i+1:] + case '3': + return layout[0:i], stdHour12, layout[i+1:] + + case '4': + return layout[0:i], stdMinute, layout[i+1:] + + case '5': + return layout[0:i], stdSecond, layout[i+1:] case 'P': // PM if len(layout) >= i+2 && layout[i+1] == 'M' { - return layout[0:i], layout[i : i+2], layout[i+2:] + return layout[0:i], stdPM, layout[i+2:] } case 'p': // pm if len(layout) >= i+2 && layout[i+1] == 'm' { - return layout[0:i], layout[i : i+2], layout[i+2:] + return layout[0:i], stdpm, layout[i+2:] } case '-': // -0700, -07:00, -07 - if len(layout) >= i+5 && layout[i:i+5] == stdNumTZ { - return layout[0:i], layout[i : i+5], layout[i+5:] + if len(layout) >= i+5 && layout[i:i+5] == "-0700" { + return layout[0:i], stdNumTZ, layout[i+5:] } - if len(layout) >= i+6 && layout[i:i+6] == stdNumColonTZ { - return layout[0:i], layout[i : i+6], layout[i+6:] + if len(layout) >= i+6 && layout[i:i+6] == "-07:00" { + return layout[0:i], stdNumColonTZ, layout[i+6:] } - if len(layout) >= i+3 && layout[i:i+3] == stdNumShortTZ { - return layout[0:i], layout[i : i+3], layout[i+3:] + if len(layout) >= i+3 && layout[i:i+3] == "-07" { + return layout[0:i], stdNumShortTZ, layout[i+3:] } case 'Z': // Z0700, Z07:00 - if len(layout) >= i+5 && layout[i:i+5] == stdISO8601TZ { - return layout[0:i], layout[i : i+5], layout[i+5:] + if len(layout) >= i+5 && layout[i:i+5] == "Z0700" { + return layout[0:i], stdISO8601TZ, layout[i+5:] } - if len(layout) >= i+6 && layout[i:i+6] == stdISO8601ColonTZ { - return layout[0:i], layout[i : i+6], layout[i+6:] + if len(layout) >= i+6 && layout[i:i+6] == "Z07:00" { + return layout[0:i], stdISO8601ColonTZ, layout[i+6:] } case '.': // .000 or .999 - repeated digits for fractional seconds. if i+1 < len(layout) && (layout[i+1] == '0' || layout[i+1] == '9') { @@ -172,12 +189,17 @@ func nextStdChunk(layout string) (prefix, std, suffix string) { } // 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:] + std := stdFracSecond0 + if layout[i+1] == '9' { + std = stdFracSecond9 + } + std |= (j - (i + 1)) << stdArgShift + return layout[0:i], std, layout[j:] } } } } - return layout, "", "" + return layout, 0, "" } var longDayNames = []string{ @@ -259,27 +281,36 @@ func lookup(tab []string, val string) (int, string, error) { return -1, val, errBad } +// appendUint appends the decimal form of x to b and returns the result. +// If x is a single-digit number and pad != 0, appendUint inserts the pad byte +// before the digit. // Duplicates functionality in strconv, but avoids dependency. -func itoa(x int) string { +func appendUint(b []byte, x uint, pad byte) []byte { + if x < 10 { + if pad != 0 { + b = append(b, pad) + } + return append(b, byte('0'+x)) + } + if x < 100 { + b = append(b, byte('0'+x/10)) + b = append(b, byte('0'+x%10)) + return b + } + 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 + return append(b, '0') } - if x < 0 { + for x >= 10 { n-- - buf[n] = '-' + buf[n] = byte(x%10 + '0') + x /= 10 } - return string(buf[n:]) + n-- + buf[n] = byte(x + '0') + return append(b, buf[n:]...) } // Never printed, just needs to be non-nil for return by atoi. @@ -292,7 +323,8 @@ func atoi(s string) (x int, err error) { neg = true s = s[1:] } - x, rem, err := leadingInt(s) + q, rem, err := leadingInt(s) + x = int(q) if err != nil || rem != "" { return 0, atoiError } @@ -302,37 +334,30 @@ func atoi(s string) (x int, err error) { return x, nil } -func pad(i int, padding string) string { - s := itoa(i) - if i < 10 { - s = padding + s +// formatNano appends a fractional second, as nanoseconds, to b +// and returns the result. +func formatNano(b []byte, nanosec uint, n int, trim bool) []byte { + u := nanosec + var buf [9]byte + for start := len(buf); start > 0; { + start-- + buf[start] = byte(u%10 + '0') + u /= 10 } - return s -} - -func zeroPad(i int) string { return pad(i, "0") } -// formatNano formats a fractional second, as nanoseconds. -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 := itoa(int(uint(nanosec) % 1e9)) - // Zero pad left without fmt. - if len(s) < 9 { - s = "000000000"[:9-len(s)] + s - } if n > 9 { n = 9 } if trim { - for n > 0 && s[n-1] == '0' { + for n > 0 && buf[n-1] == '0' { n-- } if n == 0 { - return "" + return b } } - return "." + s[:n] + b = append(b, '.') + return append(b, buf[:n]...) } // String returns the time formatted using the format string @@ -341,16 +366,6 @@ 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 the standard time, @@ -361,161 +376,172 @@ func (b *buffer) String() string { // definition of the standard time, see the documentation for ANSIC. func (t Time) Format(layout string) string { var ( + name, offset, abs = t.locabs() + year int = -1 month Month day int hour int = -1 min int sec int - b buffer + + b []byte + buf [64]byte ) + max := len(layout) + 10 + if max <= len(buf) { + b = buf[:0] + } else { + b = make([]byte, 0, max) + } // Each iteration generates one std value. - for { + for layout != "" { prefix, std, suffix := nextStdChunk(layout) - b.WriteString(prefix) - if std == "" { + if prefix != "" { + b = append(b, prefix...) + } + if std == 0 { break } + layout = suffix // 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() - } + if year < 0 && std&stdNeedDate != 0 { + year, month, day, _ = absDate(abs, true) } // 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() - } + if hour < 0 && std&stdNeedClock != 0 { + hour, min, sec = absClock(abs) } - var p string - switch std { + switch std & stdMask { case stdYear: - p = zeroPad(year % 100) + y := year + if y < 0 { + y = -y + } + b = appendUint(b, uint(y%100), '0') case stdLongYear: // Pad year to at least 4 digits. - p = itoa(year) + y := year switch { case year <= -1000: - // ok + b = append(b, '-') + y = -y case year <= -100: - p = p[:1] + "0" + p[1:] + b = append(b, "-0"...) + y = -y case year <= -10: - p = p[:1] + "00" + p[1:] + b = append(b, "-00"...) + y = -y case year < 0: - p = p[:1] + "000" + p[1:] + b = append(b, "-000"...) + y = -y case year < 10: - p = "000" + p + b = append(b, "000"...) case year < 100: - p = "00" + p + b = append(b, "00"...) case year < 1000: - p = "0" + p + b = append(b, '0') } + b = appendUint(b, uint(y), 0) case stdMonth: - p = month.String()[:3] + b = append(b, month.String()[:3]...) case stdLongMonth: - p = month.String() + m := month.String() + b = append(b, m...) case stdNumMonth: - p = itoa(int(month)) + b = appendUint(b, uint(month), 0) case stdZeroMonth: - p = zeroPad(int(month)) + b = appendUint(b, uint(month), '0') case stdWeekDay: - p = t.Weekday().String()[:3] + b = append(b, absWeekday(abs).String()[:3]...) case stdLongWeekDay: - p = t.Weekday().String() + s := absWeekday(abs).String() + b = append(b, s...) case stdDay: - p = itoa(day) + b = appendUint(b, uint(day), 0) case stdUnderDay: - p = pad(day, " ") + b = appendUint(b, uint(day), ' ') case stdZeroDay: - p = zeroPad(day) + b = appendUint(b, uint(day), '0') case stdHour: - p = zeroPad(hour) + b = appendUint(b, uint(hour), '0') case stdHour12: // Noon is 12PM, midnight is 12AM. hr := hour % 12 if hr == 0 { hr = 12 } - p = itoa(hr) + b = appendUint(b, uint(hr), 0) case stdZeroHour12: // Noon is 12PM, midnight is 12AM. hr := hour % 12 if hr == 0 { hr = 12 } - p = zeroPad(hr) + b = appendUint(b, uint(hr), '0') case stdMinute: - p = itoa(min) + b = appendUint(b, uint(min), 0) case stdZeroMinute: - p = zeroPad(min) + b = appendUint(b, uint(min), '0') case stdSecond: - p = itoa(sec) + b = appendUint(b, uint(sec), 0) case stdZeroSecond: - p = zeroPad(sec) + b = appendUint(b, uint(sec), '0') case stdPM: if hour >= 12 { - p = "PM" + b = append(b, "PM"...) } else { - p = "AM" + b = append(b, "AM"...) } case stdpm: if hour >= 12 { - p = "pm" + b = append(b, "pm"...) } else { - p = "am" + b = append(b, "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". - _, offset := t.Zone() - if offset == 0 && std[0] == 'Z' { - p = "Z" + if offset == 0 && (std == stdISO8601TZ || std == stdISO8601ColonTZ) { + b = append(b, 'Z') break } zone := offset / 60 // convert to minutes if zone < 0 { - p = "-" + b = append(b, '-') zone = -zone } else { - p = "+" + b = append(b, '+') } - p += zeroPad(zone / 60) + b = appendUint(b, uint(zone/60), '0') if std == stdISO8601ColonTZ || std == stdNumColonTZ { - p += ":" + b = append(b, ':') } - p += zeroPad(zone % 60) + b = appendUint(b, uint(zone%60), '0') case stdTZ: - 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 := offset / 60 // convert to minutes - if zone < 0 { - p = "-" - zone = -zone - } else { - p = "+" - } - p += zeroPad(zone / 60) - p += zeroPad(zone % 60) + b = append(b, name...) + break } - default: - if len(std) >= 2 && (std[0:2] == ".0" || std[0:2] == ".9") { - p = formatNano(t.Nanosecond(), len(std)-1, std[1] == '9') + // No time zone known for this time, but we must print one. + // Use the -0700 format. + zone := offset / 60 // convert to minutes + if zone < 0 { + b = append(b, '-') + zone = -zone + } else { + b = append(b, '+') } + b = appendUint(b, uint(zone/60), '0') + b = appendUint(b, uint(zone%60), '0') + case stdFracSecond0, stdFracSecond9: + b = formatNano(b, uint(t.Nanosecond()), std>>stdArgShift, std&stdMask == stdFracSecond9) } - b.WriteString(p) - layout = suffix } - return b.String() + return string(b) } var errBad = errors.New("bad value for field") // placeholder not passed to user @@ -585,14 +611,14 @@ func skip(value, prefix string) (string, error) { for len(prefix) > 0 { if prefix[0] == ' ' { if len(value) > 0 && value[0] != ' ' { - return "", errBad + return value, errBad } prefix = cutspace(prefix) value = cutspace(value) continue } if len(value) == 0 || value[0] != prefix[0] { - return "", errBad + return value, errBad } prefix = prefix[1:] value = value[1:] @@ -611,10 +637,41 @@ func skip(value, prefix string) (string, error) { // // 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. +// corresponding to Jan 1, year 0, 15:04:00 UTC (note that because the year is +// 0, this time is before the zero Time). // Years must be in the range 0000..9999. The day of the week is checked // for syntax but it is otherwise ignored. +// +// In the absence of a time zone indicator, Parse returns a time in UTC. +// +// When parsing a time with a zone offset like -0700, if the offset corresponds +// to a time zone used by the current location (Local), then Parse uses that +// location and zone in the returned time. Otherwise it records the time as +// being in a fabricated location with time fixed at the given zone offset. +// +// When parsing a time with a zone abbreviation like MST, if the zone abbreviation +// has a defined offset in the current location, then that offset is used. +// The zone abbreviation "UTC" is recognized as UTC regardless of location. +// If the zone abbreviation is unknown, Parse records the time as being +// in a fabricated location with the given zone abbreviation and a zero offset. +// This choice means that such a time can be parse and reformatted with the +// same layout losslessly, but the exact instant used in the representation will +// differ by the actual zone offset. To avoid such problems, prefer time layouts +// that use a numeric zone offset, or use ParseInLocation. func Parse(layout, value string) (Time, error) { + return parse(layout, value, UTC, Local) +} + +// ParseInLocation is like Parse but differs in two important ways. +// First, in the absence of time zone information, Parse interprets a time as UTC; +// ParseInLocation interprets the time as in the given location. +// Second, when given a zone offset or abbreviation, Parse tries to match it +// against the Local location; ParseInLocation uses the given location. +func ParseInLocation(layout, value string, loc *Location) (Time, error) { + return parse(layout, value, loc, loc) +} + +func parse(layout, value string, defaultLocation, local *Location) (Time, error) { alayout, avalue := layout, value rangeErrString := "" // set if a value is out of range amSet := false // do we need to subtract 12 from the hour for midnight? @@ -638,11 +695,12 @@ func Parse(layout, value string) (Time, error) { for { var err error prefix, std, suffix := nextStdChunk(layout) + stdstr := layout[len(prefix) : len(layout)-len(suffix)] value, err = skip(value, prefix) if err != nil { return Time{}, &ParseError{alayout, avalue, prefix, value, ""} } - if len(std) == 0 { + if std == 0 { if len(value) != 0 { return Time{}, &ParseError{alayout, avalue, "", value, ": extra text: " + value} } @@ -650,7 +708,7 @@ func Parse(layout, value string) (Time, error) { } layout = suffix var p string - switch std { + switch std & stdMask { case stdYear: if len(value) < 2 { err = errBad @@ -716,7 +774,8 @@ func Parse(layout, value string) (Time, error) { // fractional second in the format? if len(value) >= 2 && value[0] == '.' && isDigit(value, 1) { _, std, _ := nextStdChunk(layout) - if len(std) > 0 && std[0] == '.' && isDigit(std, 1) { + std &= stdMask + if std == stdFracSecond0 || std == stdFracSecond9 { // Fractional second in the layout; proceed normally break } @@ -756,7 +815,7 @@ func Parse(layout, value string) (Time, error) { err = errBad } case stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ: - if std[0] == 'Z' && len(value) >= 1 && value[0] == 'Z' { + if (std == stdISO8601TZ || std == stdISO8601ColonTZ) && len(value) >= 1 && value[0] == 'Z' { value = value[1:] z = UTC break @@ -824,21 +883,37 @@ func Parse(layout, value string) (Time, error) { } // It's a valid format. zoneName = p - default: - if len(value) < len(std) { + + case stdFracSecond0: + // stdFracSecond0 requires the exact number of digits as specified in + // the layout. + ndigit := 1 + (std >> stdArgShift) + if len(value) < ndigit { err = errBad break } - if len(std) >= 2 && std[0:2] == ".0" { - nsec, rangeErrString, err = parseNanoseconds(value, len(std)) - value = value[len(std):] + nsec, rangeErrString, err = parseNanoseconds(value, ndigit) + value = value[ndigit:] + + case stdFracSecond9: + if len(value) < 2 || value[0] != '.' || value[1] < '0' || '9' < value[1] { + // Fractional second omitted. + break + } + // Take any number of digits, even more than asked for, + // because it is what the stdSecond case would do. + i := 0 + for i < 9 && i+1 < len(value) && '0' <= value[i+1] && value[i+1] <= '9' { + i++ } + nsec, rangeErrString, err = parseNanoseconds(value, 1+i) + value = value[1+i:] } if rangeErrString != "" { - return Time{}, &ParseError{alayout, avalue, std, value, ": " + rangeErrString + " out of range"} + return Time{}, &ParseError{alayout, avalue, stdstr, value, ": " + rangeErrString + " out of range"} } if err != nil { - return Time{}, &ParseError{alayout, avalue, std, value, ""} + return Time{}, &ParseError{alayout, avalue, stdstr, value, ""} } } if pmSet && hour < 12 { @@ -847,20 +922,19 @@ func Parse(layout, value string) (Time, error) { 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 := Date(year, Month(month), day, hour, min, sec, nsec, UTC) 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) + name, offset, _, _, _ := local.lookup(t.sec + internalToUnix) if offset == zoneOffset && (zoneName == "" || name == zoneName) { - t.loc = Local + t.loc = local return t, nil } @@ -870,16 +944,14 @@ func Parse(layout, value string) (Time, error) { } if zoneName != "" { + t := Date(year, Month(month), day, hour, min, sec, nsec, UTC) // Look for local zone with the given offset. // If that zone was in effect at the given time, use it. - offset, _, ok := Local.lookupName(zoneName) + offset, _, ok := local.lookupName(zoneName, t.sec+internalToUnix) 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 - } + t.sec -= int64(offset) + t.loc = local + return t, nil } // Otherwise, create fake zone with unknown offset. @@ -887,8 +959,8 @@ func Parse(layout, value string) (Time, error) { return t, nil } - // Otherwise, fall back to UTC. - return t, nil + // Otherwise, fall back to default. + return Date(year, Month(month), day, hour, min, sec, nsec, defaultLocation), nil } func parseNanoseconds(value string, nbytes int) (ns int, rangeErrString string, err error) { @@ -896,8 +968,7 @@ func parseNanoseconds(value string, nbytes int) (ns int, rangeErrString string, err = errBad return } - ns, err = atoi(value[1:nbytes]) - if err != nil { + if ns, err = atoi(value[1:nbytes]); err != nil { return } if ns < 0 || 1e9 <= ns { @@ -917,18 +988,18 @@ func parseNanoseconds(value string, nbytes int) (ns int, rangeErrString string, 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) { +func leadingInt(s string) (x int64, 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 { + if x >= (1<<63-10)/10 { // overflow return 0, "", errLeadingInt } - x = x*10 + int(c) - '0' + x = x*10 + int64(c) - '0' } return x, s[i:], nil } @@ -973,7 +1044,7 @@ func ParseDuration(s string) (Duration, error) { for s != "" { g := float64(0) // this element of the sequence - var x int + var x int64 var err error // The next character must be [0-9.] diff --git a/src/pkg/time/internal_test.go b/src/pkg/time/internal_test.go index b753896d7..918a9f33b 100644 --- a/src/pkg/time/internal_test.go +++ b/src/pkg/time/internal_test.go @@ -6,7 +6,7 @@ package time func init() { // force US/Pacific for time zone tests - localOnce.Do(initTestingZone) + ForceUSPacificForTesting() } var Interrupt = interrupt diff --git a/src/pkg/time/sleep.go b/src/pkg/time/sleep.go index 27820b0ea..591fa27b0 100644 --- a/src/pkg/time/sleep.go +++ b/src/pkg/time/sleep.go @@ -18,10 +18,25 @@ type runtimeTimer struct { i int32 when int64 period int64 - f func(int64, interface{}) + f func(int64, interface{}) // NOTE: must not be closure arg interface{} } +// when is a helper function for setting the 'when' field of a runtimeTimer. +// It returns what the time will be, in nanoseconds, Duration d in the future. +// If d is negative, it is ignored. If the returned value would be less than +// zero because of an overflow, MaxInt64 is returned. +func when(d Duration) int64 { + if d <= 0 { + return nano() + } + t := nano() + int64(d) + if t < 0 { + t = 1<<63 - 1 // math.MaxInt64 + } + return t +} + func startTimer(*runtimeTimer) func stopTimer(*runtimeTimer) bool @@ -35,8 +50,10 @@ type Timer struct { // 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 (t *Timer) Stop() (ok bool) { +// expired or been stopped. +// Stop does not close the channel, to prevent a read from the channel succeeding +// incorrectly. +func (t *Timer) Stop() bool { return stopTimer(&t.r) } @@ -47,7 +64,7 @@ func NewTimer(d Duration) *Timer { t := &Timer{ C: c, r: runtimeTimer{ - when: nano() + int64(d), + when: when(d), f: sendTime, arg: c, }, @@ -56,6 +73,17 @@ func NewTimer(d Duration) *Timer { return t } +// Reset changes the timer to expire after duration d. +// It returns true if the timer had been active, false if the timer had +// expired or been stopped. +func (t *Timer) Reset(d Duration) bool { + w := when(d) + active := stopTimer(&t.r) + t.r.when = w + startTimer(&t.r) + return active +} + func sendTime(now int64, c interface{}) { // Non-blocking send of time on c. // Used in NewTimer, it cannot block anyway (buffer). @@ -81,7 +109,7 @@ func After(d Duration) <-chan Time { func AfterFunc(d Duration, f func()) *Timer { t := &Timer{ r: runtimeTimer{ - when: nano() + int64(d), + when: when(d), f: goFunc, arg: f, }, diff --git a/src/pkg/time/sleep_test.go b/src/pkg/time/sleep_test.go index e05773df6..9908e220f 100644 --- a/src/pkg/time/sleep_test.go +++ b/src/pkg/time/sleep_test.go @@ -54,9 +54,10 @@ func TestAfterStress(t *testing.T) { 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() + // Yield so that the OS can wake up the timer thread, + // so that it can generate channel sends for the main goroutine, + // which will eventually set stop = 1 for us. + Sleep(Nanosecond) } }() c := Tick(1) @@ -245,3 +246,70 @@ func TestSleepZeroDeadlock(t *testing.T) { } <-c } + +func testReset(d Duration) error { + t0 := NewTimer(2 * d) + Sleep(d) + if t0.Reset(3*d) != true { + return errors.New("resetting unfired timer returned false") + } + Sleep(2 * d) + select { + case <-t0.C: + return errors.New("timer fired early") + default: + } + Sleep(2 * d) + select { + case <-t0.C: + default: + return errors.New("reset timer did not fire") + } + + if t0.Reset(50*Millisecond) != false { + return errors.New("resetting expired timer returned true") + } + return nil +} + +func TestReset(t *testing.T) { + // We try to run this test with increasingly larger multiples + // until one works so slow, loaded hardware isn't as flaky, + // but without slowing down fast machines unnecessarily. + const unit = 25 * Millisecond + tries := []Duration{ + 1 * unit, + 3 * unit, + 7 * unit, + 15 * unit, + } + var err error + for _, d := range tries { + err = testReset(d) + if err == nil { + t.Logf("passed using duration %v", d) + return + } + } + t.Error(err) +} + +// Test that sleeping for an interval so large it overflows does not +// result in a short sleep duration. +func TestOverflowSleep(t *testing.T) { + const timeout = 25 * Millisecond + const big = Duration(int64(1<<63 - 1)) + select { + case <-After(big): + t.Fatalf("big timeout fired") + case <-After(timeout): + // OK + } + const neg = Duration(-1 << 63) + select { + case <-After(neg): + // OK + case <-After(timeout): + t.Fatalf("negative timeout didn't fire") + } +} diff --git a/src/pkg/time/tick.go b/src/pkg/time/tick.go index 8c6b9bc3b..b92c339c0 100644 --- a/src/pkg/time/tick.go +++ b/src/pkg/time/tick.go @@ -6,7 +6,7 @@ package time import "errors" -// A Ticker holds a synchronous channel that delivers `ticks' of a clock +// A Ticker holds a channel that delivers `ticks' of a clock // at intervals. type Ticker struct { C <-chan Time // The channel on which the ticks are delivered. @@ -39,6 +39,8 @@ func NewTicker(d Duration) *Ticker { } // Stop turns off a ticker. After Stop, no more ticks will be sent. +// Stop does not close the channel, to prevent a read from the channel succeeding +// incorrectly. func (t *Ticker) Stop() { stopTimer(&t.r) } diff --git a/src/pkg/time/time.go b/src/pkg/time/time.go index 2461dac06..d291672af 100644 --- a/src/pkg/time/time.go +++ b/src/pkg/time/time.go @@ -241,10 +241,10 @@ func (t Time) IsZero() bool { // 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 + // Avoid function calls when possible. + if l == nil || l == &localLoc { + l = l.get() } - // 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 { @@ -257,6 +257,30 @@ func (t Time) abs() uint64 { return uint64(sec + (unixToInternal + internalToAbsolute)) } +// locabs is a combination of the Zone and abs methods, +// extracting both return values from a single zone lookup. +func (t Time) locabs() (name string, offset int, abs uint64) { + l := t.loc + if l == nil || l == &localLoc { + l = l.get() + } + // Avoid function call if we hit the local time cache. + sec := t.sec + internalToUnix + if l != &utcLoc { + if l.cacheZone != nil && l.cacheStart <= sec && sec < l.cacheEnd { + name = l.cacheZone.name + offset = l.cacheZone.offset + } else { + name, offset, _, _, _ = l.lookup(sec) + } + sec += int64(offset) + } else { + name = "UTC" + } + abs = uint64(sec + (unixToInternal + internalToAbsolute)) + return +} + // Date returns the year, month, and day in which t occurs. func (t Time) Date() (year int, month Month, day int) { year, month, day, _ = t.date(true) @@ -283,8 +307,13 @@ func (t Time) Day() int { // Weekday returns the day of the week specified by t. func (t Time) Weekday() Weekday { + return absWeekday(t.abs()) +} + +// absWeekday is like Weekday but operates on an absolute time. +func absWeekday(abs uint64) Weekday { // January 1 of the absolute year, like January 1 of 2001, was a Monday. - sec := (t.abs() + uint64(Monday)*secondsPerDay) % secondsPerWeek + sec := (abs + uint64(Monday)*secondsPerDay) % secondsPerWeek return Weekday(int(sec) / secondsPerDay) } @@ -349,7 +378,12 @@ func (t Time) ISOWeek() (year, week int) { // 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) + return absClock(t.abs()) +} + +// absClock is like clock but operates on an absolute time. +func absClock(abs uint64) (hour, min, sec int) { + sec = int(abs % secondsPerDay) hour = sec / secondsPerHour sec -= hour * secondsPerHour min = sec / secondsPerMinute @@ -378,6 +412,13 @@ func (t Time) Nanosecond() int { return int(t.nsec) } +// YearDay returns the day of the year specified by t, in the range [1,365] for non-leap years, +// and [1,366] in leap years. +func (t Time) YearDay() int { + _, _, _, yday := t.date(false) + return yday + 1 +} + // A Duration represents the elapsed time between two instants // as an int64 nanosecond count. The representation limits the // largest representable duration to approximately 290 years. @@ -607,11 +648,16 @@ const ( days1970To2001 = 31*365 + 8 ) -// date computes the year and, only when full=true, +// date computes the year, day of year, and when full=true, // the month and day in which t occurs. func (t Time) date(full bool) (year int, month Month, day int, yday int) { + return absDate(t.abs(), full) +} + +// absDate is like date but operates on an absolute time. +func absDate(abs uint64, full bool) (year int, month Month, day int, yday int) { // Split into time and day. - d := t.abs() / secondsPerDay + d := abs / secondsPerDay // Account for 400 year cycles. n := d / daysPer400Years @@ -987,3 +1033,116 @@ func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) T return Time{unix + unixToInternal, int32(nsec), loc} } + +// Truncate returns the result of rounding t down to a multiple of d (since the zero time). +// If d <= 0, Truncate returns t unchanged. +func (t Time) Truncate(d Duration) Time { + if d <= 0 { + return t + } + _, r := div(t, d) + return t.Add(-r) +} + +// Round returns the result of rounding t to the nearest multiple of d (since the zero time). +// The rounding behavior for halfway values is to round up. +// If d <= 0, Round returns t unchanged. +func (t Time) Round(d Duration) Time { + if d <= 0 { + return t + } + _, r := div(t, d) + if r+r < d { + return t.Add(-r) + } + return t.Add(d - r) +} + +// div divides t by d and returns the quotient parity and remainder. +// We don't use the quotient parity anymore (round half up instead of round to even) +// but it's still here in case we change our minds. +func div(t Time, d Duration) (qmod2 int, r Duration) { + neg := false + if t.sec < 0 { + // Operate on absolute value. + neg = true + t.sec = -t.sec + t.nsec = -t.nsec + if t.nsec < 0 { + t.nsec += 1e9 + t.sec-- // t.sec >= 1 before the -- so safe + } + } + + switch { + // Special case: 2d divides 1 second. + case d < Second && Second%(d+d) == 0: + qmod2 = int(t.nsec/int32(d)) & 1 + r = Duration(t.nsec % int32(d)) + + // Special case: d is a multiple of 1 second. + case d%Second == 0: + d1 := int64(d / Second) + qmod2 = int(t.sec/d1) & 1 + r = Duration(t.sec%d1)*Second + Duration(t.nsec) + + // General case. + // This could be faster if more cleverness were applied, + // but it's really only here to avoid special case restrictions in the API. + // No one will care about these cases. + default: + // Compute nanoseconds as 128-bit number. + sec := uint64(t.sec) + tmp := (sec >> 32) * 1e9 + u1 := tmp >> 32 + u0 := tmp << 32 + tmp = uint64(sec&0xFFFFFFFF) * 1e9 + u0x, u0 := u0, u0+tmp + if u0 < u0x { + u1++ + } + u0x, u0 = u0, u0+uint64(t.nsec) + if u0 < u0x { + u1++ + } + + // Compute remainder by subtracting r<<k for decreasing k. + // Quotient parity is whether we subtract on last round. + d1 := uint64(d) + for d1>>63 != 1 { + d1 <<= 1 + } + d0 := uint64(0) + for { + qmod2 = 0 + if u1 > d1 || u1 == d1 && u0 >= d0 { + // subtract + qmod2 = 1 + u0x, u0 = u0, u0-d0 + if u0 > u0x { + u1-- + } + u1 -= d1 + } + if d1 == 0 && d0 == uint64(d) { + break + } + d0 >>= 1 + d0 |= (d1 & 1) << 63 + d1 >>= 1 + } + r = Duration(u0) + } + + if neg && r != 0 { + // If input was negative and not an exact multiple of d, we computed q, r such that + // q*d + r = -t + // But the right answers are given by -(q-1), d-r: + // q*d + r = -t + // -q*d - r = t + // -(q-1)*d + (d - r) = t + qmod2 ^= 1 + r = d - r + } + return +} diff --git a/src/pkg/time/time_test.go b/src/pkg/time/time_test.go index 28047804e..4b268f73d 100644 --- a/src/pkg/time/time_test.go +++ b/src/pkg/time/time_test.go @@ -9,6 +9,7 @@ import ( "encoding/gob" "encoding/json" "fmt" + "math/big" "math/rand" "strconv" "strings" @@ -192,6 +193,184 @@ func TestNanosecondsToUTCAndBack(t *testing.T) { } } +// The time routines provide no way to get absolute time +// (seconds since zero), but we need it to compute the right +// answer for bizarre roundings like "to the nearest 3 ns". +// Compute as t - year1 = (t - 1970) + (1970 - 2001) + (2001 - 1). +// t - 1970 is returned by Unix and Nanosecond. +// 1970 - 2001 is -(31*365+8)*86400 = -978307200 seconds. +// 2001 - 1 is 2000*365.2425*86400 = 63113904000 seconds. +const unixToZero = -978307200 + 63113904000 + +// abs returns the absolute time stored in t, as seconds and nanoseconds. +func abs(t Time) (sec, nsec int64) { + unix := t.Unix() + nano := t.Nanosecond() + return unix + unixToZero, int64(nano) +} + +// absString returns abs as a decimal string. +func absString(t Time) string { + sec, nsec := abs(t) + if sec < 0 { + sec = -sec + nsec = -nsec + if nsec < 0 { + nsec += 1e9 + sec-- + } + return fmt.Sprintf("-%d%09d", sec, nsec) + } + return fmt.Sprintf("%d%09d", sec, nsec) +} + +var truncateRoundTests = []struct { + t Time + d Duration +}{ + {Date(-1, January, 1, 12, 15, 30, 5e8, UTC), 3}, + {Date(-1, January, 1, 12, 15, 31, 5e8, UTC), 3}, + {Date(2012, January, 1, 12, 15, 30, 5e8, UTC), Second}, + {Date(2012, January, 1, 12, 15, 31, 5e8, UTC), Second}, +} + +func TestTruncateRound(t *testing.T) { + var ( + bsec = new(big.Int) + bnsec = new(big.Int) + bd = new(big.Int) + bt = new(big.Int) + br = new(big.Int) + bq = new(big.Int) + b1e9 = new(big.Int) + ) + + b1e9.SetInt64(1e9) + + testOne := func(ti, tns, di int64) bool { + t0 := Unix(ti, int64(tns)).UTC() + d := Duration(di) + if d < 0 { + d = -d + } + if d <= 0 { + d = 1 + } + + // Compute bt = absolute nanoseconds. + sec, nsec := abs(t0) + bsec.SetInt64(sec) + bnsec.SetInt64(nsec) + bt.Mul(bsec, b1e9) + bt.Add(bt, bnsec) + + // Compute quotient and remainder mod d. + bd.SetInt64(int64(d)) + bq.DivMod(bt, bd, br) + + // To truncate, subtract remainder. + // br is < d, so it fits in an int64. + r := br.Int64() + t1 := t0.Add(-Duration(r)) + + // Check that time.Truncate works. + if trunc := t0.Truncate(d); trunc != t1 { + t.Errorf("Time.Truncate(%s, %s) = %s, want %s\n"+ + "%v trunc %v =\n%v want\n%v", + t0.Format(RFC3339Nano), d, trunc, t1.Format(RFC3339Nano), + absString(t0), int64(d), absString(trunc), absString(t1)) + return false + } + + // To round, add d back if remainder r > d/2 or r == exactly d/2. + // The commented out code would round half to even instead of up, + // but that makes it time-zone dependent, which is a bit strange. + if r > int64(d)/2 || r+r == int64(d) /*&& bq.Bit(0) == 1*/ { + t1 = t1.Add(Duration(d)) + } + + // Check that time.Round works. + if rnd := t0.Round(d); rnd != t1 { + t.Errorf("Time.Round(%s, %s) = %s, want %s\n"+ + "%v round %v =\n%v want\n%v", + t0.Format(RFC3339Nano), d, rnd, t1.Format(RFC3339Nano), + absString(t0), int64(d), absString(rnd), absString(t1)) + return false + } + return true + } + + // manual test cases + for _, tt := range truncateRoundTests { + testOne(tt.t.Unix(), int64(tt.t.Nanosecond()), int64(tt.d)) + } + + // exhaustive near 0 + for i := 0; i < 100; i++ { + for j := 1; j < 100; j++ { + testOne(unixToZero, int64(i), int64(j)) + testOne(unixToZero, -int64(i), int64(j)) + if t.Failed() { + return + } + } + } + + if t.Failed() { + return + } + + // randomly generated test cases + cfg := &quick.Config{MaxCount: 100000} + if testing.Short() { + cfg.MaxCount = 1000 + } + + // divisors of Second + f1 := func(ti int64, tns int32, logdi int32) bool { + d := Duration(1) + a, b := uint(logdi%9), (logdi>>16)%9 + d <<= a + for i := 0; i < int(b); i++ { + d *= 5 + } + return testOne(ti, int64(tns), int64(d)) + } + quick.Check(f1, cfg) + + // multiples of Second + f2 := func(ti int64, tns int32, di int32) bool { + d := Duration(di) * Second + if d < 0 { + d = -d + } + return testOne(ti, int64(tns), int64(d)) + } + quick.Check(f2, cfg) + + // halfway cases + f3 := func(tns, di int64) bool { + di &= 0xfffffffe + if di == 0 { + di = 2 + } + tns -= tns % di + if tns < 0 { + tns += di / 2 + } else { + tns -= di / 2 + } + return testOne(0, tns, di) + } + quick.Check(f3, cfg) + + // full generality + f4 := func(ti int64, tns int32, di int64) bool { + return testOne(ti, int64(tns), di) + } + quick.Check(f4, cfg) +} + type TimeFormatTest struct { time Time formattedValue string @@ -289,7 +468,7 @@ type ParseTest struct { value string hasTZ bool // contains a time zone hasWD bool // contains a weekday - yearSign int // sign of year + yearSign int // sign of year, -1 indicates the year is not present in the format fracDigits int // number of digits of fractional second } @@ -299,6 +478,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}, + {"RFC1123", RFC1123, "Thu, 04 Feb 2010 22:00:57 PDT", true, true, 1, 0}, {"RFC1123Z", RFC1123Z, "Thu, 04 Feb 2010 21:00:57 -0800", true, true, 1, 0}, {"RFC3339", RFC3339, "2010-02-04T21:00:57-08:00", true, false, 1, 0}, {"custom: \"2006-01-02 15:04:05-07\"", "2006-01-02 15:04:05-07", "2010-02-04 21:00:57-08", true, false, 1, 0}, @@ -324,6 +504,23 @@ var parseTests = []ParseTest{ // Leading zeros in other places should not be taken as fractional seconds. {"zero1", "2006.01.02.15.04.05.0", "2010.02.04.21.00.57.0", false, false, 1, 1}, {"zero2", "2006.01.02.15.04.05.00", "2010.02.04.21.00.57.01", false, false, 1, 2}, + + // Accept any number of fractional second digits (including none) for .999... + // In Go 1, .999... was completely ignored in the format, meaning the first two + // cases would succeed, but the next four would not. Go 1.1 accepts all six. + {"", "2006-01-02 15:04:05.9999 -0700 MST", "2010-02-04 21:00:57 -0800 PST", true, false, 1, 0}, + {"", "2006-01-02 15:04:05.999999999 -0700 MST", "2010-02-04 21:00:57 -0800 PST", true, false, 1, 0}, + {"", "2006-01-02 15:04:05.9999 -0700 MST", "2010-02-04 21:00:57.0123 -0800 PST", true, false, 1, 4}, + {"", "2006-01-02 15:04:05.999999999 -0700 MST", "2010-02-04 21:00:57.0123 -0800 PST", true, false, 1, 4}, + {"", "2006-01-02 15:04:05.9999 -0700 MST", "2010-02-04 21:00:57.012345678 -0800 PST", true, false, 1, 9}, + {"", "2006-01-02 15:04:05.999999999 -0700 MST", "2010-02-04 21:00:57.012345678 -0800 PST", true, false, 1, 9}, + + // issue 4502. + {"", StampNano, "Feb 4 21:00:57.012345678", false, false, -1, 9}, + {"", "Jan _2 15:04:05.999", "Feb 4 21:00:57.012300000", false, false, -1, 4}, + {"", "Jan _2 15:04:05.999", "Feb 4 21:00:57.012345678", false, false, -1, 9}, + {"", "Jan _2 15:04:05.999999999", "Feb 4 21:00:57.0123", false, false, -1, 4}, + {"", "Jan _2 15:04:05.999999999", "Feb 4 21:00:57.012345678", false, false, -1, 9}, } func TestParse(t *testing.T) { @@ -337,6 +534,42 @@ func TestParse(t *testing.T) { } } +func TestParseInSydney(t *testing.T) { + loc, err := LoadLocation("Australia/Sydney") + if err != nil { + t.Fatal(err) + } + + // Check that Parse (and ParseInLocation) understand + // that Feb EST and Aug EST are different time zones in Sydney + // even though both are called EST. + t1, err := ParseInLocation("Jan 02 2006 MST", "Feb 01 2013 EST", loc) + if err != nil { + t.Fatal(err) + } + t2 := Date(2013, February, 1, 00, 00, 00, 0, loc) + if t1 != t2 { + t.Fatalf("ParseInLocation(Feb 01 2013 EST, Sydney) = %v, want %v", t1, t2) + } + _, offset := t1.Zone() + if offset != 11*60*60 { + t.Fatalf("ParseInLocation(Feb 01 2013 EST, Sydney).Zone = _, %d, want _, %d", offset, 11*60*60) + } + + t1, err = ParseInLocation("Jan 02 2006 MST", "Aug 01 2013 EST", loc) + if err != nil { + t.Fatal(err) + } + t2 = Date(2013, August, 1, 00, 00, 00, 0, loc) + if t1 != t2 { + t.Fatalf("ParseInLocation(Aug 01 2013 EST, Sydney) = %v, want %v", t1, t2) + } + _, offset = t1.Zone() + if offset != 10*60*60 { + t.Fatalf("ParseInLocation(Aug 01 2013 EST, Sydney).Zone = _, %d, want _, %d", offset, 10*60*60) + } +} + var rubyTests = []ParseTest{ {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0800 2010", true, true, 1, 0}, // Ignore the time zone in the test. If it parses, it'll be OK. @@ -359,7 +592,7 @@ func TestRubyParse(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 { + if test.yearSign >= 0 && test.yearSign*time.Year() != 2010 { t.Errorf("%s: bad year: %d not %d", test.name, time.Year(), 2010) } if time.Month() != February { @@ -440,6 +673,14 @@ var parseErrorTests = []ParseErrorTest{ {"Mon Jan _2 15:04:05.000 2006", "Thu Feb 4 23:00:59x01 2010", "cannot parse"}, {"Mon Jan _2 15:04:05.000 2006", "Thu Feb 4 23:00:59.xxx 2010", "cannot parse"}, {"Mon Jan _2 15:04:05.000 2006", "Thu Feb 4 23:00:59.-123 2010", "fractional second out of range"}, + // issue 4502. StampNano requires exactly 9 digits of precision. + {StampNano, "Dec 7 11:22:01.000000", `cannot parse ".000000" as ".000000000"`}, + {StampNano, "Dec 7 11:22:01.0000000000", "extra text: 0"}, + // issue 4493. Helpful errors. + {RFC3339, "2006-01-02T15:04:05Z07:00", `parsing time "2006-01-02T15:04:05Z07:00": extra text: 07:00`}, + {RFC3339, "2006-01-02T15:04_abc", `parsing time "2006-01-02T15:04_abc" as "2006-01-02T15:04:05Z07:00": cannot parse "_abc" as ":"`}, + {RFC3339, "2006-01-02T15:04:05_abc", `parsing time "2006-01-02T15:04:05_abc" as "2006-01-02T15:04:05Z07:00": cannot parse "_abc" as "Z07:00"`}, + {RFC3339, "2006-01-02T15:04:05Z_abc", `parsing time "2006-01-02T15:04:05Z_abc": extra text: _abc`}, } func TestParseErrors(t *testing.T) { @@ -601,6 +842,103 @@ func TestISOWeek(t *testing.T) { } } +type YearDayTest struct { + year, month, day int + yday int +} + +// Test YearDay in several different scenarios +// and corner cases +var yearDayTests = []YearDayTest{ + // Non-leap-year tests + {2007, 1, 1, 1}, + {2007, 1, 15, 15}, + {2007, 2, 1, 32}, + {2007, 2, 15, 46}, + {2007, 3, 1, 60}, + {2007, 3, 15, 74}, + {2007, 4, 1, 91}, + {2007, 12, 31, 365}, + + // Leap-year tests + {2008, 1, 1, 1}, + {2008, 1, 15, 15}, + {2008, 2, 1, 32}, + {2008, 2, 15, 46}, + {2008, 3, 1, 61}, + {2008, 3, 15, 75}, + {2008, 4, 1, 92}, + {2008, 12, 31, 366}, + + // Looks like leap-year (but isn't) tests + {1900, 1, 1, 1}, + {1900, 1, 15, 15}, + {1900, 2, 1, 32}, + {1900, 2, 15, 46}, + {1900, 3, 1, 60}, + {1900, 3, 15, 74}, + {1900, 4, 1, 91}, + {1900, 12, 31, 365}, + + // Year one tests (non-leap) + {1, 1, 1, 1}, + {1, 1, 15, 15}, + {1, 2, 1, 32}, + {1, 2, 15, 46}, + {1, 3, 1, 60}, + {1, 3, 15, 74}, + {1, 4, 1, 91}, + {1, 12, 31, 365}, + + // Year minus one tests (non-leap) + {-1, 1, 1, 1}, + {-1, 1, 15, 15}, + {-1, 2, 1, 32}, + {-1, 2, 15, 46}, + {-1, 3, 1, 60}, + {-1, 3, 15, 74}, + {-1, 4, 1, 91}, + {-1, 12, 31, 365}, + + // 400 BC tests (leap-year) + {-400, 1, 1, 1}, + {-400, 1, 15, 15}, + {-400, 2, 1, 32}, + {-400, 2, 15, 46}, + {-400, 3, 1, 61}, + {-400, 3, 15, 75}, + {-400, 4, 1, 92}, + {-400, 12, 31, 366}, + + // Special Cases + + // Gregorian calendar change (no effect) + {1582, 10, 4, 277}, + {1582, 10, 15, 288}, +} + +// Check to see if YearDay is location sensitive +var yearDayLocations = []*Location{ + FixedZone("UTC-8", -8*60*60), + FixedZone("UTC-4", -4*60*60), + UTC, + FixedZone("UTC+4", 4*60*60), + FixedZone("UTC+8", 8*60*60), +} + +func TestYearDay(t *testing.T) { + for _, loc := range yearDayLocations { + for _, ydt := range yearDayTests { + dt := Date(ydt.year, Month(ydt.month), ydt.day, 0, 0, 0, 0, loc) + yday := dt.YearDay() + if yday != ydt.yday { + t.Errorf("got %d, expected %d for %d-%02d-%02d in %v", + yday, ydt.yday, ydt.year, ydt.month, ydt.day, loc) + } + } + } +} + var durationTests = []struct { str string d Duration @@ -741,7 +1079,7 @@ 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 + {}, // nil location Date(1, 2, 3, 4, 5, 6, 7, FixedZone("", 32767*60)), Date(1, 2, 3, 4, 5, 6, 7, FixedZone("", -32768*60)), } @@ -805,7 +1143,7 @@ var jsonTests = []struct { time Time json string }{ - {Date(9999, 4, 12, 23, 20, 50, .52*1e9, UTC), `"9999-04-12T23:20:50.52Z"`}, + {Date(9999, 4, 12, 23, 20, 50, 520*1e6, UTC), `"9999-04-12T23:20:50.52Z"`}, {Date(1996, 12, 19, 16, 39, 57, 0, Local), `"1996-12-19T16:39:57-08:00"`}, {Date(0, 1, 1, 0, 0, 0, 1, FixedZone("", 1*60)), `"0000-01-01T00:00:00.000000001+00:01"`}, } @@ -892,6 +1230,8 @@ var parseDurationTests = []struct { {"-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}, + // large value + {"52763797000ns", true, 52763797000 * Nanosecond}, // errors {"", false, 0}, @@ -928,16 +1268,86 @@ func TestParseDurationRoundTrip(t *testing.T) { } } +// golang.org/issue/4622 +func TestLocationRace(t *testing.T) { + ResetLocalOnceForTest() // reset the Once to trigger the race + + c := make(chan string, 1) + go func() { + c <- Now().String() + }() + Now().String() + <-c + Sleep(100 * Millisecond) + + // Back to Los Angeles for subsequent tests: + ForceUSPacificForTesting() +} + +var ( + t Time + u int64 +) + +var mallocTest = []struct { + count int + desc string + fn func() +}{ + {0, `time.Now()`, func() { t = Now() }}, + {0, `time.Now().UnixNano()`, func() { u = Now().UnixNano() }}, +} + +func TestCountMallocs(t *testing.T) { + for _, mt := range mallocTest { + allocs := int(testing.AllocsPerRun(100, mt.fn)) + if allocs > mt.count { + t.Errorf("%s: %d allocs, want %d", mt.desc, allocs, mt.count) + } + } +} + +func TestLoadFixed(t *testing.T) { + // Issue 4064: handle locations without any zone transitions. + loc, err := LoadLocation("Etc/GMT+1") + if err != nil { + t.Fatal(err) + } + + // The tzdata name Etc/GMT+1 uses "east is negative", + // but Go and most other systems use "east is positive". + // So GMT+1 corresponds to -3600 in the Go zone, not +3600. + name, offset := Now().In(loc).Zone() + if name != "GMT+1" || offset != -1*60*60 { + t.Errorf("Now().In(loc).Zone() = %q, %d, want %q, %d", name, offset, "GMT+1", -1*60*60) + } +} + func BenchmarkNow(b *testing.B) { for i := 0; i < b.N; i++ { - Now() + t = Now() + } +} + +func BenchmarkNowUnixNano(b *testing.B) { + for i := 0; i < b.N; i++ { + u = Now().UnixNano() } } func BenchmarkFormat(b *testing.B) { - time := Unix(1265346057, 0) + t := Unix(1265346057, 0) + for i := 0; i < b.N; i++ { + t.Format("Mon Jan 2 15:04:05 2006") + } +} + +func BenchmarkFormatNow(b *testing.B) { + // Like BenchmarkFormat, but easier, because the time zone + // lookup cache is optimized for the present. + t := Now() for i := 0; i < b.N; i++ { - time.Format("Mon Jan 2 15:04:05 2006") + t.Format("Mon Jan 2 15:04:05 2006") } } diff --git a/src/pkg/time/zoneinfo.go b/src/pkg/time/zoneinfo.go index 3c5774404..c44477f47 100644 --- a/src/pkg/time/zoneinfo.go +++ b/src/pkg/time/zoneinfo.go @@ -123,35 +123,58 @@ func (l *Location) lookup(sec int64) (name string, offset int, isDST bool, start // Not using sort.Search to avoid dependencies. tx := l.tx end = 1<<63 - 1 - for len(tx) > 1 { - m := len(tx) / 2 + lo := 0 + hi := len(tx) + for hi-lo > 1 { + m := lo + (hi-lo)/2 lim := tx[m].when if sec < lim { end = lim - tx = tx[0:m] + hi = m } else { - tx = tx[m:] + lo = m } } - zone := &l.zone[tx[0].index] + zone := &l.zone[tx[lo].index] name = zone.name offset = zone.offset isDST = zone.isDST - start = tx[0].when + start = tx[lo].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) { +// the given name (such as "EST") at the given pseudo-Unix time +// (what the given time of day would be in UTC). +func (l *Location) lookupName(name string, unix int64) (offset int, isDST bool, ok bool) { l = l.get() + + // First try for a zone with the right name that was actually + // in effect at the given time. (In Sydney, Australia, both standard + // and daylight-savings time are abbreviated "EST". Using the + // offset helps us pick the right one for the given time. + // It's not perfect: during the backward transition we might pick + // either one.) + for i := range l.zone { + zone := &l.zone[i] + if zone.name == name { + nam, offset, isDST, _, _ := l.lookup(unix - int64(zone.offset)) + if nam == zone.name { + return offset, isDST, true + } + } + } + + // Otherwise fall back to an ordinary name match. for i := range l.zone { zone := &l.zone[i] if zone.name == name { return zone.offset, zone.isDST, true } } + + // Otherwise, give up. return } diff --git a/src/pkg/time/zoneinfo_read.go b/src/pkg/time/zoneinfo_read.go index ebb4205a9..4519c9962 100644 --- a/src/pkg/time/zoneinfo_read.go +++ b/src/pkg/time/zoneinfo_read.go @@ -141,7 +141,7 @@ func loadZoneData(bytes []byte) (l *Location, err error) { if n, ok = zonedata.big4(); !ok { return nil, badData } - zone[i].offset = int(n) + zone[i].offset = int(int32(n)) var b byte if b, ok = zonedata.byte(); !ok { return nil, badData @@ -174,7 +174,13 @@ func loadZoneData(bytes []byte) (l *Location, err error) { } } - // Commited to succeed. + if len(tx) == 0 { + // Build fake transition to cover all time. + // This happens in fixed locations like "Etc/GMT0". + tx = append(tx, zoneTrans{when: -1 << 63, index: 0}) + } + + // Committed to succeed. l = &Location{zone: zone, tx: tx} // Fill in the cache with information about right now, @@ -284,7 +290,7 @@ func loadZoneZip(zipfile, name string) (l *Location, err error) { // 42 off[4] // 46 name[namelen] // 46+namelen+xlen+fclen - next header - // + // if get4(buf) != zcheader { break } diff --git a/src/pkg/time/zoneinfo_windows.go b/src/pkg/time/zoneinfo_windows.go index d596fab93..a8d3dcbfe 100644 --- a/src/pkg/time/zoneinfo_windows.go +++ b/src/pkg/time/zoneinfo_windows.go @@ -15,9 +15,9 @@ import ( // 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. +// 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. +// them using the capital letters from a longer time zone description. // abbrev returns the abbreviation to use for the given zone name. func abbrev(name []uint16) string { |