summaryrefslogtreecommitdiff
path: root/src/pkg/time
diff options
context:
space:
mode:
authorOndřej Surý <ondrej@sury.org>2011-09-13 13:13:40 +0200
committerOndřej Surý <ondrej@sury.org>2011-09-13 13:13:40 +0200
commit5ff4c17907d5b19510a62e08fd8d3b11e62b431d (patch)
treec0650497e988f47be9c6f2324fa692a52dea82e1 /src/pkg/time
parent80f18fc933cf3f3e829c5455a1023d69f7b86e52 (diff)
downloadgolang-5ff4c17907d5b19510a62e08fd8d3b11e62b431d.tar.gz
Imported Upstream version 60upstream/60
Diffstat (limited to 'src/pkg/time')
-rw-r--r--src/pkg/time/Makefile46
-rw-r--r--src/pkg/time/format.go734
-rw-r--r--src/pkg/time/sleep.go177
-rw-r--r--src/pkg/time/sleep_test.go189
-rw-r--r--src/pkg/time/sys.go51
-rw-r--r--src/pkg/time/sys_plan9.go18
-rw-r--r--src/pkg/time/sys_posix.go18
-rw-r--r--src/pkg/time/tick.go177
-rw-r--r--src/pkg/time/tick_test.go58
-rw-r--r--src/pkg/time/time.go230
-rw-r--r--src/pkg/time/time_test.go506
-rw-r--r--src/pkg/time/zoneinfo_plan9.go59
-rw-r--r--src/pkg/time/zoneinfo_posix.go62
-rw-r--r--src/pkg/time/zoneinfo_unix.go213
-rw-r--r--src/pkg/time/zoneinfo_windows.go192
15 files changed, 2730 insertions, 0 deletions
diff --git a/src/pkg/time/Makefile b/src/pkg/time/Makefile
new file mode 100644
index 000000000..a6fce3fa1
--- /dev/null
+++ b/src/pkg/time/Makefile
@@ -0,0 +1,46 @@
+# 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/format.go b/src/pkg/time/format.go
new file mode 100644
index 000000000..5ddd54812
--- /dev/null
+++ b/src/pkg/time/format.go
@@ -0,0 +1,734 @@
+package time
+
+import (
+ "bytes"
+ "os"
+ "strconv"
+)
+
+const (
+ numeric = iota
+ alphabetic
+ separator
+ plus
+ minus
+)
+
+// 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.
+//
+// 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
+// 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.
+//
+// Numeric time zone offsets format as follows:
+// -0700 ±hhmm
+// -07:00 ±hh:mm
+// Replacing the sign in the format with a Z triggers
+// the ISO 8601 behavior of printing Z instead of an
+// offset for the UTC zone. Thus:
+// Z0700 Z or ±hhmm
+// Z07:00 Z or ±hh:mm
+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"
+ // Handy time stamps.
+ Stamp = "Jan _2 15:04:05"
+ StampMilli = "Jan _2 15:04:05.000"
+ StampMicro = "Jan _2 15:04:05.000000"
+ StampNano = "Jan _2 15:04:05.000000000"
+)
+
+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
+)
+
+// 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) {
+ for i := 0; i < len(layout); i++ {
+ switch layout[i] {
+ 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 {
+ 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 {
+ return layout[0:i], stdWeekDay, layout[i+3:]
+ }
+ if layout[i:i+3] == stdTZ {
+ 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:]
+ }
+
+ case '1': // 15, 1
+ if len(layout) >= i+2 && layout[i+1] == '5' {
+ return layout[0:i], stdHour, layout[i+2:]
+ }
+ return layout[0:i], stdNumMonth, layout[i+1:]
+
+ case '2': // 2006, 2
+ if len(layout) >= i+4 && layout[i:i+4] == stdLongYear {
+ return layout[0:i], stdLongYear, layout[i+4:]
+ }
+ return layout[0:i], stdDay, layout[i+1:]
+
+ case '_': // _2
+ if len(layout) >= i+2 && layout[i+1] == '2' {
+ 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 'P': // PM
+ if len(layout) >= i+2 && layout[i+1] == 'M' {
+ return layout[0:i], layout[i : i+2], 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:]
+ }
+
+ 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+6 && layout[i:i+6] == stdNumColonTZ {
+ return layout[0:i], layout[i : i+6], layout[i+6:]
+ }
+ if len(layout) >= i+3 && layout[i:i+3] == stdNumShortTZ {
+ return layout[0:i], layout[i : i+3], 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+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:]
+ }
+ }
+ }
+ return layout, "", ""
+}
+
+var longDayNames = []string{
+ "Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
+}
+
+var shortDayNames = []string{
+ "Sun",
+ "Mon",
+ "Tue",
+ "Wed",
+ "Thu",
+ "Fri",
+ "Sat",
+}
+
+var shortMonthNames = []string{
+ "---",
+ "Jan",
+ "Feb",
+ "Mar",
+ "Apr",
+ "May",
+ "Jun",
+ "Jul",
+ "Aug",
+ "Sep",
+ "Oct",
+ "Nov",
+ "Dec",
+}
+
+var longMonthNames = []string{
+ "---",
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December",
+}
+
+func lookup(tab []string, val string) (int, string, os.Error) {
+ for i, v := range tab {
+ if len(val) >= len(v) && val[0:len(v)] == v {
+ return i, val[len(v):], nil
+ }
+ }
+ return -1, val, errBad
+}
+
+func pad(i int, padding string) string {
+ s := strconv.Itoa(i)
+ if i < 10 {
+ s = padding + s
+ }
+ return s
+}
+
+func zeroPad(i int) string { return pad(i, "0") }
+
+// formatNano formats a fractional second, as nanoseconds.
+func formatNano(nanosec, n int) 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)
+ // Zero pad left without fmt.
+ if len(s) < 9 {
+ s = "000000000"[:9-len(s)] + s
+ }
+ if n > 9 {
+ n = 9
+ }
+ return "." + s[:n]
+}
+
+// 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)
+ // Each iteration generates one std value.
+ for {
+ prefix, std, suffix := nextStdChunk(layout)
+ b.WriteString(prefix)
+ if std == "" {
+ break
+ }
+ var p string
+ switch std {
+ case stdYear:
+ p = zeroPad(int(t.Year % 100))
+ case stdLongYear:
+ p = strconv.Itoa64(t.Year)
+ case stdMonth:
+ p = shortMonthNames[t.Month]
+ case stdLongMonth:
+ p = longMonthNames[t.Month]
+ case stdNumMonth:
+ p = strconv.Itoa(t.Month)
+ case stdZeroMonth:
+ p = zeroPad(t.Month)
+ case stdWeekDay:
+ p = shortDayNames[t.Weekday]
+ case stdLongWeekDay:
+ p = longDayNames[t.Weekday]
+ case stdDay:
+ p = strconv.Itoa(t.Day)
+ case stdUnderDay:
+ p = pad(t.Day, " ")
+ case stdZeroDay:
+ p = zeroPad(t.Day)
+ case stdHour:
+ p = zeroPad(t.Hour)
+ case stdHour12:
+ // Noon is 12PM, midnight is 12AM.
+ hr := t.Hour % 12
+ if hr == 0 {
+ hr = 12
+ }
+ p = strconv.Itoa(hr)
+ case stdZeroHour12:
+ // Noon is 12PM, midnight is 12AM.
+ hr := t.Hour % 12
+ if hr == 0 {
+ hr = 12
+ }
+ p = zeroPad(hr)
+ case stdMinute:
+ p = strconv.Itoa(t.Minute)
+ case stdZeroMinute:
+ p = zeroPad(t.Minute)
+ case stdSecond:
+ p = strconv.Itoa(t.Second)
+ case stdZeroSecond:
+ p = zeroPad(t.Second)
+ 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' {
+ p = "Z"
+ break
+ }
+ zone := t.ZoneOffset / 60 // convert to minutes
+ if zone < 0 {
+ p = "-"
+ zone = -zone
+ } else {
+ p = "+"
+ }
+ p += zeroPad(zone / 60)
+ if std == stdISO8601ColonTZ || std == stdNumColonTZ {
+ 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
+ } else {
+ // No time zone known for this time, but we must print one.
+ // Use the -0700 format.
+ zone := t.ZoneOffset / 60 // convert to minutes
+ if zone < 0 {
+ p = "-"
+ zone = -zone
+ } else {
+ p = "+"
+ }
+ p += zeroPad(zone / 60)
+ p += zeroPad(zone % 60)
+ }
+ default:
+ if len(std) >= 2 && std[0:2] == ".0" {
+ p = formatNano(t.Nanosecond, len(std)-1)
+ }
+ }
+ b.WriteString(p)
+ layout = suffix
+ }
+ 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
+
+// ParseError describes a problem parsing a time string.
+type ParseError struct {
+ Layout string
+ Value string
+ LayoutElem string
+ ValueElem string
+ Message string
+}
+
+// String is the string representation of a ParseError.
+func (e *ParseError) String() 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)
+ }
+ return "parsing time " +
+ strconv.Quote(e.Value) + e.Message
+}
+
+// isDigit returns true if s[i] is a decimal digit, false if not or
+// if s[i] is out of range.
+func isDigit(s string, i int) bool {
+ if len(s) <= i {
+ return false
+ }
+ c := s[i]
+ return '0' <= c && c <= '9'
+}
+
+// getnum parses s[0:1] or s[0:2] (fixed forces 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) {
+ if !isDigit(s, 0) {
+ return 0, s, errBad
+ }
+ if !isDigit(s, 1) {
+ if fixed {
+ return 0, s, errBad
+ }
+ return int(s[0] - '0'), s[1:], nil
+ }
+ return int(s[0]-'0')*10 + int(s[1]-'0'), s[2:], nil
+}
+
+func cutspace(s string) string {
+ for len(s) > 0 && s[0] == ' ' {
+ s = s[1:]
+ }
+ return s
+}
+
+// skip removes the given prefix from value,
+// treating runs of space characters as equivalent.
+func skip(value, prefix string) (string, os.Error) {
+ for len(prefix) > 0 {
+ if prefix[0] == ' ' {
+ if len(value) > 0 && value[0] != ' ' {
+ return "", errBad
+ }
+ prefix = cutspace(prefix)
+ value = cutspace(value)
+ continue
+ }
+ if len(value) == 0 || value[0] != prefix[0] {
+ return "", errBad
+ }
+ prefix = prefix[1:]
+ value = value[1:]
+ }
+ return value, nil
+}
+
+// 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.
+//
+// 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
+ 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
+ // Each iteration processes one std value.
+ for {
+ var err os.Error
+ prefix, std, suffix := nextStdChunk(layout)
+ value, err = skip(value, prefix)
+ if err != nil {
+ return nil, &ParseError{alayout, avalue, prefix, value, ""}
+ }
+ if len(std) == 0 {
+ if len(value) != 0 {
+ return nil, &ParseError{alayout, avalue, "", value, ": extra text: " + value}
+ }
+ break
+ }
+ layout = suffix
+ var p string
+ switch std {
+ case stdYear:
+ if len(value) < 2 {
+ err = errBad
+ 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
+ } else {
+ t.Year += 2000
+ }
+ case stdLongYear:
+ if len(value) < 4 || !isDigit(value, 0) {
+ err = errBad
+ break
+ }
+ p, value = value[0:4], value[4:]
+ t.Year, err = strconv.Atoi64(p)
+ case stdMonth:
+ t.Month, value, err = lookup(shortMonthNames, value)
+ case stdLongMonth:
+ t.Month, value, err = lookup(longMonthNames, value)
+ case stdNumMonth, stdZeroMonth:
+ t.Month, value, err = getnum(value, std == stdZeroMonth)
+ if t.Month <= 0 || 12 < t.Month {
+ rangeErrString = "month"
+ }
+ case stdWeekDay:
+ t.Weekday, value, err = lookup(shortDayNames, value)
+ case stdLongWeekDay:
+ t.Weekday, 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?
+ rangeErrString = "day"
+ }
+ case stdHour:
+ t.Hour, value, err = getnum(value, false)
+ if t.Hour < 0 || 24 <= t.Hour {
+ rangeErrString = "hour"
+ }
+ case stdHour12, stdZeroHour12:
+ t.Hour, value, err = getnum(value, std == stdZeroHour12)
+ if t.Hour < 0 || 12 < t.Hour {
+ rangeErrString = "hour"
+ }
+ case stdMinute, stdZeroMinute:
+ t.Minute, value, err = getnum(value, std == stdZeroMinute)
+ if t.Minute < 0 || 60 <= t.Minute {
+ rangeErrString = "minute"
+ }
+ case stdSecond, stdZeroSecond:
+ t.Second, value, err = getnum(value, std == stdZeroSecond)
+ if t.Second < 0 || 60 <= t.Second {
+ rangeErrString = "second"
+ }
+ // Special case: do we have a fractional second but no
+ // 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) {
+ // Fractional second in the layout; proceed normally
+ break
+ }
+ // No fractional second in the layout but we have one in the input.
+ n := 2
+ for ; n < len(value) && isDigit(value, n); n++ {
+ }
+ rangeErrString, err = t.parseNanoseconds(value, n)
+ value = value[n:]
+ }
+ case stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ:
+ if std[0] == 'Z' && len(value) >= 1 && value[0] == 'Z' {
+ value = value[1:]
+ t.Zone = "UTC"
+ break
+ }
+ var sign, hh, mm string
+ if std == stdISO8601ColonTZ || std == stdNumColonTZ {
+ if len(value) < 6 {
+ err = errBad
+ break
+ }
+ if value[3] != ':' {
+ err = errBad
+ break
+ }
+ sign, hh, mm, 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:]
+ } else {
+ if len(value) < 5 {
+ err = errBad
+ break
+ }
+ sign, hh, mm, value = value[0:1], value[1:3], value[3:5], value[5:]
+ }
+ var hr, min int
+ hr, err = strconv.Atoi(hh)
+ if err == nil {
+ min, err = strconv.Atoi(mm)
+ }
+ t.ZoneOffset = (hr*60 + min) * 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
+ 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:]
+ break
+ }
+
+ if len(value) >= 3 && value[2] == 'T' {
+ p, value = value[0:3], value[3:]
+ } else if len(value) >= 4 && value[3] == 'T' {
+ p, value = value[0:4], value[4:]
+ } else {
+ err = errBad
+ break
+ }
+ for i := 0; i < len(p); i++ {
+ if p[i] < 'A' || 'Z' < p[i] {
+ err = errBad
+ }
+ }
+ if err != nil {
+ break
+ }
+ // It's a valid format.
+ t.Zone = p
+ // Can we find its offset?
+ if offset, found := lookupByName(p); found {
+ t.ZoneOffset = offset
+ }
+ default:
+ if len(value) < len(std) {
+ err = errBad
+ break
+ }
+ if len(std) >= 2 && std[0:2] == ".0" {
+ rangeErrString, err = t.parseNanoseconds(value, len(std))
+ value = value[len(std):]
+ }
+ }
+ if rangeErrString != "" {
+ return nil, &ParseError{alayout, avalue, std, value, ": " + rangeErrString + " out of range"}
+ }
+ if err != nil {
+ return nil, &ParseError{alayout, avalue, std, value, ""}
+ }
+ }
+ if pmSet && t.Hour < 12 {
+ t.Hour += 12
+ } else if amSet && t.Hour == 12 {
+ t.Hour = 0
+ }
+ return &t, nil
+}
+
+func (t *Time) parseNanoseconds(value string, nbytes int) (rangErrString string, err os.Error) {
+ if value[0] != '.' {
+ return "", errBad
+ }
+ var ns int
+ ns, err = strconv.Atoi(value[1:nbytes])
+ if err != nil {
+ return "", err
+ }
+ if ns < 0 || 1e9 <= ns {
+ return "fractional second", nil
+ }
+ // We need nanoseconds, which means scaling by the number
+ // of missing digits in the format, maximum length 10. If it's
+ // longer than 10, we won't scale.
+ scaleDigits := 10 - nbytes
+ for i := 0; i < scaleDigits; i++ {
+ ns *= 10
+ }
+ t.Nanosecond = ns
+ return
+}
diff --git a/src/pkg/time/sleep.go b/src/pkg/time/sleep.go
new file mode 100644
index 000000000..314622d0d
--- /dev/null
+++ b/src/pkg/time/sleep.go
@@ -0,0 +1,177 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package time
+
+import (
+ "container/heap"
+ "sync"
+)
+
+// 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
+}
+
+// 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
+}
+
+// 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
+}
+
+// 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()
+ })
+}
+
+// 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
+}
+
+// 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)
+ }
+ timerMutex.Unlock()
+ return
+}
+
+// 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]
+ }
+ }
+ 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
+}
+
+func (timerHeap) Push(x interface{}) {
+ e := x.(*Timer)
+ e.i = len(timers)
+ timers = append(timers, e)
+}
+
+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
+}
diff --git a/src/pkg/time/sleep_test.go b/src/pkg/time/sleep_test.go
new file mode 100644
index 000000000..a4a1a429f
--- /dev/null
+++ b/src/pkg/time/sleep_test.go
@@ -0,0 +1,189 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package time_test
+
+import (
+ "fmt"
+ "os"
+ "syscall"
+ "testing"
+ "sort"
+ . "time"
+)
+
+func TestSleep(t *testing.T) {
+ const delay = int64(100e6)
+ go func() {
+ Sleep(delay / 2)
+ syscall.Kill(os.Getpid(), syscall.SIGCHLD)
+ }()
+ start := Nanoseconds()
+ Sleep(delay)
+ duration := Nanoseconds() - start
+ if duration < delay {
+ t.Fatalf("Sleep(%d) slept for only %d ns", delay, duration)
+ }
+}
+
+// Test the basic function calling behavior. Correct queueing
+// behavior is tested elsewhere, since After and AfterFunc share
+// the same code.
+func TestAfterFunc(t *testing.T) {
+ i := 10
+ c := make(chan bool)
+ var f func()
+ f = func() {
+ i--
+ if i >= 0 {
+ AfterFunc(0, f)
+ Sleep(1e9)
+ } else {
+ c <- true
+ }
+ }
+
+ AfterFunc(0, f)
+ <-c
+}
+
+func BenchmarkAfterFunc(b *testing.B) {
+ i := b.N
+ c := make(chan bool)
+ var f func()
+ f = func() {
+ i--
+ if i >= 0 {
+ AfterFunc(0, f)
+ } else {
+ c <- true
+ }
+ }
+
+ AfterFunc(0, f)
+ <-c
+}
+
+func BenchmarkAfter(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ <-After(1)
+ }
+}
+
+func BenchmarkStop(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ NewTimer(1e9).Stop()
+ }
+}
+
+func TestAfter(t *testing.T) {
+ const delay = int64(100e6)
+ start := Nanoseconds()
+ end := <-After(delay)
+ if duration := Nanoseconds() - start; duration < delay {
+ t.Fatalf("After(%d) slept for only %d ns", delay, duration)
+ }
+ if min := start + delay; end < min {
+ t.Fatalf("After(%d) expect >= %d, got %d", delay, min, end)
+ }
+}
+
+func TestAfterTick(t *testing.T) {
+ const (
+ Delta = 100 * 1e6
+ Count = 10
+ )
+ t0 := Nanoseconds()
+ 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))
+ }
+}
+
+func TestAfterStop(t *testing.T) {
+ const msec = 1e6
+ AfterFunc(100*msec, func() {})
+ t0 := NewTimer(50 * msec)
+ c1 := make(chan bool, 1)
+ t1 := AfterFunc(150*msec, func() { c1 <- true })
+ c2 := After(200 * msec)
+ if !t0.Stop() {
+ t.Fatalf("failed to stop event 0")
+ }
+ if !t1.Stop() {
+ t.Fatalf("failed to stop event 1")
+ }
+ <-c2
+ select {
+ case <-t0.C:
+ t.Fatalf("event 0 was not stopped")
+ case <-c1:
+ t.Fatalf("event 1 was not stopped")
+ default:
+ }
+ if t1.Stop() {
+ t.Fatalf("Stop returned true twice")
+ }
+}
+
+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")
+ for i := 0; i < attempts && err != nil; i++ {
+ if err = testAfterQueuing(t); err != nil {
+ t.Logf("attempt %v failed: %v", i, err)
+ }
+ }
+ if err != nil {
+ t.Fatal(err)
+ }
+}
+
+var slots = []int{5, 3, 6, 6, 6, 1, 1, 2, 7, 9, 4, 8, 0}
+
+type afterResult struct {
+ slot int
+ t int64
+}
+
+func await(slot int, result chan<- afterResult, ac <-chan int64) {
+ result <- afterResult{slot, <-ac}
+}
+
+func testAfterQueuing(t *testing.T) os.Error {
+ const (
+ Delta = 100 * 1e6
+ )
+ // 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()
+ for _, slot := range slots {
+ go await(slot, result, After(int64(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)
+ }
+ 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))
+ }
+ }
+ return nil
+}
diff --git a/src/pkg/time/sys.go b/src/pkg/time/sys.go
new file mode 100644
index 000000000..9fde3b3b6
--- /dev/null
+++ b/src/pkg/time/sys.go
@@ -0,0 +1,51 @@
+// 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
new file mode 100644
index 000000000..abe8649a2
--- /dev/null
+++ b/src/pkg/time/sys_plan9.go
@@ -0,0 +1,18 @@
+// 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 {
+ err := syscall.Sleep(t)
+ if err != nil {
+ return os.NewSyscallError("sleep", err)
+ }
+ return nil
+}
diff --git a/src/pkg/time/sys_posix.go b/src/pkg/time/sys_posix.go
new file mode 100644
index 000000000..0d1eb72fc
--- /dev/null
+++ b/src/pkg/time/sys_posix.go
@@ -0,0 +1,18 @@
+// 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/tick.go b/src/pkg/time/tick.go
new file mode 100644
index 000000000..852bae9c9
--- /dev/null
+++ b/src/pkg/time/tick.go
@@ -0,0 +1,177 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package time
+
+import (
+ "os"
+ "sync"
+)
+
+// 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
+}
+
+// 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
+ }
+}
+
+// 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 {
+ 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
+}
diff --git a/src/pkg/time/tick_test.go b/src/pkg/time/tick_test.go
new file mode 100644
index 000000000..4dcb63956
--- /dev/null
+++ b/src/pkg/time/tick_test.go
@@ -0,0 +1,58 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package time_test
+
+import (
+ "testing"
+ . "time"
+)
+
+func TestTicker(t *testing.T) {
+ const (
+ Delta = 100 * 1e6
+ Count = 10
+ )
+ ticker := NewTicker(Delta)
+ t0 := Nanoseconds()
+ for i := 0; i < Count; i++ {
+ <-ticker.C
+ }
+ ticker.Stop()
+ 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))
+ }
+ // Now test that the ticker stopped
+ Sleep(2 * Delta)
+ select {
+ case <-ticker.C:
+ t.Fatal("Ticker did not shut down")
+ default:
+ // ok
+ }
+}
+
+// Test that a bug tearing down a ticker has been fixed. This routine should not deadlock.
+func TestTeardown(t *testing.T) {
+ for i := 0; i < 3; i++ {
+ ticker := NewTicker(1e8)
+ <-ticker.C
+ ticker.Stop()
+ }
+}
+
+func BenchmarkTicker(b *testing.B) {
+ ticker := NewTicker(1)
+ b.ResetTimer()
+ b.StartTimer()
+ for i := 0; i < b.N; i++ {
+ <-ticker.C
+ }
+ b.StopTimer()
+ ticker.Stop()
+}
diff --git a/src/pkg/time/time.go b/src/pkg/time/time.go
new file mode 100644
index 000000000..0e05da484
--- /dev/null
+++ b/src/pkg/time/time.go
@@ -0,0 +1,230 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Package time provides functionality for measuring and displaying time.
+package time
+
+// Days of the week.
+const (
+ Sunday = iota
+ Monday
+ Tuesday
+ Wednesday
+ Thursday
+ Friday
+ 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 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}
+
+func months(year int64) []int {
+ if year%4 == 0 && (year%100 != 0 || year%400 == 0) {
+ return leapyear
+ }
+ return nonleapyear
+}
+
+const (
+ secondsPerDay = 24 * 60 * 60
+ daysPer400Years = 365*400 + 97
+ daysPer100Years = 365*100 + 24
+ daysPer4Years = 365*4 + 1
+ days1970To2001 = 31*365 + 8
+)
+
+// 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)
+
+ // Split into time and day.
+ day := sec / secondsPerDay
+ sec -= day * secondsPerDay
+ if sec < 0 {
+ day--
+ sec += secondsPerDay
+ }
+
+ // Time
+ t.Hour = int(sec / 3600)
+ t.Minute = int((sec / 60) % 60)
+ t.Second = int(sec % 60)
+
+ // Day 0 = January 1, 1970 was a Thursday
+ t.Weekday = int((day + Thursday) % 7)
+ if t.Weekday < 0 {
+ t.Weekday += 7
+ }
+
+ // 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
+
+ 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
+ }
+
+ // Cut off 400 year cycles.
+ n := day / daysPer400Years
+ year += 400 * n
+ day -= daysPer400Years * n
+
+ // Cut off 100-year cycles
+ n = day / daysPer100Years
+ if n > 3 { // happens on last day of 400th year
+ n = 3
+ }
+ 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
+ }
+ 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
+ }
+ year += n
+ day -= 365 * n
+
+ t.Year = year
+
+ // If someone ever needs yearday,
+ // tyearday = day (+1?)
+
+ months := months(year)
+ var m int
+ yday := int(day)
+ for m = 0; m < 12 && yday >= months[m]; m++ {
+ yday -= months[m]
+ }
+ t.Month = m + 1
+ t.Day = yday + 1
+ t.Zone = "UTC"
+
+ return t
+}
+
+// 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)
+ 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
+ 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)
+ return t
+}
+
+// LocalTime returns the current time as a parsed Time value in the local time zone.
+func LocalTime() *Time { return NanosecondsToLocalTime(Nanoseconds()) }
+
+// 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)
+
+ // Rewrite year to be >= 2001.
+ year := t.Year
+ if year < 2001 {
+ n := (2001-year)/400 + 1
+ year += 400 * n
+ day -= daysPer400Years * n
+ }
+
+ // Add in days from 400-year cycles.
+ n := (year - 2001) / 400
+ year -= 400 * n
+ day += daysPer400Years * n
+
+ // Add in 100-year cycles.
+ n = (year - 2001) / 100
+ year -= 100 * n
+ day += daysPer100Years * n
+
+ // Add in 4-year cycles.
+ n = (year - 2001) / 4
+ year -= 4 * n
+ day += daysPer4Years * n
+
+ // Add in non-leap years.
+ n = year - 2001
+ day += 365 * n
+
+ // Add in days this year.
+ months := months(t.Year)
+ for m := 0; m < t.Month-1; m++ {
+ day += int64(months[m])
+ }
+ day += int64(t.Day - 1)
+
+ // Convert days to seconds since January 1, 2001.
+ sec := day * secondsPerDay
+
+ // 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
+}
+
+// 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)
+}
diff --git a/src/pkg/time/time_test.go b/src/pkg/time/time_test.go
new file mode 100644
index 000000000..dceed491a
--- /dev/null
+++ b/src/pkg/time/time_test.go
@@ -0,0 +1,506 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package time_test
+
+import (
+ "os"
+ "strconv"
+ "strings"
+ "testing"
+ "testing/quick"
+ . "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()
+ // 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)
+ t.Error("Likely problem: the time zone files have not been installed.")
+ }
+}
+
+type TimeTest struct {
+ seconds int64
+ golden Time
+}
+
+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"}},
+}
+
+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"}},
+}
+
+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"}},
+}
+
+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
+}
+
+func TestSecondsToUTC(t *testing.T) {
+ for _, test := range utctests {
+ sec := test.seconds
+ golden := &test.golden
+ tm := SecondsToUTC(sec)
+ newsec := tm.Seconds()
+ if newsec != sec {
+ t.Errorf("SecondsToUTC(%d).Seconds() = %d", sec, newsec)
+ }
+ if !same(tm, golden) {
+ t.Errorf("SecondsToUTC(%d):", sec)
+ t.Errorf(" want=%+v", *golden)
+ t.Errorf(" have=%+v", *tm)
+ }
+ }
+}
+
+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()
+ 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)
+ }
+ }
+}
+
+func TestSecondsToLocalTime(t *testing.T) {
+ for _, test := range localtests {
+ sec := test.seconds
+ golden := &test.golden
+ tm := SecondsToLocalTime(sec)
+ newsec := tm.Seconds()
+ 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)
+ }
+ }
+}
+
+func TestNanoecondsToLocalTime(t *testing.T) {
+ for _, test := range nanolocaltests {
+ golden := &test.golden
+ nsec := test.seconds*1e9 + int64(golden.Nanosecond)
+ tm := NanosecondsToLocalTime(nsec)
+ newnsec := tm.Nanoseconds()
+ 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)
+ }
+ }
+}
+
+func TestSecondsToUTCAndBack(t *testing.T) {
+ f := func(sec int64) bool { return SecondsToUTC(sec).Seconds() == sec }
+ f32 := func(sec int32) bool { return f(int64(sec)) }
+ cfg := &quick.Config{MaxCount: 10000}
+
+ // Try a reasonable date first, then the huge ones.
+ if err := quick.Check(f32, cfg); err != nil {
+ t.Fatal(err)
+ }
+ if err := quick.Check(f, cfg); err != nil {
+ t.Fatal(err)
+ }
+}
+
+func TestNanosecondsToUTCAndBack(t *testing.T) {
+ f := func(nsec int64) bool { return NanosecondsToUTC(nsec).Nanoseconds() == nsec }
+ f32 := func(nsec int32) bool { return f(int64(nsec)) }
+ cfg := &quick.Config{MaxCount: 10000}
+
+ // Try a small date first, then the large ones. (The span is only a few hundred years
+ // for nanoseconds in an int64.)
+ if err := quick.Check(f32, cfg); err != nil {
+ t.Fatal(err)
+ }
+ if err := quick.Check(f, cfg); err != nil {
+ t.Fatal(err)
+ }
+}
+
+type TimeFormatTest struct {
+ time Time
+ formattedValue string
+}
+
+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"},
+}
+
+func TestRFC3339Conversion(t *testing.T) {
+ for _, f := range rfc3339Formats {
+ if f.time.Format(RFC3339) != f.formattedValue {
+ t.Error("RFC3339:")
+ t.Errorf(" want=%+v", f.formattedValue)
+ t.Errorf(" have=%+v", f.time.Format(RFC3339))
+ }
+ }
+}
+
+type FormatTest struct {
+ name string
+ format string
+ result string
+}
+
+var formatTests = []FormatTest{
+ {"ANSIC", ANSIC, "Wed Feb 4 21:00:57 2009"},
+ {"UnixDate", UnixDate, "Wed Feb 4 21:00:57 PST 2009"},
+ {"RubyDate", RubyDate, "Wed Feb 04 21:00:57 -0800 2009"},
+ {"RFC822", RFC822, "04 Feb 09 2100 PST"},
+ {"RFC850", RFC850, "Wednesday, 04-Feb-09 21:00:57 PST"},
+ {"RFC1123", RFC1123, "Wed, 04 Feb 2009 21:00:57 PST"},
+ {"RFC3339", RFC3339, "2009-02-04T21:00:57-08:00"},
+ {"Kitchen", Kitchen, "9:00PM"},
+ {"am/pm", "3pm", "9pm"},
+ {"AM/PM", "3PM", "9PM"},
+ {"two-digit year", "06 01 02", "09 02 04"},
+ // Time stamps, Fractional seconds.
+ {"Stamp", Stamp, "Feb 4 21:00:57"},
+ {"StampMilli", StampMilli, "Feb 4 21:00:57.012"},
+ {"StampMicro", StampMicro, "Feb 4 21:00:57.012345"},
+ {"StampNano", StampNano, "Feb 4 21:00:57.012345678"},
+}
+
+func TestFormat(t *testing.T) {
+ // The numeric time represents Thu Feb 4 21:00:57.012345678 PST 2010
+ time := NanosecondsToLocalTime(1233810057012345678)
+ for _, test := range formatTests {
+ result := time.Format(test.format)
+ if result != test.result {
+ t.Errorf("%s expected %q got %q", test.name, test.result, result)
+ }
+ }
+}
+
+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
+}
+
+var parseTests = []ParseTest{
+ {"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1, 0},
+ {"UnixDate", UnixDate, "Thu Feb 4 21:00:57 PST 2010", true, true, 1, 0},
+ {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0800 2010", true, true, 1, 0},
+ {"RFC850", RFC850, "Thursday, 04-Feb-10 21:00:57 PST", true, true, 1, 0},
+ {"RFC1123", RFC1123, "Thu, 04 Feb 2010 21:00:57 PST", true, true, 1, 0},
+ {"RFC3339", RFC3339, "2010-02-04T21:00:57-08:00", true, false, 1, 0},
+ {"custom: \"2006-01-02 15:04:05-07\"", "2006-01-02 15:04:05-07", "2010-02-04 21:00:57-08", true, false, 1, 0},
+ // Optional fractional seconds.
+ {"ANSIC", ANSIC, "Thu Feb 4 21:00:57.0 2010", false, true, 1, 1},
+ {"UnixDate", UnixDate, "Thu Feb 4 21:00:57.01 PST 2010", true, true, 1, 2},
+ {"RubyDate", RubyDate, "Thu Feb 04 21:00:57.012 -0800 2010", true, true, 1, 3},
+ {"RFC850", RFC850, "Thursday, 04-Feb-10 21:00:57.0123 PST", true, true, 1, 4},
+ {"RFC1123", RFC1123, "Thu, 04 Feb 2010 21:00:57.01234 PST", true, true, 1, 5},
+ {"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},
+ // 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},
+ {"nanosecond", "Mon Jan _2 15:04:05.000000000 2006", "Thu Feb 4 21:00:57.012345678 2010", false, true, 1, 9},
+ // Leading zeros in other places should not be taken as fractional seconds.
+ {"zero1", "2006.01.02.15.04.05.0", "2010.02.04.21.00.57.0", false, false, 1, 1},
+ {"zero2", "2006.01.02.15.04.05.00", "2010.02.04.21.00.57.01", false, false, 1, 2},
+}
+
+func TestParse(t *testing.T) {
+ for _, test := range parseTests {
+ time, err := Parse(test.format, test.value)
+ if err != nil {
+ t.Errorf("%s error: %v", test.name, err)
+ } else {
+ checkTime(time, &test, t)
+ }
+ }
+}
+
+var rubyTests = []ParseTest{
+ {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0800 2010", true, true, 1, 0},
+ // Ignore the time zone in the test. If it parses, it'll be OK.
+ {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0000 2010", false, true, 1, 0},
+ {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 +0000 2010", false, true, 1, 0},
+ {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 +1130 2010", false, true, 1, 0},
+}
+
+// Problematic time zone format needs special tests.
+func TestRubyParse(t *testing.T) {
+ for _, test := range rubyTests {
+ time, err := Parse(test.format, test.value)
+ if err != nil {
+ t.Errorf("%s error: %v", test.name, err)
+ } else {
+ checkTime(time, &test, t)
+ }
+ }
+}
+
+func checkTime(time *Time, test *ParseTest, t *testing.T) {
+ // The time should be Thu Feb 4 21:00:57 PST 2010
+ if test.yearSign*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.Day != 4 {
+ t.Errorf("%s: bad day: %d not %d", test.name, time.Day, 4)
+ }
+ if time.Hour != 21 {
+ t.Errorf("%s: bad hour: %d not %d", test.name, time.Hour, 21)
+ }
+ if time.Minute != 0 {
+ t.Errorf("%s: bad minute: %d not %d", test.name, time.Minute, 0)
+ }
+ if time.Second != 57 {
+ t.Errorf("%s: bad second: %d not %d", test.name, time.Second, 57)
+ }
+ // Nanoseconds must be checked against the precision of the input.
+ nanosec, err := strconv.Atoui("012345678"[:test.fracDigits] + "000000000"[:9-test.fracDigits])
+ if err != nil {
+ panic(err)
+ }
+ 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)
+ }
+ if test.hasWD && time.Weekday != 4 {
+ t.Errorf("%s: bad weekday: %d not %d", test.name, time.Weekday, 4)
+ }
+}
+
+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 {
+ // not required to work
+ return true
+ }
+ t2, err := Parse(fmt, t1.Format(fmt))
+ if err != nil {
+ t.Errorf("error: %s", err)
+ return false
+ }
+ if !same(t1, t2) {
+ t.Errorf("different: %q %q", t1, t2)
+ return false
+ }
+ return true
+ }
+ f32 := func(sec int32) bool { return f(int64(sec)) }
+ cfg := &quick.Config{MaxCount: 10000}
+
+ // Try a reasonable date first, then the huge ones.
+ if err := quick.Check(f32, cfg); err != nil {
+ t.Fatal(err)
+ }
+ if err := quick.Check(f, cfg); err != nil {
+ t.Fatal(err)
+ }
+}
+
+type ParseErrorTest struct {
+ format string
+ value string
+ expect string // must appear within the error
+}
+
+var parseErrorTests = []ParseErrorTest{
+ {ANSIC, "Feb 4 21:00:60 2010", "cannot parse"}, // cannot parse Feb as Mon
+ {ANSIC, "Thu Feb 4 21:00:57 @2010", "cannot parse"},
+ {ANSIC, "Thu Feb 4 21:00:60 2010", "second out of range"},
+ {ANSIC, "Thu Feb 4 21:61:57 2010", "minute out of range"},
+ {ANSIC, "Thu Feb 4 24:00:60 2010", "hour out of range"},
+ {"Mon Jan _2 15:04:05.000 2006", "Thu Feb 4 23:00:59x01 2010", "cannot parse"},
+ {"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"},
+}
+
+func TestParseErrors(t *testing.T) {
+ for _, test := range parseErrorTests {
+ _, err := Parse(test.format, test.value)
+ if err == nil {
+ t.Errorf("expected error for %q %q", test.format, test.value)
+ } else if strings.Index(err.String(), 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}
+ const expect = "12:00PM"
+ got := noon.Format("3:04PM")
+ if got != expect {
+ t.Errorf("got %q; expect %q", got, expect)
+ }
+ got = noon.Format("03:04PM")
+ if got != expect {
+ t.Errorf("got %q; expect %q", got, expect)
+ }
+}
+
+func TestMidnightIs12AM(t *testing.T) {
+ midnight := Time{Hour: 0}
+ expect := "12:00AM"
+ got := midnight.Format("3:04PM")
+ if got != expect {
+ t.Errorf("got %q; expect %q", got, expect)
+ }
+ got = midnight.Format("03:04PM")
+ if got != expect {
+ t.Errorf("got %q; expect %q", got, expect)
+ }
+}
+
+func Test12PMIsNoon(t *testing.T) {
+ noon, err := Parse("3:04PM", "12:00PM")
+ if err != nil {
+ t.Fatal("error parsing date:", err)
+ }
+ if noon.Hour != 12 {
+ t.Errorf("got %d; expect 12", noon.Hour)
+ }
+ noon, err = Parse("03:04PM", "12:00PM")
+ if err != nil {
+ t.Fatal("error parsing date:", err)
+ }
+ if noon.Hour != 12 {
+ t.Errorf("got %d; expect 12", noon.Hour)
+ }
+}
+
+func Test12AMIsMidnight(t *testing.T) {
+ midnight, err := Parse("3:04PM", "12:00AM")
+ if err != nil {
+ t.Fatal("error parsing date:", err)
+ }
+ if midnight.Hour != 0 {
+ t.Errorf("got %d; expect 0", midnight.Hour)
+ }
+ midnight, err = Parse("03:04PM", "12:00AM")
+ if err != nil {
+ t.Fatal("error parsing date:", err)
+ }
+ if midnight.Hour != 0 {
+ t.Errorf("got %d; expect 0", midnight.Hour)
+ }
+}
+
+// Check that a time without a Zone still produces a (numeric) time zone
+// when formatted with MST as a requested zone.
+func TestMissingZone(t *testing.T) {
+ time, err := Parse(RubyDate, "Tue 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
+ str := time.Format(UnixDate) // uses MST as its time zone
+ if str != expect {
+ t.Errorf("expected %q got %q", expect, str)
+ }
+}
+
+func TestMinutesInTimeZone(t *testing.T) {
+ time, err := Parse(RubyDate, "Mon Jan 02 15:04:05 +0123 2006")
+ if err != nil {
+ t.Fatal("error parsing date:", err)
+ }
+ expected := (1*60 + 23) * 60
+ if time.ZoneOffset != expected {
+ t.Errorf("ZoneOffset incorrect, expected %d got %d", expected, time.ZoneOffset)
+ }
+}
+
+func BenchmarkSeconds(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ Seconds()
+ }
+}
+
+func BenchmarkNanoseconds(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ Nanoseconds()
+ }
+}
+
+func BenchmarkFormat(b *testing.B) {
+ time := SecondsToLocalTime(1265346057)
+ for i := 0; i < b.N; i++ {
+ time.Format("Mon Jan 2 15:04:05 2006")
+ }
+}
+
+func BenchmarkParse(b *testing.B) {
+ for i := 0; i < b.N; i++ {
+ Parse(ANSIC, "Mon Jan 2 15:04:05 2006")
+ }
+}
diff --git a/src/pkg/time/zoneinfo_plan9.go b/src/pkg/time/zoneinfo_plan9.go
new file mode 100644
index 000000000..3c3e7c424
--- /dev/null
+++ b/src/pkg/time/zoneinfo_plan9.go
@@ -0,0 +1,59 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Parse Plan 9 timezone(2) files.
+
+package time
+
+import (
+ "os"
+ "strconv"
+ "strings"
+)
+
+func parseZones(s string) (zt []zonetime) {
+ f := strings.Fields(s)
+ if len(f) < 4 {
+ return
+ }
+
+ // standard timezone offset
+ o, err := strconv.Atoi(f[1])
+ if err != nil {
+ return
+ }
+ std := &zone{name: f[0], utcoff: o, isdst: false}
+
+ // alternate timezone offset
+ o, err = strconv.Atoi(f[3])
+ if err != nil {
+ return
+ }
+ dst := &zone{name: f[2], utcoff: o, isdst: true}
+
+ // transition time pairs
+ f = f[4:]
+ for i := 0; i < len(f); i++ {
+ z := std
+ if i%2 == 0 {
+ z = dst
+ }
+ t, err := strconv.Atoi(f[i])
+ if err != nil {
+ return nil
+ }
+ t -= std.utcoff
+ zt = append(zt, zonetime{time: int32(t), zone: z})
+ }
+ return
+}
+
+func setupZone() {
+ t, err := os.Getenverror("timezone")
+ if err != nil {
+ // do nothing: use UTC
+ return
+ }
+ zones = parseZones(t)
+}
diff --git a/src/pkg/time/zoneinfo_posix.go b/src/pkg/time/zoneinfo_posix.go
new file mode 100644
index 000000000..b49216410
--- /dev/null
+++ b/src/pkg/time/zoneinfo_posix.go
@@ -0,0 +1,62 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+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_unix.go b/src/pkg/time/zoneinfo_unix.go
new file mode 100644
index 000000000..f3ea7b6fd
--- /dev/null
+++ b/src/pkg/time/zoneinfo_unix.go
@@ -0,0 +1,213 @@
+// 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 (
+ "io/ioutil"
+ "os"
+)
+
+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)
+}
+
+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:])
+ }
+
+ // 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
+ }
+ if i < len(isutc) {
+ zt[i].isutc = isutc[i] != 0
+ }
+ }
+ return zt, true
+}
+
+func readinfofile(name string) ([]zonetime, bool) {
+ buf, err := ioutil.ReadFile(name)
+ if err != nil {
+ return nil, false
+ }
+ return parseinfo(buf)
+}
+
+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
+ }
+ }
+ case len(tz) == 0:
+ // do nothing: use UTC
+ }
+}
diff --git a/src/pkg/time/zoneinfo_windows.go b/src/pkg/time/zoneinfo_windows.go
new file mode 100644
index 000000000..fabc00601
--- /dev/null
+++ b/src/pkg/time/zoneinfo_windows.go
@@ -0,0 +1,192 @@
+// 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 (
+ "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
+}
+
+// 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
+}
+
+// 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)
+ }
+}
+
+// Convert zone cutoff time to sec in number of seconds since the Unix epoch, given particular year.
+func (z *zone) cutoffSeconds(year int64) 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
+ if i < 0 {
+ i += 7
+ }
+ t.Day += i
+ if week := int(z.day) - 1; week < 4 {
+ t.Day += week * 7
+ } else {
+ // "Last" instance of the day.
+ t.Day += 4 * 7
+ if t.Day > months(year)[t.Month] {
+ t.Day -= 7
+ }
+ }
+ // Result is in "local" time. Adjust it for "utc".
+ return t.Seconds() - int64(z.prev.offset)
+}
+
+// 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
+}
+
+type zoneinfo struct {
+ disabled bool // daylight saving time is not used locally
+ offsetIfDisabled int
+ januaryIsStd bool // is january 1 standard time?
+ std, dst zone
+}
+
+// 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
+ }
+ }
+ return z
+}
+
+var tz zoneinfo
+var initError os.Error
+var onceSetupZone sync.Once
+
+func setupZone() {
+ var i syscall.Timezoneinformation
+ if _, e := syscall.GetTimeZoneInformation(&i); e != 0 {
+ initError = os.NewSyscallError("GetTimeZoneInformation", e)
+ return
+ }
+ if !tz.std.populate(i.Bias, i.StandardBias, &i.StandardDate, i.StandardName[0:]) {
+ tz.disabled = true
+ tz.offsetIfDisabled = tz.std.offset
+ return
+ }
+ 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
+}
+
+// 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
+ }
+ switch name {
+ case tz.std.name:
+ return tz.std.offset, true
+ case tz.dst.name:
+ return tz.dst.offset, true
+ }
+ return 0, false
+}