diff options
author | Ondřej Surý <ondrej@sury.org> | 2011-09-13 13:13:40 +0200 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2011-09-13 13:13:40 +0200 |
commit | 5ff4c17907d5b19510a62e08fd8d3b11e62b431d (patch) | |
tree | c0650497e988f47be9c6f2324fa692a52dea82e1 /src/pkg/time | |
parent | 80f18fc933cf3f3e829c5455a1023d69f7b86e52 (diff) | |
download | golang-5ff4c17907d5b19510a62e08fd8d3b11e62b431d.tar.gz |
Imported Upstream version 60upstream/60
Diffstat (limited to 'src/pkg/time')
-rw-r--r-- | src/pkg/time/Makefile | 46 | ||||
-rw-r--r-- | src/pkg/time/format.go | 734 | ||||
-rw-r--r-- | src/pkg/time/sleep.go | 177 | ||||
-rw-r--r-- | src/pkg/time/sleep_test.go | 189 | ||||
-rw-r--r-- | src/pkg/time/sys.go | 51 | ||||
-rw-r--r-- | src/pkg/time/sys_plan9.go | 18 | ||||
-rw-r--r-- | src/pkg/time/sys_posix.go | 18 | ||||
-rw-r--r-- | src/pkg/time/tick.go | 177 | ||||
-rw-r--r-- | src/pkg/time/tick_test.go | 58 | ||||
-rw-r--r-- | src/pkg/time/time.go | 230 | ||||
-rw-r--r-- | src/pkg/time/time_test.go | 506 | ||||
-rw-r--r-- | src/pkg/time/zoneinfo_plan9.go | 59 | ||||
-rw-r--r-- | src/pkg/time/zoneinfo_posix.go | 62 | ||||
-rw-r--r-- | src/pkg/time/zoneinfo_unix.go | 213 | ||||
-rw-r--r-- | src/pkg/time/zoneinfo_windows.go | 192 |
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 +} |