summaryrefslogtreecommitdiff
path: root/src/pkg/time
diff options
context:
space:
mode:
Diffstat (limited to 'src/pkg/time')
-rw-r--r--src/pkg/time/Makefile46
-rw-r--r--src/pkg/time/example_test.go58
-rw-r--r--src/pkg/time/format.go712
-rw-r--r--src/pkg/time/internal_test.go13
-rw-r--r--src/pkg/time/sleep.go212
-rw-r--r--src/pkg/time/sleep_test.go130
-rw-r--r--src/pkg/time/sys.go51
-rw-r--r--src/pkg/time/sys_plan9.go66
-rw-r--r--src/pkg/time/sys_posix.go18
-rw-r--r--src/pkg/time/sys_unix.go76
-rw-r--r--src/pkg/time/sys_windows.go73
-rw-r--r--src/pkg/time/tick.go188
-rw-r--r--src/pkg/time/tick_test.go24
-rw-r--r--src/pkg/time/time.go1069
-rw-r--r--src/pkg/time/time_test.go695
-rw-r--r--src/pkg/time/zoneinfo.go204
-rw-r--r--src/pkg/time/zoneinfo_plan9.go145
-rw-r--r--src/pkg/time/zoneinfo_posix.go62
-rw-r--r--src/pkg/time/zoneinfo_read.go341
-rw-r--r--src/pkg/time/zoneinfo_unix.go227
-rw-r--r--src/pkg/time/zoneinfo_windows.go275
21 files changed, 3309 insertions, 1376 deletions
diff --git a/src/pkg/time/Makefile b/src/pkg/time/Makefile
deleted file mode 100644
index a6fce3fa1..000000000
--- a/src/pkg/time/Makefile
+++ /dev/null
@@ -1,46 +0,0 @@
-# Copyright 2009 The Go Authors. All rights reserved.
-# Use of this source code is governed by a BSD-style
-# license that can be found in the LICENSE file.
-
-include ../../Make.inc
-
-TARG=time
-GOFILES=\
- format.go\
- sleep.go\
- sys.go\
- tick.go\
- time.go\
-
-GOFILES_freebsd=\
- sys_posix.go\
- zoneinfo_posix.go\
- zoneinfo_unix.go\
-
-GOFILES_darwin=\
- sys_posix.go\
- zoneinfo_posix.go\
- zoneinfo_unix.go\
-
-GOFILES_linux=\
- sys_posix.go\
- zoneinfo_posix.go\
- zoneinfo_unix.go\
-
-GOFILES_openbsd=\
- sys_posix.go\
- zoneinfo_posix.go\
- zoneinfo_unix.go\
-
-GOFILES_windows=\
- sys_posix.go\
- zoneinfo_windows.go\
-
-GOFILES_plan9=\
- sys_plan9.go\
- zoneinfo_posix.go\
- zoneinfo_plan9.go\
-
-GOFILES+=$(GOFILES_$(GOOS))
-
-include ../../Make.pkg
diff --git a/src/pkg/time/example_test.go b/src/pkg/time/example_test.go
new file mode 100644
index 000000000..944cc789c
--- /dev/null
+++ b/src/pkg/time/example_test.go
@@ -0,0 +1,58 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package time_test
+
+import (
+ "fmt"
+ "time"
+)
+
+func expensiveCall() {}
+
+func ExampleDuration() {
+ t0 := time.Now()
+ expensiveCall()
+ t1 := time.Now()
+ fmt.Printf("The call took %v to run.\n", t1.Sub(t0))
+}
+
+var c chan int
+
+func handle(int) {}
+
+func ExampleAfter() {
+ select {
+ case m := <-c:
+ handle(m)
+ case <-time.After(5 * time.Minute):
+ fmt.Println("timed out")
+ }
+}
+
+func ExampleSleep() {
+ time.Sleep(100 * time.Millisecond)
+}
+
+func statusUpdate() string { return "" }
+
+func ExampleTick() {
+ c := time.Tick(1 * time.Minute)
+ for now := range c {
+ fmt.Printf("%v %s\n", now, statusUpdate())
+ }
+}
+
+func ExampleMonth() {
+ _, month, day := time.Now().Date()
+ if month == time.November && day == 10 {
+ fmt.Println("Happy Go day!")
+ }
+}
+
+func ExampleDate() {
+ t := time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)
+ fmt.Printf("Go launched at %s\n", t.Local())
+ // Output: Go launched at 2009-11-10 15:00:00 -0800 PST
+}
diff --git a/src/pkg/time/format.go b/src/pkg/time/format.go
index 5ddd54812..ad52bab21 100644
--- a/src/pkg/time/format.go
+++ b/src/pkg/time/format.go
@@ -1,33 +1,30 @@
-package time
+// Copyright 2010 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
-import (
- "bytes"
- "os"
- "strconv"
-)
+package time
-const (
- numeric = iota
- alphabetic
- separator
- plus
- minus
-)
+import "errors"
// These are predefined layouts for use in Time.Format.
// The standard time used in the layouts is:
-// Mon Jan 2 15:04:05 MST 2006 (MST is GMT-0700)
-// which is Unix time 1136243045.
-// (Think of it as 01/02 03:04:05PM '06 -0700.)
-// To define your own format, write down what the standard
-// time would look like formatted your way.
+// Mon Jan 2 15:04:05 MST 2006
+// which is Unix time 1136243045. Since MST is GMT-0700,
+// the standard time can be thought of as
+// 01/02 03:04:05PM '06 -0700
+// To define your own format, write down what the standard time would look
+// like formatted your way; see the values of constants like ANSIC,
+// StampMicro or Kitchen for examples.
//
// Within the format string, an underscore _ represents a space that may be
// replaced by a digit if the following number (a day) has two digits; for
// compatibility with fixed-width Unix time formats.
//
// A decimal point followed by one or more zeros represents a fractional
-// second. When parsing (only), the input may contain a fractional second
+// second, printed to the given number of decimal places. A decimal point
+// followed by one or more nines represents a fractional second, printed to
+// the given number of decimal places, with trailing zeros removed.
+// When parsing (only), the input may contain a fractional second
// field immediately after the seconds field, even if the layout does not
// signify its presence. In that case a decimal point followed by a maximal
// series of digits is parsed as a fractional second.
@@ -41,16 +38,17 @@ const (
// Z0700 Z or ±hhmm
// Z07:00 Z or ±hh:mm
const (
- ANSIC = "Mon Jan _2 15:04:05 2006"
- UnixDate = "Mon Jan _2 15:04:05 MST 2006"
- RubyDate = "Mon Jan 02 15:04:05 -0700 2006"
- RFC822 = "02 Jan 06 1504 MST"
- // RFC822 with Zulu time.
- RFC822Z = "02 Jan 06 1504 -0700"
- RFC850 = "Monday, 02-Jan-06 15:04:05 MST"
- RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
- RFC3339 = "2006-01-02T15:04:05Z07:00"
- Kitchen = "3:04PM"
+ ANSIC = "Mon Jan _2 15:04:05 2006"
+ UnixDate = "Mon Jan _2 15:04:05 MST 2006"
+ RubyDate = "Mon Jan 02 15:04:05 -0700 2006"
+ RFC822 = "02 Jan 06 1504 MST"
+ RFC822Z = "02 Jan 06 1504 -0700" // RFC822 with numeric zone
+ RFC850 = "Monday, 02-Jan-06 15:04:05 MST"
+ RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST"
+ RFC1123Z = "Mon, 02 Jan 2006 15:04:05 -0700" // RFC1123 with numeric zone
+ RFC3339 = "2006-01-02T15:04:05Z07:00"
+ RFC3339Nano = "2006-01-02T15:04:05.999999999Z07:00"
+ Kitchen = "3:04PM"
// Handy time stamps.
Stamp = "Jan _2 15:04:05"
StampMilli = "Jan _2 15:04:05.000"
@@ -165,15 +163,17 @@ func nextStdChunk(layout string) (prefix, std, suffix string) {
if len(layout) >= i+6 && layout[i:i+6] == stdISO8601ColonTZ {
return layout[0:i], layout[i : i+6], layout[i+6:]
}
- case '.': // .000 - multiple digits of zeros (only) for fractional seconds.
- numZeros := 0
- var j int
- for j = i + 1; j < len(layout) && layout[j] == '0'; j++ {
- numZeros++
- }
- // String of digits must end here - only fractional second is all zeros.
- if numZeros > 0 && !isDigit(layout, j) {
- return layout[0:i], layout[i : i+1+numZeros], layout[i+1+numZeros:]
+ case '.': // .000 or .999 - repeated digits for fractional seconds.
+ if i+1 < len(layout) && (layout[i+1] == '0' || layout[i+1] == '9') {
+ ch := layout[i+1]
+ j := i + 1
+ for j < len(layout) && layout[j] == ch {
+ j++
+ }
+ // String of digits must end here - only fractional second is all digits.
+ if !isDigit(layout, j) {
+ return layout[0:i], layout[i:j], layout[j:]
+ }
}
}
}
@@ -232,17 +232,78 @@ var longMonthNames = []string{
"December",
}
-func lookup(tab []string, val string) (int, string, os.Error) {
+// match returns true if s1 and s2 match ignoring case.
+// It is assumed s1 and s2 are the same length.
+func match(s1, s2 string) bool {
+ for i := 0; i < len(s1); i++ {
+ c1 := s1[i]
+ c2 := s2[i]
+ if c1 != c2 {
+ // Switch to lower-case; 'a'-'A' is known to be a single bit.
+ c1 |= 'a' - 'A'
+ c2 |= 'a' - 'A'
+ if c1 != c2 || c1 < 'a' || c1 > 'z' {
+ return false
+ }
+ }
+ }
+ return true
+}
+
+func lookup(tab []string, val string) (int, string, error) {
for i, v := range tab {
- if len(val) >= len(v) && val[0:len(v)] == v {
+ if len(val) >= len(v) && match(val[0:len(v)], v) {
return i, val[len(v):], nil
}
}
return -1, val, errBad
}
+// Duplicates functionality in strconv, but avoids dependency.
+func itoa(x int) string {
+ var buf [32]byte
+ n := len(buf)
+ if x == 0 {
+ return "0"
+ }
+ u := uint(x)
+ if x < 0 {
+ u = -u
+ }
+ for u > 0 {
+ n--
+ buf[n] = byte(u%10 + '0')
+ u /= 10
+ }
+ if x < 0 {
+ n--
+ buf[n] = '-'
+ }
+ return string(buf[n:])
+}
+
+// Never printed, just needs to be non-nil for return by atoi.
+var atoiError = errors.New("time: invalid number")
+
+// Duplicates functionality in strconv, but avoids dependency.
+func atoi(s string) (x int, err error) {
+ neg := false
+ if s != "" && s[0] == '-' {
+ neg = true
+ s = s[1:]
+ }
+ x, rem, err := leadingInt(s)
+ if err != nil || rem != "" {
+ return 0, atoiError
+ }
+ if neg {
+ x = -x
+ }
+ return x, nil
+}
+
func pad(i int, padding string) string {
- s := strconv.Itoa(i)
+ s := itoa(i)
if i < 10 {
s = padding + s
}
@@ -252,10 +313,10 @@ func pad(i int, padding string) string {
func zeroPad(i int) string { return pad(i, "0") }
// formatNano formats a fractional second, as nanoseconds.
-func formatNano(nanosec, n int) string {
+func formatNano(nanosec, n int, trim bool) string {
// User might give us bad data. Make sure it's positive and in range.
// They'll get nonsense output but it will have the right format.
- s := strconv.Uitoa(uint(nanosec) % 1e9)
+ s := itoa(int(uint(nanosec) % 1e9))
// Zero pad left without fmt.
if len(s) < 9 {
s = "000000000"[:9-len(s)] + s
@@ -263,17 +324,51 @@ func formatNano(nanosec, n int) string {
if n > 9 {
n = 9
}
+ if trim {
+ for n > 0 && s[n-1] == '0' {
+ n--
+ }
+ if n == 0 {
+ return ""
+ }
+ }
return "." + s[:n]
}
+// String returns the time formatted using the format string
+// "2006-01-02 15:04:05.999999999 -0700 MST"
+func (t Time) String() string {
+ return t.Format("2006-01-02 15:04:05.999999999 -0700 MST")
+}
+
+type buffer []byte
+
+func (b *buffer) WriteString(s string) {
+ *b = append(*b, s...)
+}
+
+func (b *buffer) String() string {
+ return string([]byte(*b))
+}
+
// Format returns a textual representation of the time value formatted
// according to layout. The layout defines the format by showing the
-// representation of a standard time, which is then used to describe
-// the time to be formatted. Predefined layouts ANSIC, UnixDate,
-// RFC3339 and others describe standard representations. For more
-// information about the formats, see the documentation for ANSIC.
-func (t *Time) Format(layout string) string {
- b := new(bytes.Buffer)
+// representation of the standard time,
+// Mon Jan 2 15:04:05 -0700 MST 2006
+// which is then used to describe the time to be formatted. Predefined
+// layouts ANSIC, UnixDate, RFC3339 and others describe standard
+// representations. For more information about the formats and the
+// definition of the standard time, see the documentation for ANSIC.
+func (t Time) Format(layout string) string {
+ var (
+ year int = -1
+ month Month
+ day int
+ hour int = -1
+ min int
+ sec int
+ b buffer
+ )
// Each iteration generates one std value.
for {
prefix, std, suffix := nextStdChunk(layout)
@@ -281,62 +376,109 @@ func (t *Time) Format(layout string) string {
if std == "" {
break
}
+
+ // Compute year, month, day if needed.
+ if year < 0 {
+ // Jan 01 02 2006
+ if a, z := std[0], std[len(std)-1]; a == 'J' || a == 'j' || z == '1' || z == '2' || z == '6' {
+ year, month, day = t.Date()
+ }
+ }
+
+ // Compute hour, minute, second if needed.
+ if hour < 0 {
+ // 03 04 05 15 pm
+ if z := std[len(std)-1]; z == '3' || z == '4' || z == '5' || z == 'm' || z == 'M' {
+ hour, min, sec = t.Clock()
+ }
+ }
+
var p string
switch std {
case stdYear:
- p = zeroPad(int(t.Year % 100))
+ p = zeroPad(year % 100)
case stdLongYear:
- p = strconv.Itoa64(t.Year)
+ // Pad year to at least 4 digits.
+ p = itoa(year)
+ switch {
+ case year <= -1000:
+ // ok
+ case year <= -100:
+ p = p[:1] + "0" + p[1:]
+ case year <= -10:
+ p = p[:1] + "00" + p[1:]
+ case year < 0:
+ p = p[:1] + "000" + p[1:]
+ case year < 10:
+ p = "000" + p
+ case year < 100:
+ p = "00" + p
+ case year < 1000:
+ p = "0" + p
+ }
case stdMonth:
- p = shortMonthNames[t.Month]
+ p = month.String()[:3]
case stdLongMonth:
- p = longMonthNames[t.Month]
+ p = month.String()
case stdNumMonth:
- p = strconv.Itoa(t.Month)
+ p = itoa(int(month))
case stdZeroMonth:
- p = zeroPad(t.Month)
+ p = zeroPad(int(month))
case stdWeekDay:
- p = shortDayNames[t.Weekday]
+ p = t.Weekday().String()[:3]
case stdLongWeekDay:
- p = longDayNames[t.Weekday]
+ p = t.Weekday().String()
case stdDay:
- p = strconv.Itoa(t.Day)
+ p = itoa(day)
case stdUnderDay:
- p = pad(t.Day, " ")
+ p = pad(day, " ")
case stdZeroDay:
- p = zeroPad(t.Day)
+ p = zeroPad(day)
case stdHour:
- p = zeroPad(t.Hour)
+ p = zeroPad(hour)
case stdHour12:
// Noon is 12PM, midnight is 12AM.
- hr := t.Hour % 12
+ hr := hour % 12
if hr == 0 {
hr = 12
}
- p = strconv.Itoa(hr)
+ p = itoa(hr)
case stdZeroHour12:
// Noon is 12PM, midnight is 12AM.
- hr := t.Hour % 12
+ hr := hour % 12
if hr == 0 {
hr = 12
}
p = zeroPad(hr)
case stdMinute:
- p = strconv.Itoa(t.Minute)
+ p = itoa(min)
case stdZeroMinute:
- p = zeroPad(t.Minute)
+ p = zeroPad(min)
case stdSecond:
- p = strconv.Itoa(t.Second)
+ p = itoa(sec)
case stdZeroSecond:
- p = zeroPad(t.Second)
+ p = zeroPad(sec)
+ case stdPM:
+ if hour >= 12 {
+ p = "PM"
+ } else {
+ p = "AM"
+ }
+ case stdpm:
+ if hour >= 12 {
+ p = "pm"
+ } else {
+ p = "am"
+ }
case stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumColonTZ:
// Ugly special case. We cheat and take the "Z" variants
// to mean "the time zone as formatted for ISO 8601".
- if t.ZoneOffset == 0 && std[0] == 'Z' {
+ _, offset := t.Zone()
+ if offset == 0 && std[0] == 'Z' {
p = "Z"
break
}
- zone := t.ZoneOffset / 60 // convert to minutes
+ zone := offset / 60 // convert to minutes
if zone < 0 {
p = "-"
zone = -zone
@@ -348,25 +490,14 @@ func (t *Time) Format(layout string) string {
p += ":"
}
p += zeroPad(zone % 60)
- case stdPM:
- if t.Hour >= 12 {
- p = "PM"
- } else {
- p = "AM"
- }
- case stdpm:
- if t.Hour >= 12 {
- p = "pm"
- } else {
- p = "am"
- }
case stdTZ:
- if t.Zone != "" {
- p = t.Zone
+ name, offset := t.Zone()
+ if name != "" {
+ p = name
} else {
// No time zone known for this time, but we must print one.
// Use the -0700 format.
- zone := t.ZoneOffset / 60 // convert to minutes
+ zone := offset / 60 // convert to minutes
if zone < 0 {
p = "-"
zone = -zone
@@ -377,8 +508,8 @@ func (t *Time) Format(layout string) string {
p += zeroPad(zone % 60)
}
default:
- if len(std) >= 2 && std[0:2] == ".0" {
- p = formatNano(t.Nanosecond, len(std)-1)
+ if len(std) >= 2 && (std[0:2] == ".0" || std[0:2] == ".9") {
+ p = formatNano(t.Nanosecond(), len(std)-1, std[1] == '9')
}
}
b.WriteString(p)
@@ -387,15 +518,7 @@ func (t *Time) Format(layout string) string {
return b.String()
}
-// String returns a Unix-style representation of the time value.
-func (t *Time) String() string {
- if t == nil {
- return "<nil>"
- }
- return t.Format(UnixDate)
-}
-
-var errBad = os.NewError("bad value for field") // placeholder not passed to user
+var errBad = errors.New("bad value for field") // placeholder not passed to user
// ParseError describes a problem parsing a time string.
type ParseError struct {
@@ -406,17 +529,21 @@ type ParseError struct {
Message string
}
-// String is the string representation of a ParseError.
-func (e *ParseError) String() string {
+func quote(s string) string {
+ return "\"" + s + "\""
+}
+
+// Error returns the string representation of a ParseError.
+func (e *ParseError) Error() string {
if e.Message == "" {
return "parsing time " +
- strconv.Quote(e.Value) + " as " +
- strconv.Quote(e.Layout) + ": cannot parse " +
- strconv.Quote(e.ValueElem) + " as " +
- strconv.Quote(e.LayoutElem)
+ quote(e.Value) + " as " +
+ quote(e.Layout) + ": cannot parse " +
+ quote(e.ValueElem) + " as " +
+ quote(e.LayoutElem)
}
return "parsing time " +
- strconv.Quote(e.Value) + e.Message
+ quote(e.Value) + e.Message
}
// isDigit returns true if s[i] is a decimal digit, false if not or
@@ -432,7 +559,7 @@ func isDigit(s string, i int) bool {
// getnum parses s[0:1] or s[0:2] (fixed forces the latter)
// as a decimal integer and returns the integer and the
// remainder of the string.
-func getnum(s string, fixed bool) (int, string, os.Error) {
+func getnum(s string, fixed bool) (int, string, error) {
if !isDigit(s, 0) {
return 0, s, errBad
}
@@ -454,7 +581,7 @@ func cutspace(s string) string {
// skip removes the given prefix from value,
// treating runs of space characters as equivalent.
-func skip(value, prefix string) (string, os.Error) {
+func skip(value, prefix string) (string, error) {
for len(prefix) > 0 {
if prefix[0] == ' ' {
if len(value) > 0 && value[0] != ' ' {
@@ -474,35 +601,50 @@ func skip(value, prefix string) (string, os.Error) {
}
// Parse parses a formatted string and returns the time value it represents.
-// The layout defines the format by showing the representation of a standard
-// time, which is then used to describe the string to be parsed. Predefined
-// layouts ANSIC, UnixDate, RFC3339 and others describe standard
-// representations.For more information about the formats, see the
-// documentation for ANSIC.
+// The layout defines the format by showing the representation of the
+// standard time,
+// Mon Jan 2 15:04:05 -0700 MST 2006
+// which is then used to describe the string to be parsed. Predefined layouts
+// ANSIC, UnixDate, RFC3339 and others describe standard representations. For
+// more information about the formats and the definition of the standard
+// time, see the documentation for ANSIC.
//
-// Only those elements present in the value will be set in the returned time
-// structure. Also, if the input string represents an inconsistent time
-// (such as having the wrong day of the week), the returned value will also
-// be inconsistent. In any case, the elements of the returned time will be
-// sane: hours in 0..23, minutes in 0..59, day of month in 1..31, etc.
-// Years must be in the range 0000..9999.
-func Parse(alayout, avalue string) (*Time, os.Error) {
- var t Time
+// Elements omitted from the value are assumed to be zero or, when
+// zero is impossible, one, so parsing "3:04pm" returns the time
+// corresponding to Jan 1, year 0, 15:04:00 UTC.
+// Years must be in the range 0000..9999. The day of the week is checked
+// for syntax but it is otherwise ignored.
+func Parse(layout, value string) (Time, error) {
+ alayout, avalue := layout, value
rangeErrString := "" // set if a value is out of range
amSet := false // do we need to subtract 12 from the hour for midnight?
pmSet := false // do we need to add 12 to the hour?
- layout, value := alayout, avalue
+
+ // Time being constructed.
+ var (
+ year int
+ month int = 1 // January
+ day int = 1
+ hour int
+ min int
+ sec int
+ nsec int
+ z *Location
+ zoneOffset int = -1
+ zoneName string
+ )
+
// Each iteration processes one std value.
for {
- var err os.Error
+ var err error
prefix, std, suffix := nextStdChunk(layout)
value, err = skip(value, prefix)
if err != nil {
- return nil, &ParseError{alayout, avalue, prefix, value, ""}
+ return Time{}, &ParseError{alayout, avalue, prefix, value, ""}
}
if len(std) == 0 {
if len(value) != 0 {
- return nil, &ParseError{alayout, avalue, "", value, ": extra text: " + value}
+ return Time{}, &ParseError{alayout, avalue, "", value, ": extra text: " + value}
}
break
}
@@ -515,11 +657,11 @@ func Parse(alayout, avalue string) (*Time, os.Error) {
break
}
p, value = value[0:2], value[2:]
- t.Year, err = strconv.Atoi64(p)
- if t.Year >= 69 { // Unix time starts Dec 31 1969 in some time zones
- t.Year += 1900
+ year, err = atoi(p)
+ if year >= 69 { // Unix time starts Dec 31 1969 in some time zones
+ year += 1900
} else {
- t.Year += 2000
+ year += 2000
}
case stdLongYear:
if len(value) < 4 || !isDigit(value, 0) {
@@ -527,47 +669,47 @@ func Parse(alayout, avalue string) (*Time, os.Error) {
break
}
p, value = value[0:4], value[4:]
- t.Year, err = strconv.Atoi64(p)
+ year, err = atoi(p)
case stdMonth:
- t.Month, value, err = lookup(shortMonthNames, value)
+ month, value, err = lookup(shortMonthNames, value)
case stdLongMonth:
- t.Month, value, err = lookup(longMonthNames, value)
+ month, value, err = lookup(longMonthNames, value)
case stdNumMonth, stdZeroMonth:
- t.Month, value, err = getnum(value, std == stdZeroMonth)
- if t.Month <= 0 || 12 < t.Month {
+ month, value, err = getnum(value, std == stdZeroMonth)
+ if month <= 0 || 12 < month {
rangeErrString = "month"
}
case stdWeekDay:
- t.Weekday, value, err = lookup(shortDayNames, value)
+ // Ignore weekday except for error checking.
+ _, value, err = lookup(shortDayNames, value)
case stdLongWeekDay:
- t.Weekday, value, err = lookup(longDayNames, value)
+ _, value, err = lookup(longDayNames, value)
case stdDay, stdUnderDay, stdZeroDay:
if std == stdUnderDay && len(value) > 0 && value[0] == ' ' {
value = value[1:]
}
- t.Day, value, err = getnum(value, std == stdZeroDay)
- if t.Day < 0 || 31 < t.Day {
- // TODO: be more thorough in date check?
+ day, value, err = getnum(value, std == stdZeroDay)
+ if day < 0 || 31 < day {
rangeErrString = "day"
}
case stdHour:
- t.Hour, value, err = getnum(value, false)
- if t.Hour < 0 || 24 <= t.Hour {
+ hour, value, err = getnum(value, false)
+ if hour < 0 || 24 <= hour {
rangeErrString = "hour"
}
case stdHour12, stdZeroHour12:
- t.Hour, value, err = getnum(value, std == stdZeroHour12)
- if t.Hour < 0 || 12 < t.Hour {
+ hour, value, err = getnum(value, std == stdZeroHour12)
+ if hour < 0 || 12 < hour {
rangeErrString = "hour"
}
case stdMinute, stdZeroMinute:
- t.Minute, value, err = getnum(value, std == stdZeroMinute)
- if t.Minute < 0 || 60 <= t.Minute {
+ min, value, err = getnum(value, std == stdZeroMinute)
+ if min < 0 || 60 <= min {
rangeErrString = "minute"
}
case stdSecond, stdZeroSecond:
- t.Second, value, err = getnum(value, std == stdZeroSecond)
- if t.Second < 0 || 60 <= t.Second {
+ sec, value, err = getnum(value, std == stdZeroSecond)
+ if sec < 0 || 60 <= sec {
rangeErrString = "second"
}
// Special case: do we have a fractional second but no
@@ -582,16 +724,44 @@ func Parse(alayout, avalue string) (*Time, os.Error) {
n := 2
for ; n < len(value) && isDigit(value, n); n++ {
}
- rangeErrString, err = t.parseNanoseconds(value, n)
+ nsec, rangeErrString, err = parseNanoseconds(value, n)
value = value[n:]
}
+ case stdPM:
+ if len(value) < 2 {
+ err = errBad
+ break
+ }
+ p, value = value[0:2], value[2:]
+ switch p {
+ case "PM":
+ pmSet = true
+ case "AM":
+ amSet = true
+ default:
+ err = errBad
+ }
+ case stdpm:
+ if len(value) < 2 {
+ err = errBad
+ break
+ }
+ p, value = value[0:2], value[2:]
+ switch p {
+ case "pm":
+ pmSet = true
+ case "am":
+ amSet = true
+ default:
+ err = errBad
+ }
case stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ:
if std[0] == 'Z' && len(value) >= 1 && value[0] == 'Z' {
value = value[1:]
- t.Zone = "UTC"
+ z = UTC
break
}
- var sign, hh, mm string
+ var sign, hour, min string
if std == stdISO8601ColonTZ || std == stdNumColonTZ {
if len(value) < 6 {
err = errBad
@@ -601,65 +771,38 @@ func Parse(alayout, avalue string) (*Time, os.Error) {
err = errBad
break
}
- sign, hh, mm, value = value[0:1], value[1:3], value[4:6], value[6:]
+ sign, hour, min, value = value[0:1], value[1:3], value[4:6], value[6:]
} else if std == stdNumShortTZ {
if len(value) < 3 {
err = errBad
break
}
- sign, hh, mm, value = value[0:1], value[1:3], "00", value[3:]
+ sign, hour, min, value = value[0:1], value[1:3], "00", value[3:]
} else {
if len(value) < 5 {
err = errBad
break
}
- sign, hh, mm, value = value[0:1], value[1:3], value[3:5], value[5:]
+ sign, hour, min, value = value[0:1], value[1:3], value[3:5], value[5:]
}
- var hr, min int
- hr, err = strconv.Atoi(hh)
+ var hr, mm int
+ hr, err = atoi(hour)
if err == nil {
- min, err = strconv.Atoi(mm)
+ mm, err = atoi(min)
}
- t.ZoneOffset = (hr*60 + min) * 60 // offset is in seconds
+ zoneOffset = (hr*60 + mm) * 60 // offset is in seconds
switch sign[0] {
case '+':
case '-':
- t.ZoneOffset = -t.ZoneOffset
- default:
- err = errBad
- }
- case stdPM:
- if len(value) < 2 {
- err = errBad
- break
- }
- p, value = value[0:2], value[2:]
- switch p {
- case "PM":
- pmSet = true
- case "AM":
- amSet = true
- default:
- err = errBad
- }
- case stdpm:
- if len(value) < 2 {
- err = errBad
- break
- }
- p, value = value[0:2], value[2:]
- switch p {
- case "pm":
- pmSet = true
- case "am":
- amSet = true
+ zoneOffset = -zoneOffset
default:
err = errBad
}
case stdTZ:
// Does it look like a time zone?
if len(value) >= 3 && value[0:3] == "UTC" {
- t.Zone, value = value[0:3], value[3:]
+ z = UTC
+ value = value[3:]
break
}
@@ -680,47 +823,86 @@ func Parse(alayout, avalue string) (*Time, os.Error) {
break
}
// It's a valid format.
- t.Zone = p
- // Can we find its offset?
- if offset, found := lookupByName(p); found {
- t.ZoneOffset = offset
- }
+ zoneName = p
default:
if len(value) < len(std) {
err = errBad
break
}
if len(std) >= 2 && std[0:2] == ".0" {
- rangeErrString, err = t.parseNanoseconds(value, len(std))
+ nsec, rangeErrString, err = parseNanoseconds(value, len(std))
value = value[len(std):]
}
}
if rangeErrString != "" {
- return nil, &ParseError{alayout, avalue, std, value, ": " + rangeErrString + " out of range"}
+ return Time{}, &ParseError{alayout, avalue, std, value, ": " + rangeErrString + " out of range"}
}
if err != nil {
- return nil, &ParseError{alayout, avalue, std, value, ""}
+ return Time{}, &ParseError{alayout, avalue, std, value, ""}
+ }
+ }
+ if pmSet && hour < 12 {
+ hour += 12
+ } else if amSet && hour == 12 {
+ hour = 0
+ }
+
+ // TODO: be more aggressive checking day?
+ if z != nil {
+ return Date(year, Month(month), day, hour, min, sec, nsec, z), nil
+ }
+
+ t := Date(year, Month(month), day, hour, min, sec, nsec, UTC)
+ if zoneOffset != -1 {
+ t.sec -= int64(zoneOffset)
+
+ // Look for local zone with the given offset.
+ // If that zone was in effect at the given time, use it.
+ name, offset, _, _, _ := Local.lookup(t.sec + internalToUnix)
+ if offset == zoneOffset && (zoneName == "" || name == zoneName) {
+ t.loc = Local
+ return t, nil
}
+
+ // Otherwise create fake zone to record offset.
+ t.loc = FixedZone(zoneName, zoneOffset)
+ return t, nil
}
- if pmSet && t.Hour < 12 {
- t.Hour += 12
- } else if amSet && t.Hour == 12 {
- t.Hour = 0
+
+ if zoneName != "" {
+ // Look for local zone with the given offset.
+ // If that zone was in effect at the given time, use it.
+ offset, _, ok := Local.lookupName(zoneName)
+ if ok {
+ name, off, _, _, _ := Local.lookup(t.sec + internalToUnix - int64(offset))
+ if name == zoneName && off == offset {
+ t.sec -= int64(offset)
+ t.loc = Local
+ return t, nil
+ }
+ }
+
+ // Otherwise, create fake zone with unknown offset.
+ t.loc = FixedZone(zoneName, 0)
+ return t, nil
}
- return &t, nil
+
+ // Otherwise, fall back to UTC.
+ return t, nil
}
-func (t *Time) parseNanoseconds(value string, nbytes int) (rangErrString string, err os.Error) {
+func parseNanoseconds(value string, nbytes int) (ns int, rangeErrString string, err error) {
if value[0] != '.' {
- return "", errBad
+ err = errBad
+ return
}
- var ns int
- ns, err = strconv.Atoi(value[1:nbytes])
+ ns, err = atoi(value[1:nbytes])
if err != nil {
- return "", err
+ return
}
if ns < 0 || 1e9 <= ns {
- return "fractional second", nil
+ rangeErrString = "fractional second"
+ return
}
// We need nanoseconds, which means scaling by the number
// of missing digits in the format, maximum length 10. If it's
@@ -729,6 +911,128 @@ func (t *Time) parseNanoseconds(value string, nbytes int) (rangErrString string,
for i := 0; i < scaleDigits; i++ {
ns *= 10
}
- t.Nanosecond = ns
return
}
+
+var errLeadingInt = errors.New("time: bad [0-9]*") // never printed
+
+// leadingInt consumes the leading [0-9]* from s.
+func leadingInt(s string) (x int, rem string, err error) {
+ i := 0
+ for ; i < len(s); i++ {
+ c := s[i]
+ if c < '0' || c > '9' {
+ break
+ }
+ if x >= (1<<31-10)/10 {
+ // overflow
+ return 0, "", errLeadingInt
+ }
+ x = x*10 + int(c) - '0'
+ }
+ return x, s[i:], nil
+}
+
+var unitMap = map[string]float64{
+ "ns": float64(Nanosecond),
+ "us": float64(Microsecond),
+ "µs": float64(Microsecond), // U+00B5 = micro symbol
+ "μs": float64(Microsecond), // U+03BC = Greek letter mu
+ "ms": float64(Millisecond),
+ "s": float64(Second),
+ "m": float64(Minute),
+ "h": float64(Hour),
+}
+
+// ParseDuration parses a duration string.
+// A duration string is a possibly signed sequence of
+// decimal numbers, each with optional fraction and a unit suffix,
+// such as "300ms", "-1.5h" or "2h45m".
+// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
+func ParseDuration(s string) (Duration, error) {
+ // [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
+ orig := s
+ f := float64(0)
+ neg := false
+
+ // Consume [-+]?
+ if s != "" {
+ c := s[0]
+ if c == '-' || c == '+' {
+ neg = c == '-'
+ s = s[1:]
+ }
+ }
+ // Special case: if all that is left is "0", this is zero.
+ if s == "0" {
+ return 0, nil
+ }
+ if s == "" {
+ return 0, errors.New("time: invalid duration " + orig)
+ }
+ for s != "" {
+ g := float64(0) // this element of the sequence
+
+ var x int
+ var err error
+
+ // The next character must be [0-9.]
+ if !(s[0] == '.' || ('0' <= s[0] && s[0] <= '9')) {
+ return 0, errors.New("time: invalid duration " + orig)
+ }
+ // Consume [0-9]*
+ pl := len(s)
+ x, s, err = leadingInt(s)
+ if err != nil {
+ return 0, errors.New("time: invalid duration " + orig)
+ }
+ g = float64(x)
+ pre := pl != len(s) // whether we consumed anything before a period
+
+ // Consume (\.[0-9]*)?
+ post := false
+ if s != "" && s[0] == '.' {
+ s = s[1:]
+ pl := len(s)
+ x, s, err = leadingInt(s)
+ if err != nil {
+ return 0, errors.New("time: invalid duration " + orig)
+ }
+ scale := 1
+ for n := pl - len(s); n > 0; n-- {
+ scale *= 10
+ }
+ g += float64(x) / float64(scale)
+ post = pl != len(s)
+ }
+ if !pre && !post {
+ // no digits (e.g. ".s" or "-.s")
+ return 0, errors.New("time: invalid duration " + orig)
+ }
+
+ // Consume unit.
+ i := 0
+ for ; i < len(s); i++ {
+ c := s[i]
+ if c == '.' || ('0' <= c && c <= '9') {
+ break
+ }
+ }
+ if i == 0 {
+ return 0, errors.New("time: missing unit in duration " + orig)
+ }
+ u := s[:i]
+ s = s[i:]
+ unit, ok := unitMap[u]
+ if !ok {
+ return 0, errors.New("time: unknown unit " + u + " in duration " + orig)
+ }
+
+ f += g * unit
+ }
+
+ if neg {
+ f = -f
+ }
+ return Duration(f), nil
+}
diff --git a/src/pkg/time/internal_test.go b/src/pkg/time/internal_test.go
new file mode 100644
index 000000000..b753896d7
--- /dev/null
+++ b/src/pkg/time/internal_test.go
@@ -0,0 +1,13 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package time
+
+func init() {
+ // force US/Pacific for time zone tests
+ localOnce.Do(initTestingZone)
+}
+
+var Interrupt = interrupt
+var DaysIn = daysIn
diff --git a/src/pkg/time/sleep.go b/src/pkg/time/sleep.go
index 314622d0d..27820b0ea 100644
--- a/src/pkg/time/sleep.go
+++ b/src/pkg/time/sleep.go
@@ -4,174 +4,92 @@
package time
-import (
- "container/heap"
- "sync"
-)
+// Sleep pauses the current goroutine for the duration d.
+func Sleep(d Duration)
-// The Timer type represents a single event.
-// When the Timer expires, the current time will be sent on C
-// unless the Timer represents an AfterFunc event.
-type Timer struct {
- C <-chan int64
- t int64 // The absolute time that the event should fire.
- f func(int64) // The function to call when the event fires.
- i int // The event's index inside eventHeap.
-}
-
-type timerHeap []*Timer
-
-// forever is the absolute time (in ns) of an event that is forever away.
-const forever = 1 << 62
-
-// maxSleepTime is the maximum length of time that a sleeper
-// sleeps for before checking if it is defunct.
-const maxSleepTime = 1e9
-
-var (
- // timerMutex guards the variables inside this var group.
- timerMutex sync.Mutex
-
- // timers holds a binary heap of pending events, terminated with a sentinel.
- timers timerHeap
-
- // currentSleeper is an ever-incrementing counter which represents
- // the current sleeper. It allows older sleepers to detect that they are
- // defunct and exit.
- currentSleeper int64
-)
-
-func init() {
- timers.Push(&Timer{t: forever}) // sentinel
+func nano() int64 {
+ sec, nsec := now()
+ return sec*1e9 + int64(nsec)
}
-// NewTimer creates a new Timer that will send
-// the current time on its channel after at least ns nanoseconds.
-func NewTimer(ns int64) *Timer {
- c := make(chan int64, 1)
- e := after(ns, func(t int64) { c <- t })
- e.C = c
- return e
+// Interface to timers implemented in package runtime.
+// Must be in sync with ../runtime/runtime.h:/^struct.Timer$
+type runtimeTimer struct {
+ i int32
+ when int64
+ period int64
+ f func(int64, interface{})
+ arg interface{}
}
-// After waits at least ns nanoseconds before sending the current time
-// on the returned channel.
-// It is equivalent to NewTimer(ns).C.
-func After(ns int64) <-chan int64 {
- return NewTimer(ns).C
-}
+func startTimer(*runtimeTimer)
+func stopTimer(*runtimeTimer) bool
-// AfterFunc waits at least ns nanoseconds before calling f
-// in its own goroutine. It returns a Timer that can
-// be used to cancel the call using its Stop method.
-func AfterFunc(ns int64, f func()) *Timer {
- return after(ns, func(_ int64) {
- go f()
- })
+// The Timer type represents a single event.
+// When the Timer expires, the current time will be sent on C,
+// unless the Timer was created by AfterFunc.
+type Timer struct {
+ C <-chan Time
+ r runtimeTimer
}
// Stop prevents the Timer from firing.
// It returns true if the call stops the timer, false if the timer has already
// expired or stopped.
-func (e *Timer) Stop() (ok bool) {
- timerMutex.Lock()
- // Avoid removing the first event in the queue so that
- // we don't start a new sleeper unnecessarily.
- if e.i > 0 {
- heap.Remove(timers, e.i)
- }
- ok = e.f != nil
- e.f = nil
- timerMutex.Unlock()
- return
+func (t *Timer) Stop() (ok bool) {
+ return stopTimer(&t.r)
}
-// after is the implementation of After and AfterFunc.
-// When the current time is after ns, it calls f with the current time.
-// It assumes that f will not block.
-func after(ns int64, f func(int64)) (e *Timer) {
- now := Nanoseconds()
- t := now + ns
- if ns > 0 && t < now {
- panic("time: time overflow")
- }
- timerMutex.Lock()
- t0 := timers[0].t
- e = &Timer{nil, t, f, -1}
- heap.Push(timers, e)
- // Start a new sleeper if the new event is before
- // the first event in the queue. If the length of time
- // until the new event is at least maxSleepTime,
- // then we're guaranteed that the sleeper will wake up
- // in time to service it, so no new sleeper is needed.
- if t0 > t && (t0 == forever || ns < maxSleepTime) {
- currentSleeper++
- go sleeper(currentSleeper)
+// NewTimer creates a new Timer that will send
+// the current time on its channel after at least duration d.
+func NewTimer(d Duration) *Timer {
+ c := make(chan Time, 1)
+ t := &Timer{
+ C: c,
+ r: runtimeTimer{
+ when: nano() + int64(d),
+ f: sendTime,
+ arg: c,
+ },
}
- timerMutex.Unlock()
- return
+ startTimer(&t.r)
+ return t
}
-// sleeper continually looks at the earliest event in the queue, waits until it happens,
-// then removes any events in the queue that are due. It stops when the queue
-// is empty or when another sleeper has been started.
-func sleeper(sleeperId int64) {
- timerMutex.Lock()
- e := timers[0]
- t := Nanoseconds()
- for e.t != forever {
- if dt := e.t - t; dt > 0 {
- if dt > maxSleepTime {
- dt = maxSleepTime
- }
- timerMutex.Unlock()
- sysSleep(dt)
- timerMutex.Lock()
- if currentSleeper != sleeperId {
- // Another sleeper has been started, making this one redundant.
- break
- }
- }
- e = timers[0]
- t = Nanoseconds()
- for t >= e.t {
- if e.f != nil {
- e.f(t)
- e.f = nil
- }
- heap.Pop(timers)
- e = timers[0]
- }
+func sendTime(now int64, c interface{}) {
+ // Non-blocking send of time on c.
+ // Used in NewTimer, it cannot block anyway (buffer).
+ // Used in NewTicker, dropping sends on the floor is
+ // the desired behavior when the reader gets behind,
+ // because the sends are periodic.
+ select {
+ case c.(chan Time) <- Unix(0, now):
+ default:
}
- timerMutex.Unlock()
-}
-
-func (timerHeap) Len() int {
- return len(timers)
-}
-
-func (timerHeap) Less(i, j int) bool {
- return timers[i].t < timers[j].t
}
-func (timerHeap) Swap(i, j int) {
- timers[i], timers[j] = timers[j], timers[i]
- timers[i].i = i
- timers[j].i = j
+// After waits for the duration to elapse and then sends the current time
+// on the returned channel.
+// It is equivalent to NewTimer(d).C.
+func After(d Duration) <-chan Time {
+ return NewTimer(d).C
}
-func (timerHeap) Push(x interface{}) {
- e := x.(*Timer)
- e.i = len(timers)
- timers = append(timers, e)
+// AfterFunc waits for the duration to elapse and then calls f
+// in its own goroutine. It returns a Timer that can
+// be used to cancel the call using its Stop method.
+func AfterFunc(d Duration, f func()) *Timer {
+ t := &Timer{
+ r: runtimeTimer{
+ when: nano() + int64(d),
+ f: goFunc,
+ arg: f,
+ },
+ }
+ startTimer(&t.r)
+ return t
}
-func (timerHeap) Pop() interface{} {
- // TODO: possibly shrink array.
- n := len(timers) - 1
- e := timers[n]
- timers[n] = nil
- timers = timers[0:n]
- e.i = -1
- return e
+func goFunc(now int64, arg interface{}) {
+ go arg.(func())()
}
diff --git a/src/pkg/time/sleep_test.go b/src/pkg/time/sleep_test.go
index a4a1a429f..526d58d75 100644
--- a/src/pkg/time/sleep_test.go
+++ b/src/pkg/time/sleep_test.go
@@ -5,25 +5,26 @@
package time_test
import (
+ "errors"
"fmt"
- "os"
- "syscall"
- "testing"
+ "runtime"
"sort"
+ "sync/atomic"
+ "testing"
. "time"
)
func TestSleep(t *testing.T) {
- const delay = int64(100e6)
+ const delay = 100 * Millisecond
go func() {
Sleep(delay / 2)
- syscall.Kill(os.Getpid(), syscall.SIGCHLD)
+ Interrupt()
}()
- start := Nanoseconds()
+ start := Now()
Sleep(delay)
- duration := Nanoseconds() - start
+ duration := Now().Sub(start)
if duration < delay {
- t.Fatalf("Sleep(%d) slept for only %d ns", delay, duration)
+ t.Fatalf("Sleep(%s) slept for only %s", delay, duration)
}
}
@@ -38,7 +39,7 @@ func TestAfterFunc(t *testing.T) {
i--
if i >= 0 {
AfterFunc(0, f)
- Sleep(1e9)
+ Sleep(1 * Second)
} else {
c <- true
}
@@ -48,6 +49,23 @@ func TestAfterFunc(t *testing.T) {
<-c
}
+func TestAfterStress(t *testing.T) {
+ stop := uint32(0)
+ go func() {
+ for atomic.LoadUint32(&stop) == 0 {
+ runtime.GC()
+ // Need to yield, because otherwise
+ // the main goroutine will never set the stop flag.
+ runtime.Gosched()
+ }
+ }()
+ c := Tick(1)
+ for i := 0; i < 100; i++ {
+ <-c
+ }
+ atomic.StoreUint32(&stop, 1)
+}
+
func BenchmarkAfterFunc(b *testing.B) {
i := b.N
c := make(chan bool)
@@ -73,47 +91,49 @@ func BenchmarkAfter(b *testing.B) {
func BenchmarkStop(b *testing.B) {
for i := 0; i < b.N; i++ {
- NewTimer(1e9).Stop()
+ NewTimer(1 * Second).Stop()
}
}
func TestAfter(t *testing.T) {
- const delay = int64(100e6)
- start := Nanoseconds()
+ const delay = 100 * Millisecond
+ start := Now()
end := <-After(delay)
- if duration := Nanoseconds() - start; duration < delay {
- t.Fatalf("After(%d) slept for only %d ns", delay, duration)
+ if duration := Now().Sub(start); duration < delay {
+ t.Fatalf("After(%s) slept for only %d ns", delay, duration)
}
- if min := start + delay; end < min {
- t.Fatalf("After(%d) expect >= %d, got %d", delay, min, end)
+ if min := start.Add(delay); end.Before(min) {
+ t.Fatalf("After(%s) expect >= %s, got %s", delay, min, end)
}
}
func TestAfterTick(t *testing.T) {
- const (
- Delta = 100 * 1e6
- Count = 10
- )
- t0 := Nanoseconds()
+ const Count = 10
+ Delta := 100 * Millisecond
+ if testing.Short() {
+ Delta = 10 * Millisecond
+ }
+ t0 := Now()
for i := 0; i < Count; i++ {
<-After(Delta)
}
- t1 := Nanoseconds()
- ns := t1 - t0
- target := int64(Delta * Count)
- slop := target * 2 / 10
- if ns < target-slop || ns > target+slop {
- t.Fatalf("%d ticks of %g ns took %g ns, expected %g", Count, float64(Delta), float64(ns), float64(target))
+ t1 := Now()
+ d := t1.Sub(t0)
+ target := Delta * Count
+ if d < target*9/10 {
+ t.Fatalf("%d ticks of %s too fast: took %s, expected %s", Count, Delta, d, target)
+ }
+ if !testing.Short() && d > target*30/10 {
+ t.Fatalf("%d ticks of %s too slow: took %s, expected %s", Count, Delta, d, target)
}
}
func TestAfterStop(t *testing.T) {
- const msec = 1e6
- AfterFunc(100*msec, func() {})
- t0 := NewTimer(50 * msec)
+ AfterFunc(100*Millisecond, func() {})
+ t0 := NewTimer(50 * Millisecond)
c1 := make(chan bool, 1)
- t1 := AfterFunc(150*msec, func() { c1 <- true })
- c2 := After(200 * msec)
+ t1 := AfterFunc(150*Millisecond, func() { c1 <- true })
+ c2 := After(200 * Millisecond)
if !t0.Stop() {
t.Fatalf("failed to stop event 0")
}
@@ -137,7 +157,7 @@ func TestAfterQueuing(t *testing.T) {
// This test flakes out on some systems,
// so we'll try it a few times before declaring it a failure.
const attempts = 3
- err := os.NewError("!=nil")
+ err := errors.New("!=nil")
for i := 0; i < attempts && err != nil; i++ {
if err = testAfterQueuing(t); err != nil {
t.Logf("attempt %v failed: %v", i, err)
@@ -152,38 +172,54 @@ var slots = []int{5, 3, 6, 6, 6, 1, 1, 2, 7, 9, 4, 8, 0}
type afterResult struct {
slot int
- t int64
+ t Time
}
-func await(slot int, result chan<- afterResult, ac <-chan int64) {
+func await(slot int, result chan<- afterResult, ac <-chan Time) {
result <- afterResult{slot, <-ac}
}
-func testAfterQueuing(t *testing.T) os.Error {
- const (
- Delta = 100 * 1e6
- )
+func testAfterQueuing(t *testing.T) error {
+ Delta := 100 * Millisecond
+ if testing.Short() {
+ Delta = 20 * Millisecond
+ }
// make the result channel buffered because we don't want
// to depend on channel queueing semantics that might
// possibly change in the future.
result := make(chan afterResult, len(slots))
- t0 := Nanoseconds()
+ t0 := Now()
for _, slot := range slots {
- go await(slot, result, After(int64(slot)*Delta))
+ go await(slot, result, After(Duration(slot)*Delta))
}
sort.Ints(slots)
for _, slot := range slots {
r := <-result
if r.slot != slot {
- return fmt.Errorf("after queue got slot %d, expected %d", r.slot, slot)
+ return fmt.Errorf("after slot %d, expected %d", r.slot, slot)
}
- ns := r.t - t0
- target := int64(slot * Delta)
- slop := int64(Delta) / 4
- if ns < target-slop || ns > target+slop {
- return fmt.Errorf("after queue slot %d arrived at %g, expected [%g,%g]", slot, float64(ns), float64(target-slop), float64(target+slop))
+ dt := r.t.Sub(t0)
+ target := Duration(slot) * Delta
+ if dt < target-Delta/2 || dt > target+Delta*10 {
+ return fmt.Errorf("After(%s) arrived at %s, expected [%s,%s]", target, dt, target-Delta/2, target+Delta*10)
}
}
return nil
}
+
+func TestTimerStopStress(t *testing.T) {
+ if testing.Short() {
+ return
+ }
+ for i := 0; i < 100; i++ {
+ go func(i int) {
+ timer := AfterFunc(2*Second, func() {
+ t.Fatalf("timer %d was not stopped", i)
+ })
+ Sleep(1 * Second)
+ timer.Stop()
+ }(i)
+ }
+ Sleep(3 * Second)
+}
diff --git a/src/pkg/time/sys.go b/src/pkg/time/sys.go
deleted file mode 100644
index 9fde3b3b6..000000000
--- a/src/pkg/time/sys.go
+++ /dev/null
@@ -1,51 +0,0 @@
-// Copyright 2009 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package time
-
-import "os"
-
-// Seconds reports the number of seconds since the Unix epoch,
-// January 1, 1970 00:00:00 UTC.
-func Seconds() int64 {
- sec, _, err := os.Time()
- if err != nil {
- panic(err)
- }
- return sec
-}
-
-// Nanoseconds reports the number of nanoseconds since the Unix epoch,
-// January 1, 1970 00:00:00 UTC.
-func Nanoseconds() int64 {
- sec, nsec, err := os.Time()
- if err != nil {
- panic(err)
- }
- return sec*1e9 + nsec
-}
-
-// Sleep pauses the current goroutine for at least ns nanoseconds.
-// Higher resolution sleeping may be provided by syscall.Nanosleep
-// on some operating systems.
-func Sleep(ns int64) os.Error {
- _, err := sleep(Nanoseconds(), ns)
- return err
-}
-
-// sleep takes the current time and a duration,
-// pauses for at least ns nanoseconds, and
-// returns the current time and an error.
-func sleep(t, ns int64) (int64, os.Error) {
- // TODO(cw): use monotonic-time once it's available
- end := t + ns
- for t < end {
- err := sysSleep(end - t)
- if err != nil {
- return 0, err
- }
- t = Nanoseconds()
- }
- return t, nil
-}
diff --git a/src/pkg/time/sys_plan9.go b/src/pkg/time/sys_plan9.go
index abe8649a2..848472944 100644
--- a/src/pkg/time/sys_plan9.go
+++ b/src/pkg/time/sys_plan9.go
@@ -2,17 +2,75 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+// +build plan9
+
package time
import (
- "os"
+ "errors"
"syscall"
)
-func sysSleep(t int64) os.Error {
- err := syscall.Sleep(t)
+// for testing: whatever interrupts a sleep
+func interrupt() {
+ // cannot predict pid, don't want to kill group
+}
+
+// readFile reads and returns the content of the named file.
+// It is a trivial implementation of ioutil.ReadFile, reimplemented
+// here to avoid depending on io/ioutil or os.
+func readFile(name string) ([]byte, error) {
+ f, err := syscall.Open(name, syscall.O_RDONLY)
+ if err != nil {
+ return nil, err
+ }
+ defer syscall.Close(f)
+ var (
+ buf [4096]byte
+ ret []byte
+ n int
+ )
+ for {
+ n, err = syscall.Read(f, buf[:])
+ if n > 0 {
+ ret = append(ret, buf[:n]...)
+ }
+ if n == 0 || err != nil {
+ break
+ }
+ }
+ return ret, err
+}
+
+func open(name string) (uintptr, error) {
+ fd, err := syscall.Open(name, syscall.O_RDONLY)
if err != nil {
- return os.NewSyscallError("sleep", err)
+ return 0, err
+ }
+ return uintptr(fd), nil
+}
+
+func closefd(fd uintptr) {
+ syscall.Close(int(fd))
+}
+
+func preadn(fd uintptr, buf []byte, off int) error {
+ whence := 0
+ if off < 0 {
+ whence = 2
+ }
+ if _, err := syscall.Seek(int(fd), int64(off), whence); err != nil {
+ return err
+ }
+ for len(buf) > 0 {
+ m, err := syscall.Read(int(fd), buf)
+ if m <= 0 {
+ if err == nil {
+ return errors.New("short read")
+ }
+ return err
+ }
+ buf = buf[m:]
}
return nil
}
diff --git a/src/pkg/time/sys_posix.go b/src/pkg/time/sys_posix.go
deleted file mode 100644
index 0d1eb72fc..000000000
--- a/src/pkg/time/sys_posix.go
+++ /dev/null
@@ -1,18 +0,0 @@
-// Copyright 2011 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package time
-
-import (
- "os"
- "syscall"
-)
-
-func sysSleep(t int64) os.Error {
- errno := syscall.Sleep(t)
- if errno != 0 && errno != syscall.EINTR {
- return os.NewSyscallError("sleep", errno)
- }
- return nil
-}
diff --git a/src/pkg/time/sys_unix.go b/src/pkg/time/sys_unix.go
new file mode 100644
index 000000000..7f69b492c
--- /dev/null
+++ b/src/pkg/time/sys_unix.go
@@ -0,0 +1,76 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build darwin freebsd linux netbsd openbsd
+
+package time
+
+import (
+ "errors"
+ "syscall"
+)
+
+// for testing: whatever interrupts a sleep
+func interrupt() {
+ syscall.Kill(syscall.Getpid(), syscall.SIGCHLD)
+}
+
+// readFile reads and returns the content of the named file.
+// It is a trivial implementation of ioutil.ReadFile, reimplemented
+// here to avoid depending on io/ioutil or os.
+func readFile(name string) ([]byte, error) {
+ f, err := syscall.Open(name, syscall.O_RDONLY, 0)
+ if err != nil {
+ return nil, err
+ }
+ defer syscall.Close(f)
+ var (
+ buf [4096]byte
+ ret []byte
+ n int
+ )
+ for {
+ n, err = syscall.Read(f, buf[:])
+ if n > 0 {
+ ret = append(ret, buf[:n]...)
+ }
+ if n == 0 || err != nil {
+ break
+ }
+ }
+ return ret, err
+}
+
+func open(name string) (uintptr, error) {
+ fd, err := syscall.Open(name, syscall.O_RDONLY, 0)
+ if err != nil {
+ return 0, err
+ }
+ return uintptr(fd), nil
+}
+
+func closefd(fd uintptr) {
+ syscall.Close(int(fd))
+}
+
+func preadn(fd uintptr, buf []byte, off int) error {
+ whence := 0
+ if off < 0 {
+ whence = 2
+ }
+ if _, err := syscall.Seek(int(fd), int64(off), whence); err != nil {
+ return err
+ }
+ for len(buf) > 0 {
+ m, err := syscall.Read(int(fd), buf)
+ if m <= 0 {
+ if err == nil {
+ return errors.New("short read")
+ }
+ return err
+ }
+ buf = buf[m:]
+ }
+ return nil
+}
diff --git a/src/pkg/time/sys_windows.go b/src/pkg/time/sys_windows.go
new file mode 100644
index 000000000..de63b4bf4
--- /dev/null
+++ b/src/pkg/time/sys_windows.go
@@ -0,0 +1,73 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package time
+
+import (
+ "errors"
+ "syscall"
+)
+
+// for testing: whatever interrupts a sleep
+func interrupt() {
+}
+
+// readFile reads and returns the content of the named file.
+// It is a trivial implementation of ioutil.ReadFile, reimplemented
+// here to avoid depending on io/ioutil or os.
+func readFile(name string) ([]byte, error) {
+ f, err := syscall.Open(name, syscall.O_RDONLY, 0)
+ if err != nil {
+ return nil, err
+ }
+ defer syscall.Close(f)
+ var (
+ buf [4096]byte
+ ret []byte
+ n int
+ )
+ for {
+ n, err = syscall.Read(f, buf[:])
+ if n > 0 {
+ ret = append(ret, buf[:n]...)
+ }
+ if n == 0 || err != nil {
+ break
+ }
+ }
+ return ret, err
+}
+
+func open(name string) (uintptr, error) {
+ fd, err := syscall.Open(name, syscall.O_RDONLY, 0)
+ if err != nil {
+ return 0, err
+ }
+ return uintptr(fd), nil
+}
+
+func closefd(fd uintptr) {
+ syscall.Close(syscall.Handle(fd))
+}
+
+func preadn(fd uintptr, buf []byte, off int) error {
+ whence := 0
+ if off < 0 {
+ whence = 2
+ }
+ if _, err := syscall.Seek(syscall.Handle(fd), int64(off), whence); err != nil {
+ return err
+ }
+ for len(buf) > 0 {
+ m, err := syscall.Read(syscall.Handle(fd), buf)
+ if m <= 0 {
+ if err == nil {
+ return errors.New("short read")
+ }
+ return err
+ }
+ buf = buf[m:]
+ }
+ return nil
+}
diff --git a/src/pkg/time/tick.go b/src/pkg/time/tick.go
index 852bae9c9..8c6b9bc3b 100644
--- a/src/pkg/time/tick.go
+++ b/src/pkg/time/tick.go
@@ -4,174 +4,50 @@
package time
-import (
- "os"
- "sync"
-)
+import "errors"
// A Ticker holds a synchronous channel that delivers `ticks' of a clock
// at intervals.
type Ticker struct {
- C <-chan int64 // The channel on which the ticks are delivered.
- c chan<- int64 // The same channel, but the end we use.
- ns int64
- shutdown chan bool // Buffered channel used to signal shutdown.
- nextTick int64
- next *Ticker
+ C <-chan Time // The channel on which the ticks are delivered.
+ r runtimeTimer
+}
+
+// NewTicker returns a new Ticker containing a channel that will send the
+// time with a period specified by the duration argument.
+// It adjusts the intervals or drops ticks to make up for slow receivers.
+// The duration d must be greater than zero; if not, NewTicker will panic.
+func NewTicker(d Duration) *Ticker {
+ if d <= 0 {
+ panic(errors.New("non-positive interval for NewTicker"))
+ }
+ // Give the channel a 1-element time buffer.
+ // If the client falls behind while reading, we drop ticks
+ // on the floor until the client catches up.
+ c := make(chan Time, 1)
+ t := &Ticker{
+ C: c,
+ r: runtimeTimer{
+ when: nano() + int64(d),
+ period: int64(d),
+ f: sendTime,
+ arg: c,
+ },
+ }
+ startTimer(&t.r)
+ return t
}
// Stop turns off a ticker. After Stop, no more ticks will be sent.
func (t *Ticker) Stop() {
- select {
- case t.shutdown <- true:
- // ok
- default:
- // Stop in progress already
- }
+ stopTimer(&t.r)
}
// Tick is a convenience wrapper for NewTicker providing access to the ticking
// channel only. Useful for clients that have no need to shut down the ticker.
-func Tick(ns int64) <-chan int64 {
- if ns <= 0 {
+func Tick(d Duration) <-chan Time {
+ if d <= 0 {
return nil
}
- return NewTicker(ns).C
-}
-
-type alarmer struct {
- wakeUp chan bool // wakeup signals sent/received here
- wakeMeAt chan int64
- wakeTime int64
-}
-
-// Set alarm to go off at time ns, if not already set earlier.
-func (a *alarmer) set(ns int64) {
- switch {
- case a.wakeTime > ns:
- // Next tick we expect is too late; shut down the late runner
- // and (after fallthrough) start a new wakeLoop.
- close(a.wakeMeAt)
- fallthrough
- case a.wakeMeAt == nil:
- // There's no wakeLoop, start one.
- a.wakeMeAt = make(chan int64)
- a.wakeUp = make(chan bool, 1)
- go wakeLoop(a.wakeMeAt, a.wakeUp)
- fallthrough
- case a.wakeTime == 0:
- // Nobody else is waiting; it's just us.
- a.wakeTime = ns
- a.wakeMeAt <- ns
- default:
- // There's already someone scheduled.
- }
-}
-
-// Channel to notify tickerLoop of new Tickers being created.
-var newTicker chan *Ticker
-
-func startTickerLoop() {
- newTicker = make(chan *Ticker)
- go tickerLoop()
-}
-
-// wakeLoop delivers ticks at scheduled times, sleeping until the right moment.
-// If another, earlier Ticker is created while it sleeps, tickerLoop() will start a new
-// wakeLoop and signal that this one is done by closing the wakeMeAt channel.
-func wakeLoop(wakeMeAt chan int64, wakeUp chan bool) {
- for wakeAt := range wakeMeAt {
- Sleep(wakeAt - Nanoseconds())
- wakeUp <- true
- }
-}
-
-// A single tickerLoop serves all ticks to Tickers. It waits for two events:
-// either the creation of a new Ticker or a tick from the alarm,
-// signaling a time to wake up one or more Tickers.
-func tickerLoop() {
- // Represents the next alarm to be delivered.
- var alarm alarmer
- var now, wakeTime int64
- var tickers *Ticker
- for {
- select {
- case t := <-newTicker:
- // Add Ticker to list
- t.next = tickers
- tickers = t
- // Arrange for a new alarm if this one precedes the existing one.
- alarm.set(t.nextTick)
- case <-alarm.wakeUp:
- now = Nanoseconds()
- wakeTime = now + 1e15 // very long in the future
- var prev *Ticker = nil
- // Scan list of tickers, delivering updates to those
- // that need it and determining the next wake time.
- // TODO(r): list should be sorted in time order.
- for t := tickers; t != nil; t = t.next {
- select {
- case <-t.shutdown:
- // Ticker is done; remove it from list.
- if prev == nil {
- tickers = t.next
- } else {
- prev.next = t.next
- }
- continue
- default:
- }
- if t.nextTick <= now {
- if len(t.c) == 0 {
- // Only send if there's room. We must not block.
- // The channel is allocated with a one-element
- // buffer, which is sufficient: if he hasn't picked
- // up the last tick, no point in sending more.
- t.c <- now
- }
- t.nextTick += t.ns
- if t.nextTick <= now {
- // Still behind; advance in one big step.
- t.nextTick += (now - t.nextTick + t.ns) / t.ns * t.ns
- }
- }
- if t.nextTick < wakeTime {
- wakeTime = t.nextTick
- }
- prev = t
- }
- if tickers != nil {
- // Please send wakeup at earliest required time.
- // If there are no tickers, don't bother.
- alarm.wakeTime = wakeTime
- alarm.wakeMeAt <- wakeTime
- } else {
- alarm.wakeTime = 0
- }
- }
- }
-}
-
-var onceStartTickerLoop sync.Once
-
-// NewTicker returns a new Ticker containing a channel that will
-// send the time, in nanoseconds, every ns nanoseconds. It adjusts the
-// intervals to make up for pauses in delivery of the ticks. The value of
-// ns must be greater than zero; if not, NewTicker will panic.
-func NewTicker(ns int64) *Ticker {
- if ns <= 0 {
- panic(os.NewError("non-positive interval for NewTicker"))
- }
- c := make(chan int64, 1) // See comment on send in tickerLoop
- t := &Ticker{
- C: c,
- c: c,
- ns: ns,
- shutdown: make(chan bool, 1),
- nextTick: Nanoseconds() + ns,
- }
- onceStartTickerLoop.Do(startTickerLoop)
- // must be run in background so global Tickers can be created
- go func() { newTicker <- t }()
- return t
+ return NewTicker(d).C
}
diff --git a/src/pkg/time/tick_test.go b/src/pkg/time/tick_test.go
index 4dcb63956..d8a086ceb 100644
--- a/src/pkg/time/tick_test.go
+++ b/src/pkg/time/tick_test.go
@@ -10,22 +10,20 @@ import (
)
func TestTicker(t *testing.T) {
- const (
- Delta = 100 * 1e6
- Count = 10
- )
+ const Count = 10
+ Delta := 100 * Millisecond
ticker := NewTicker(Delta)
- t0 := Nanoseconds()
+ t0 := Now()
for i := 0; i < Count; i++ {
<-ticker.C
}
ticker.Stop()
- t1 := Nanoseconds()
- ns := t1 - t0
- target := int64(Delta * Count)
+ t1 := Now()
+ dt := t1.Sub(t0)
+ target := Delta * Count
slop := target * 2 / 10
- if ns < target-slop || ns > target+slop {
- t.Fatalf("%d ticks of %g ns took %g ns, expected %g", Count, float64(Delta), float64(ns), float64(target))
+ if dt < target-slop || (!testing.Short() && dt > target+slop) {
+ t.Fatalf("%d %s ticks took %s, expected [%s,%s]", Count, Delta, dt, target-slop, target+slop)
}
// Now test that the ticker stopped
Sleep(2 * Delta)
@@ -39,8 +37,12 @@ func TestTicker(t *testing.T) {
// Test that a bug tearing down a ticker has been fixed. This routine should not deadlock.
func TestTeardown(t *testing.T) {
+ Delta := 100 * Millisecond
+ if testing.Short() {
+ Delta = 20 * Millisecond
+ }
for i := 0; i < 3; i++ {
- ticker := NewTicker(1e8)
+ ticker := NewTicker(Delta)
<-ticker.C
ticker.Stop()
}
diff --git a/src/pkg/time/time.go b/src/pkg/time/time.go
index 0e05da484..473bc2a45 100644
--- a/src/pkg/time/time.go
+++ b/src/pkg/time/time.go
@@ -3,11 +3,112 @@
// license that can be found in the LICENSE file.
// Package time provides functionality for measuring and displaying time.
+//
+// The calendrical calculations always assume a Gregorian calendar.
package time
-// Days of the week.
+import "errors"
+
+// A Time represents an instant in time with nanosecond precision.
+//
+// Programs using times should typically store and pass them as values,
+// not pointers. That is, time variables and struct fields should be of
+// type time.Time, not *time.Time. A Time value can be used by
+// multiple goroutines simultaneously.
+//
+// Time instants can be compared using the Before, After, and Equal methods.
+// The Sub method subtracts two instants, producing a Duration.
+// The Add method adds a Time and a Duration, producing a Time.
+//
+// The zero value of type Time is January 1, year 1, 00:00:00.000000000 UTC.
+// As this time is unlikely to come up in practice, the IsZero method gives
+// a simple way of detecting a time that has not been initialized explicitly.
+//
+// Each Time has associated with it a Location, consulted when computing the
+// presentation form of the time, such as in the Format, Hour, and Year methods.
+// The methods Local, UTC, and In return a Time with a specific location.
+// Changing the location in this way changes only the presentation; it does not
+// change the instant in time being denoted and therefore does not affect the
+// computations described in earlier paragraphs.
+//
+type Time struct {
+ // sec gives the number of seconds elapsed since
+ // January 1, year 1 00:00:00 UTC.
+ sec int64
+
+ // nsec specifies a non-negative nanosecond
+ // offset within the second named by Seconds.
+ // It must be in the range [0, 999999999].
+ nsec int32
+
+ // loc specifies the Location that should be used to
+ // determine the minute, hour, month, day, and year
+ // that correspond to this Time.
+ // Only the zero Time has a nil Location.
+ // In that case it is interpreted to mean UTC.
+ loc *Location
+}
+
+// After reports whether the time instant t is after u.
+func (t Time) After(u Time) bool {
+ return t.sec > u.sec || t.sec == u.sec && t.nsec > u.nsec
+}
+
+// Before reports whether the time instant t is before u.
+func (t Time) Before(u Time) bool {
+ return t.sec < u.sec || t.sec == u.sec && t.nsec < u.nsec
+}
+
+// Equal reports whether t and u represent the same time instant.
+// Two times can be equal even if they are in different locations.
+// For example, 6:00 +0200 CEST and 4:00 UTC are Equal.
+// This comparison is different from using t == u, which also compares
+// the locations.
+func (t Time) Equal(u Time) bool {
+ return t.sec == u.sec && t.nsec == u.nsec
+}
+
+// A Month specifies a month of the year (January = 1, ...).
+type Month int
+
const (
- Sunday = iota
+ January Month = 1 + iota
+ February
+ March
+ April
+ May
+ June
+ July
+ August
+ September
+ October
+ November
+ December
+)
+
+var months = [...]string{
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December",
+}
+
+// String returns the English name of the month ("January", "February", ...).
+func (m Month) String() string { return months[m-1] }
+
+// A Weekday specifies a day of the week (Sunday = 0, ...).
+type Weekday int
+
+const (
+ Sunday Weekday = iota
Monday
Tuesday
Wednesday
@@ -16,215 +117,871 @@ const (
Saturday
)
-// Time is the struct representing a parsed time value.
-type Time struct {
- Year int64 // 2006 is 2006
- Month, Day int // Jan-2 is 1, 2
- Hour, Minute, Second int // 15:04:05 is 15, 4, 5.
- Nanosecond int // Fractional second.
- Weekday int // Sunday, Monday, ...
- ZoneOffset int // seconds east of UTC, e.g. -7*60*60 for -0700
- Zone string // e.g., "MST"
+var days = [...]string{
+ "Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday",
}
-var nonleapyear = []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
-var leapyear = []int{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}
+// String returns the English name of the day ("Sunday", "Monday", ...).
+func (d Weekday) String() string { return days[d] }
+
+// Computations on time.
+//
+// The zero value for a Time is defined to be
+// January 1, year 1, 00:00:00.000000000 UTC
+// which (1) looks like a zero, or as close as you can get in a date
+// (1-1-1 00:00:00 UTC), (2) is unlikely enough to arise in practice to
+// be a suitable "not set" sentinel, unlike Jan 1 1970, and (3) has a
+// non-negative year even in time zones west of UTC, unlike 1-1-0
+// 00:00:00 UTC, which would be 12-31-(-1) 19:00:00 in New York.
+//
+// The zero Time value does not force a specific epoch for the time
+// representation. For example, to use the Unix epoch internally, we
+// could define that to distinguish a zero value from Jan 1 1970, that
+// time would be represented by sec=-1, nsec=1e9. However, it does
+// suggest a representation, namely using 1-1-1 00:00:00 UTC as the
+// epoch, and that's what we do.
+//
+// The Add and Sub computations are oblivious to the choice of epoch.
+//
+// The presentation computations - year, month, minute, and so on - all
+// rely heavily on division and modulus by positive constants. For
+// calendrical calculations we want these divisions to round down, even
+// for negative values, so that the remainder is always positive, but
+// Go's division (like most hardware division instructions) rounds to
+// zero. We can still do those computations and then adjust the result
+// for a negative numerator, but it's annoying to write the adjustment
+// over and over. Instead, we can change to a different epoch so long
+// ago that all the times we care about will be positive, and then round
+// to zero and round down coincide. These presentation routines already
+// have to add the zone offset, so adding the translation to the
+// alternate epoch is cheap. For example, having a non-negative time t
+// means that we can write
+//
+// sec = t % 60
+//
+// instead of
+//
+// sec = t % 60
+// if sec < 0 {
+// sec += 60
+// }
+//
+// everywhere.
+//
+// The calendar runs on an exact 400 year cycle: a 400-year calendar
+// printed for 1970-2469 will apply as well to 2470-2869. Even the days
+// of the week match up. It simplifies the computations to choose the
+// cycle boundaries so that the exceptional years are always delayed as
+// long as possible. That means choosing a year equal to 1 mod 400, so
+// that the first leap year is the 4th year, the first missed leap year
+// is the 100th year, and the missed missed leap year is the 400th year.
+// So we'd prefer instead to print a calendar for 2001-2400 and reuse it
+// for 2401-2800.
+//
+// Finally, it's convenient if the delta between the Unix epoch and
+// long-ago epoch is representable by an int64 constant.
+//
+// These three considerations—choose an epoch as early as possible, that
+// uses a year equal to 1 mod 400, and that is no more than 2⁶³ seconds
+// earlier than 1970—bring us to the year -292277022399. We refer to
+// this year as the absolute zero year, and to times measured as a uint64
+// seconds since this year as absolute times.
+//
+// Times measured as an int64 seconds since the year 1—the representation
+// used for Time's sec field—are called internal times.
+//
+// Times measured as an int64 seconds since the year 1970 are called Unix
+// times.
+//
+// It is tempting to just use the year 1 as the absolute epoch, defining
+// that the routines are only valid for years >= 1. However, the
+// routines would then be invalid when displaying the epoch in time zones
+// west of UTC, since it is year 0. It doesn't seem tenable to say that
+// printing the zero time correctly isn't supported in half the time
+// zones. By comparison, it's reasonable to mishandle some times in
+// the year -292277022399.
+//
+// All this is opaque to clients of the API and can be changed if a
+// better implementation presents itself.
-func months(year int64) []int {
- if year%4 == 0 && (year%100 != 0 || year%400 == 0) {
- return leapyear
+const (
+ // The unsigned zero year for internal calculations.
+ // Must be 1 mod 400, and times before it will not compute correctly,
+ // but otherwise can be changed at will.
+ absoluteZeroYear = -292277022399
+
+ // The year of the zero Time.
+ // Assumed by the unixToInternal computation below.
+ internalYear = 1
+
+ // The year of the zero Unix time.
+ unixYear = 1970
+
+ // Offsets to convert between internal and absolute or Unix times.
+ absoluteToInternal int64 = (absoluteZeroYear - internalYear) * 365.2425 * secondsPerDay
+ internalToAbsolute = -absoluteToInternal
+
+ unixToInternal int64 = (1969*365 + 1969/4 - 1969/100 + 1969/400) * secondsPerDay
+ internalToUnix int64 = -unixToInternal
+)
+
+// IsZero reports whether t represents the zero time instant,
+// January 1, year 1, 00:00:00 UTC.
+func (t Time) IsZero() bool {
+ return t.sec == 0 && t.nsec == 0
+}
+
+// abs returns the time t as an absolute time, adjusted by the zone offset.
+// It is called when computing a presentation property like Month or Hour.
+func (t Time) abs() uint64 {
+ l := t.loc
+ if l == nil {
+ l = &utcLoc
}
- return nonleapyear
+ // Avoid function call if we hit the local time cache.
+ sec := t.sec + internalToUnix
+ if l != &utcLoc {
+ if l.cacheZone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
+ sec += int64(l.cacheZone.offset)
+ } else {
+ _, offset, _, _, _ := l.lookup(sec)
+ sec += int64(offset)
+ }
+ }
+ return uint64(sec + (unixToInternal + internalToAbsolute))
+}
+
+// Date returns the year, month, and day in which t occurs.
+func (t Time) Date() (year int, month Month, day int) {
+ year, month, day, _ = t.date(true)
+ return
+}
+
+// Year returns the year in which t occurs.
+func (t Time) Year() int {
+ year, _, _, _ := t.date(false)
+ return year
+}
+
+// Month returns the month of the year specified by t.
+func (t Time) Month() Month {
+ _, month, _, _ := t.date(true)
+ return month
}
+// Day returns the day of the month specified by t.
+func (t Time) Day() int {
+ _, _, day, _ := t.date(true)
+ return day
+}
+
+// Weekday returns the day of the week specified by t.
+func (t Time) Weekday() Weekday {
+ // January 1 of the absolute year, like January 1 of 2001, was a Monday.
+ sec := (t.abs() + uint64(Monday)*secondsPerDay) % secondsPerWeek
+ return Weekday(int(sec) / secondsPerDay)
+}
+
+// ISOWeek returns the ISO 8601 year and week number in which t occurs.
+// Week ranges from 1 to 53. Jan 01 to Jan 03 of year n might belong to
+// week 52 or 53 of year n-1, and Dec 29 to Dec 31 might belong to week 1
+// of year n+1.
+func (t Time) ISOWeek() (year, week int) {
+ year, month, day, yday := t.date(true)
+ wday := int(t.Weekday()+6) % 7 // weekday but Monday = 0.
+ const (
+ Mon int = iota
+ Tue
+ Wed
+ Thu
+ Fri
+ Sat
+ Sun
+ )
+
+ // Calculate week as number of Mondays in year up to
+ // and including today, plus 1 because the first week is week 0.
+ // Putting the + 1 inside the numerator as a + 7 keeps the
+ // numerator from being negative, which would cause it to
+ // round incorrectly.
+ week = (yday - wday + 7) / 7
+
+ // The week number is now correct under the assumption
+ // that the first Monday of the year is in week 1.
+ // If Jan 1 is a Tuesday, Wednesday, or Thursday, the first Monday
+ // is actually in week 2.
+ jan1wday := (wday - yday + 7*53) % 7
+ if Tue <= jan1wday && jan1wday <= Thu {
+ week++
+ }
+
+ // If the week number is still 0, we're in early January but in
+ // the last week of last year.
+ if week == 0 {
+ year--
+ week = 52
+ // A year has 53 weeks when Jan 1 or Dec 31 is a Thursday,
+ // meaning Jan 1 of the next year is a Friday
+ // or it was a leap year and Jan 1 of the next year is a Saturday.
+ if jan1wday == Fri || (jan1wday == Sat && isLeap(year)) {
+ week++
+ }
+ }
+
+ // December 29 to 31 are in week 1 of next year if
+ // they are after the last Thursday of the year and
+ // December 31 is a Monday, Tuesday, or Wednesday.
+ if month == December && day >= 29 && wday < Thu {
+ if dec31wday := (wday + 31 - day) % 7; Mon <= dec31wday && dec31wday <= Wed {
+ year++
+ week = 1
+ }
+ }
+
+ return
+}
+
+// Clock returns the hour, minute, and second within the day specified by t.
+func (t Time) Clock() (hour, min, sec int) {
+ sec = int(t.abs() % secondsPerDay)
+ hour = sec / secondsPerHour
+ sec -= hour * secondsPerHour
+ min = sec / secondsPerMinute
+ sec -= min * secondsPerMinute
+ return
+}
+
+// Hour returns the hour within the day specified by t, in the range [0, 23].
+func (t Time) Hour() int {
+ return int(t.abs()%secondsPerDay) / secondsPerHour
+}
+
+// Minute returns the minute offset within the hour specified by t, in the range [0, 59].
+func (t Time) Minute() int {
+ return int(t.abs()%secondsPerHour) / secondsPerMinute
+}
+
+// Second returns the second offset within the minute specified by t, in the range [0, 59].
+func (t Time) Second() int {
+ return int(t.abs() % secondsPerMinute)
+}
+
+// Nanosecond returns the nanosecond offset within the second specified by t,
+// in the range [0, 999999999].
+func (t Time) Nanosecond() int {
+ return int(t.nsec)
+}
+
+// A Duration represents the elapsed time between two instants
+// as an int64 nanosecond count. The representation limits the
+// largest representable duration to approximately 290 years.
+type Duration int64
+
+// Common durations. There is no definition for units of Day or larger
+// to avoid confusion across daylight savings time zone transitions.
+//
+// To count the number of units in a Duration, divide:
+// second := time.Second
+// fmt.Print(int64(second/time.Millisecond)) // prints 1000
+//
+// To convert an integer number of units to a Duration, multiply:
+// seconds := 10
+// fmt.Print(time.Duration(seconds)*time.Second) // prints 10s
+//
const (
- secondsPerDay = 24 * 60 * 60
- daysPer400Years = 365*400 + 97
- daysPer100Years = 365*100 + 24
- daysPer4Years = 365*4 + 1
- days1970To2001 = 31*365 + 8
+ Nanosecond Duration = 1
+ Microsecond = 1000 * Nanosecond
+ Millisecond = 1000 * Microsecond
+ Second = 1000 * Millisecond
+ Minute = 60 * Second
+ Hour = 60 * Minute
)
-// SecondsToUTC converts sec, in number of seconds since the Unix epoch,
-// into a parsed Time value in the UTC time zone.
-func SecondsToUTC(sec int64) *Time {
- t := new(Time)
+// Duration returns a string representing the duration in the form "72h3m0.5s".
+// Leading zero units are omitted. As a special case, durations less than one
+// second format use a smaller unit (milli-, micro-, or nanoseconds) to ensure
+// that the leading digit is non-zero. The zero duration formats as 0,
+// with no unit.
+func (d Duration) String() string {
+ // Largest time is 2540400h10m10.000000000s
+ var buf [32]byte
+ w := len(buf)
+
+ u := uint64(d)
+ neg := d < 0
+ if neg {
+ u = -u
+ }
- // Split into time and day.
- day := sec / secondsPerDay
- sec -= day * secondsPerDay
- if sec < 0 {
- day--
- sec += secondsPerDay
+ if u < uint64(Second) {
+ // Special case: if duration is smaller than a second,
+ // use smaller units, like 1.2ms
+ var (
+ prec int
+ unit byte
+ )
+ switch {
+ case u == 0:
+ return "0"
+ case u < uint64(Microsecond):
+ // print nanoseconds
+ prec = 0
+ unit = 'n'
+ case u < uint64(Millisecond):
+ // print microseconds
+ prec = 3
+ unit = 'u'
+ default:
+ // print milliseconds
+ prec = 6
+ unit = 'm'
+ }
+ w -= 2
+ buf[w] = unit
+ buf[w+1] = 's'
+ w, u = fmtFrac(buf[:w], u, prec)
+ w = fmtInt(buf[:w], u)
+ } else {
+ w--
+ buf[w] = 's'
+
+ w, u = fmtFrac(buf[:w], u, 9)
+
+ // u is now integer seconds
+ w = fmtInt(buf[:w], u%60)
+ u /= 60
+
+ // u is now integer minutes
+ if u > 0 {
+ w--
+ buf[w] = 'm'
+ w = fmtInt(buf[:w], u%60)
+ u /= 60
+
+ // u is now integer hours
+ // Stop at hours because days can be different lengths.
+ if u > 0 {
+ w--
+ buf[w] = 'h'
+ w = fmtInt(buf[:w], u)
+ }
+ }
+ }
+
+ if neg {
+ w--
+ buf[w] = '-'
}
- // Time
- t.Hour = int(sec / 3600)
- t.Minute = int((sec / 60) % 60)
- t.Second = int(sec % 60)
+ return string(buf[w:])
+}
- // Day 0 = January 1, 1970 was a Thursday
- t.Weekday = int((day + Thursday) % 7)
- if t.Weekday < 0 {
- t.Weekday += 7
+// fmtFrac formats the fraction of v/10**prec (e.g., ".12345") into the
+// tail of buf, omitting trailing zeros. it omits the decimal
+// point too when the fraction is 0. It returns the index where the
+// output bytes begin and the value v/10**prec.
+func fmtFrac(buf []byte, v uint64, prec int) (nw int, nv uint64) {
+ // Omit trailing zeros up to and including decimal point.
+ w := len(buf)
+ print := false
+ for i := 0; i < prec; i++ {
+ digit := v % 10
+ print = print || digit != 0
+ if print {
+ w--
+ buf[w] = byte(digit) + '0'
+ }
+ v /= 10
}
+ if print {
+ w--
+ buf[w] = '.'
+ }
+ return w, v
+}
+
+// fmtInt formats v into the tail of buf.
+// It returns the index where the output begins.
+func fmtInt(buf []byte, v uint64) int {
+ w := len(buf)
+ if v == 0 {
+ w--
+ buf[w] = '0'
+ } else {
+ for v > 0 {
+ w--
+ buf[w] = byte(v%10) + '0'
+ v /= 10
+ }
+ }
+ return w
+}
+
+// Nanoseconds returns the duration as an integer nanosecond count.
+func (d Duration) Nanoseconds() int64 { return int64(d) }
+
+// These methods return float64 because the dominant
+// use case is for printing a floating point number like 1.5s, and
+// a truncation to integer would make them not useful in those cases.
+// Splitting the integer and fraction ourselves guarantees that
+// converting the returned float64 to an integer rounds the same
+// way that a pure integer conversion would have, even in cases
+// where, say, float64(d.Nanoseconds())/1e9 would have rounded
+// differently.
+
+// Seconds returns the duration as a floating point number of seconds.
+func (d Duration) Seconds() float64 {
+ sec := d / Second
+ nsec := d % Second
+ return float64(sec) + float64(nsec)*1e-9
+}
- // Change day from 0 = 1970 to 0 = 2001,
- // to make leap year calculations easier
- // (2001 begins 4-, 100-, and 400-year cycles ending in a leap year.)
- day -= days1970To2001
+// Minutes returns the duration as a floating point number of minutes.
+func (d Duration) Minutes() float64 {
+ min := d / Minute
+ nsec := d % Minute
+ return float64(min) + float64(nsec)*(1e-9/60)
+}
- year := int64(2001)
- if day < 0 {
- // Go back enough 400 year cycles to make day positive.
- n := -day/daysPer400Years + 1
- year -= 400 * n
- day += daysPer400Years * n
+// Hours returns the duration as a floating point number of hours.
+func (d Duration) Hours() float64 {
+ hour := d / Hour
+ nsec := d % Hour
+ return float64(hour) + float64(nsec)*(1e-9/60/60)
+}
+
+// Add returns the time t+d.
+func (t Time) Add(d Duration) Time {
+ t.sec += int64(d / 1e9)
+ t.nsec += int32(d % 1e9)
+ if t.nsec >= 1e9 {
+ t.sec++
+ t.nsec -= 1e9
+ } else if t.nsec < 0 {
+ t.sec--
+ t.nsec += 1e9
}
+ return t
+}
- // Cut off 400 year cycles.
- n := day / daysPer400Years
- year += 400 * n
- day -= daysPer400Years * n
+// Sub returns the duration t-u.
+// To compute t-d for a duration d, use t.Add(-d).
+func (t Time) Sub(u Time) Duration {
+ return Duration(t.sec-u.sec)*Second + Duration(t.nsec-u.nsec)
+}
+
+// Since returns the time elapsed since t.
+// It is shorthand for time.Now().Sub(t).
+func Since(t Time) Duration {
+ return Now().Sub(t)
+}
+
+// AddDate returns the time corresponding to adding the
+// given number of years, months, and days to t.
+// For example, AddDate(-1, 2, 3) applied to January 1, 2011
+// returns March 4, 2010.
+//
+// AddDate normalizes its result in the same way that Date does,
+// so, for example, adding one month to October 31 yields
+// December 1, the normalized form for November 31.
+func (t Time) AddDate(years int, months int, days int) Time {
+ year, month, day := t.Date()
+ hour, min, sec := t.Clock()
+ return Date(year+years, month+Month(months), day+days, hour, min, sec, int(t.nsec), t.loc)
+}
- // Cut off 100-year cycles
- n = day / daysPer100Years
- if n > 3 { // happens on last day of 400th year
- n = 3
+const (
+ secondsPerMinute = 60
+ secondsPerHour = 60 * 60
+ secondsPerDay = 24 * secondsPerHour
+ secondsPerWeek = 7 * secondsPerDay
+ daysPer400Years = 365*400 + 97
+ daysPer100Years = 365*100 + 24
+ daysPer4Years = 365*4 + 1
+ days1970To2001 = 31*365 + 8
+)
+
+// date computes the year and, only when full=true,
+// the month and day in which t occurs.
+func (t Time) date(full bool) (year int, month Month, day int, yday int) {
+ // Split into time and day.
+ d := t.abs() / secondsPerDay
+
+ // Account for 400 year cycles.
+ n := d / daysPer400Years
+ y := 400 * n
+ d -= daysPer400Years * n
+
+ // Cut off 100-year cycles.
+ // The last cycle has one extra leap year, so on the last day
+ // of that year, day / daysPer100Years will be 4 instead of 3.
+ // Cut it back down to 3 by subtracting n>>2.
+ n = d / daysPer100Years
+ n -= n >> 2
+ y += 100 * n
+ d -= daysPer100Years * n
+
+ // Cut off 4-year cycles.
+ // The last cycle has a missing leap year, which does not
+ // affect the computation.
+ n = d / daysPer4Years
+ y += 4 * n
+ d -= daysPer4Years * n
+
+ // Cut off years within a 4-year cycle.
+ // The last year is a leap year, so on the last day of that year,
+ // day / 365 will be 4 instead of 3. Cut it back down to 3
+ // by subtracting n>>2.
+ n = d / 365
+ n -= n >> 2
+ y += n
+ d -= 365 * n
+
+ year = int(int64(y) + absoluteZeroYear)
+ yday = int(d)
+
+ if !full {
+ return
}
- year += 100 * n
- day -= daysPer100Years * n
- // Cut off 4-year cycles
- n = day / daysPer4Years
- if n > 24 { // happens on last day of 100th year
- n = 24
+ day = yday
+ if isLeap(year) {
+ // Leap year
+ switch {
+ case day > 31+29-1:
+ // After leap day; pretend it wasn't there.
+ day--
+ case day == 31+29-1:
+ // Leap day.
+ month = February
+ day = 29
+ return
+ }
}
- year += 4 * n
- day -= daysPer4Years * n
- // Cut off non-leap years.
- n = day / 365
- if n > 3 { // happens on last day of 4th year
- n = 3
+ // Estimate month on assumption that every month has 31 days.
+ // The estimate may be too low by at most one month, so adjust.
+ month = Month(day / 31)
+ end := int(daysBefore[month+1])
+ var begin int
+ if day >= end {
+ month++
+ begin = end
+ } else {
+ begin = int(daysBefore[month])
}
- year += n
- day -= 365 * n
- t.Year = year
+ month++ // because January is 1
+ day = day - begin + 1
+ return
+}
- // If someone ever needs yearday,
- // tyearday = day (+1?)
+// daysBefore[m] counts the number of days in a non-leap year
+// before month m begins. There is an entry for m=12, counting
+// the number of days before January of next year (365).
+var daysBefore = [...]int32{
+ 0,
+ 31,
+ 31 + 28,
+ 31 + 28 + 31,
+ 31 + 28 + 31 + 30,
+ 31 + 28 + 31 + 30 + 31,
+ 31 + 28 + 31 + 30 + 31 + 30,
+ 31 + 28 + 31 + 30 + 31 + 30 + 31,
+ 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31,
+ 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30,
+ 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31,
+ 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30,
+ 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31,
+}
- months := months(year)
- var m int
- yday := int(day)
- for m = 0; m < 12 && yday >= months[m]; m++ {
- yday -= months[m]
+func daysIn(m Month, year int) int {
+ if m == February && isLeap(year) {
+ return 29
}
- t.Month = m + 1
- t.Day = yday + 1
- t.Zone = "UTC"
+ return int(daysBefore[m] - daysBefore[m-1])
+}
- return t
+// Provided by package runtime.
+func now() (sec int64, nsec int32)
+
+// Now returns the current local time.
+func Now() Time {
+ sec, nsec := now()
+ return Time{sec + unixToInternal, nsec, Local}
}
-// NanosecondsToUTC converts nsec, in number of nanoseconds since the Unix epoch,
-// into a parsed Time value in the UTC time zone.
-func NanosecondsToUTC(nsec int64) *Time {
- // This one calls SecondsToUTC rather than the other way around because
- // that admits a much larger span of time; NanosecondsToUTC is limited
- // to a few hundred years only.
- t := SecondsToUTC(nsec / 1e9)
- t.Nanosecond = int(nsec % 1e9)
+// UTC returns t with the location set to UTC.
+func (t Time) UTC() Time {
+ t.loc = UTC
return t
}
-// UTC returns the current time as a parsed Time value in the UTC time zone.
-func UTC() *Time { return NanosecondsToUTC(Nanoseconds()) }
-
-// SecondsToLocalTime converts sec, in number of seconds since the Unix epoch,
-// into a parsed Time value in the local time zone.
-func SecondsToLocalTime(sec int64) *Time {
- z, offset := lookupTimezone(sec)
- t := SecondsToUTC(sec + int64(offset))
- t.Zone = z
- t.ZoneOffset = offset
+// Local returns t with the location set to local time.
+func (t Time) Local() Time {
+ t.loc = Local
return t
}
-// NanosecondsToLocalTime converts nsec, in number of nanoseconds since the Unix epoch,
-// into a parsed Time value in the local time zone.
-func NanosecondsToLocalTime(nsec int64) *Time {
- t := SecondsToLocalTime(nsec / 1e9)
- t.Nanosecond = int(nsec % 1e9)
+// In returns t with the location information set to loc.
+//
+// In panics if loc is nil.
+func (t Time) In(loc *Location) Time {
+ if loc == nil {
+ panic("time: missing Location in call to Time.In")
+ }
+ t.loc = loc
return t
}
-// LocalTime returns the current time as a parsed Time value in the local time zone.
-func LocalTime() *Time { return NanosecondsToLocalTime(Nanoseconds()) }
+// Location returns the time zone information associated with t.
+func (t Time) Location() *Location {
+ l := t.loc
+ if l == nil {
+ l = UTC
+ }
+ return l
+}
+
+// Zone computes the time zone in effect at time t, returning the abbreviated
+// name of the zone (such as "CET") and its offset in seconds east of UTC.
+func (t Time) Zone() (name string, offset int) {
+ name, offset, _, _, _ = t.loc.lookup(t.sec + internalToUnix)
+ return
+}
+
+// Unix returns t as a Unix time, the number of seconds elapsed
+// since January 1, 1970 UTC.
+func (t Time) Unix() int64 {
+ return t.sec + internalToUnix
+}
+
+// UnixNano returns t as a Unix time, the number of nanoseconds elapsed
+// since January 1, 1970 UTC.
+func (t Time) UnixNano() int64 {
+ return (t.sec+internalToUnix)*1e9 + int64(t.nsec)
+}
+
+const timeGobVersion byte = 1
+
+// GobEncode implements the gob.GobEncoder interface.
+func (t Time) GobEncode() ([]byte, error) {
+ var offsetMin int16 // minutes east of UTC. -1 is UTC.
+
+ if t.Location() == &utcLoc {
+ offsetMin = -1
+ } else {
+ _, offset := t.Zone()
+ if offset%60 != 0 {
+ return nil, errors.New("Time.GobEncode: zone offset has fractional minute")
+ }
+ offset /= 60
+ if offset < -32768 || offset == -1 || offset > 32767 {
+ return nil, errors.New("Time.GobEncode: unexpected zone offset")
+ }
+ offsetMin = int16(offset)
+ }
+
+ enc := []byte{
+ timeGobVersion, // byte 0 : version
+ byte(t.sec >> 56), // bytes 1-8: seconds
+ byte(t.sec >> 48),
+ byte(t.sec >> 40),
+ byte(t.sec >> 32),
+ byte(t.sec >> 24),
+ byte(t.sec >> 16),
+ byte(t.sec >> 8),
+ byte(t.sec),
+ byte(t.nsec >> 24), // bytes 9-12: nanoseconds
+ byte(t.nsec >> 16),
+ byte(t.nsec >> 8),
+ byte(t.nsec),
+ byte(offsetMin >> 8), // bytes 13-14: zone offset in minutes
+ byte(offsetMin),
+ }
+
+ return enc, nil
+}
+
+// GobDecode implements the gob.GobDecoder interface.
+func (t *Time) GobDecode(buf []byte) error {
+ if len(buf) == 0 {
+ return errors.New("Time.GobDecode: no data")
+ }
+
+ if buf[0] != timeGobVersion {
+ return errors.New("Time.GobDecode: unsupported version")
+ }
+
+ if len(buf) != /*version*/ 1+ /*sec*/ 8+ /*nsec*/ 4+ /*zone offset*/ 2 {
+ return errors.New("Time.GobDecode: invalid length")
+ }
+
+ buf = buf[1:]
+ t.sec = int64(buf[7]) | int64(buf[6])<<8 | int64(buf[5])<<16 | int64(buf[4])<<24 |
+ int64(buf[3])<<32 | int64(buf[2])<<40 | int64(buf[1])<<48 | int64(buf[0])<<56
+
+ buf = buf[8:]
+ t.nsec = int32(buf[3]) | int32(buf[2])<<8 | int32(buf[1])<<16 | int32(buf[0])<<24
+
+ buf = buf[4:]
+ offset := int(int16(buf[1])|int16(buf[0])<<8) * 60
+
+ if offset == -1*60 {
+ t.loc = &utcLoc
+ } else if _, localoff, _, _, _ := Local.lookup(t.sec + internalToUnix); offset == localoff {
+ t.loc = Local
+ } else {
+ t.loc = FixedZone("", offset)
+ }
+
+ return nil
+}
+
+// MarshalJSON implements the json.Marshaler interface.
+// Time is formatted as RFC3339.
+func (t Time) MarshalJSON() ([]byte, error) {
+ if y := t.Year(); y < 0 || y >= 10000 {
+ return nil, errors.New("Time.MarshalJSON: year outside of range [0,9999]")
+ }
+ return []byte(t.Format(`"` + RFC3339Nano + `"`)), nil
+}
+
+// UnmarshalJSON implements the json.Unmarshaler interface.
+// Time is expected in RFC3339 format.
+func (t *Time) UnmarshalJSON(data []byte) (err error) {
+ // Fractional seconds are handled implicitly by Parse.
+ *t, err = Parse(`"`+RFC3339+`"`, string(data))
+ return
+}
-// Seconds returns the number of seconds since January 1, 1970 represented by the
-// parsed Time value.
-func (t *Time) Seconds() int64 {
- // First, accumulate days since January 1, 2001.
- // Using 2001 instead of 1970 makes the leap-year
- // handling easier (see SecondsToUTC), because
- // it is at the beginning of the 4-, 100-, and 400-year cycles.
- day := int64(0)
+// Unix returns the local Time corresponding to the given Unix time,
+// sec seconds and nsec nanoseconds since January 1, 1970 UTC.
+// It is valid to pass nsec outside the range [0, 999999999].
+func Unix(sec int64, nsec int64) Time {
+ if nsec < 0 || nsec >= 1e9 {
+ n := nsec / 1e9
+ sec += n
+ nsec -= n * 1e9
+ if nsec < 0 {
+ nsec += 1e9
+ sec--
+ }
+ }
+ return Time{sec + unixToInternal, int32(nsec), Local}
+}
+
+func isLeap(year int) bool {
+ return year%4 == 0 && (year%100 != 0 || year%400 == 0)
+}
+
+// norm returns nhi, nlo such that
+// hi * base + lo == nhi * base + nlo
+// 0 <= nlo < base
+func norm(hi, lo, base int) (nhi, nlo int) {
+ if lo < 0 {
+ n := (-lo-1)/base + 1
+ hi -= n
+ lo += n * base
+ }
+ if lo >= base {
+ n := lo / base
+ hi += n
+ lo -= n * base
+ }
+ return hi, lo
+}
- // Rewrite year to be >= 2001.
- year := t.Year
- if year < 2001 {
- n := (2001-year)/400 + 1
- year += 400 * n
- day -= daysPer400Years * n
+// Date returns the Time corresponding to
+// yyyy-mm-dd hh:mm:ss + nsec nanoseconds
+// in the appropriate zone for that time in the given location.
+//
+// The month, day, hour, min, sec, and nsec values may be outside
+// their usual ranges and will be normalized during the conversion.
+// For example, October 32 converts to November 1.
+//
+// A daylight savings time transition skips or repeats times.
+// For example, in the United States, March 13, 2011 2:15am never occurred,
+// while November 6, 2011 1:15am occurred twice. In such cases, the
+// choice of time zone, and therefore the time, is not well-defined.
+// Date returns a time that is correct in one of the two zones involved
+// in the transition, but it does not guarantee which.
+//
+// Date panics if loc is nil.
+func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) Time {
+ if loc == nil {
+ panic("time: missing Location in call to Date")
}
+ // Normalize month, overflowing into year.
+ m := int(month) - 1
+ year, m = norm(year, m, 12)
+ month = Month(m) + 1
+
+ // Normalize nsec, sec, min, hour, overflowing into day.
+ sec, nsec = norm(sec, nsec, 1e9)
+ min, sec = norm(min, sec, 60)
+ hour, min = norm(hour, min, 60)
+ day, hour = norm(day, hour, 24)
+
+ y := uint64(int64(year) - absoluteZeroYear)
+
+ // Compute days since the absolute epoch.
+
// Add in days from 400-year cycles.
- n := (year - 2001) / 400
- year -= 400 * n
- day += daysPer400Years * n
+ n := y / 400
+ y -= 400 * n
+ d := daysPer400Years * n
// Add in 100-year cycles.
- n = (year - 2001) / 100
- year -= 100 * n
- day += daysPer100Years * n
+ n = y / 100
+ y -= 100 * n
+ d += daysPer100Years * n
// Add in 4-year cycles.
- n = (year - 2001) / 4
- year -= 4 * n
- day += daysPer4Years * n
+ n = y / 4
+ y -= 4 * n
+ d += daysPer4Years * n
// Add in non-leap years.
- n = year - 2001
- day += 365 * n
+ n = y
+ d += 365 * n
- // Add in days this year.
- months := months(t.Year)
- for m := 0; m < t.Month-1; m++ {
- day += int64(months[m])
+ // Add in days before this month.
+ d += uint64(daysBefore[month-1])
+ if isLeap(year) && month >= March {
+ d++ // February 29
}
- day += int64(t.Day - 1)
- // Convert days to seconds since January 1, 2001.
- sec := day * secondsPerDay
+ // Add in days before today.
+ d += uint64(day - 1)
// Add in time elapsed today.
- sec += int64(t.Hour) * 3600
- sec += int64(t.Minute) * 60
- sec += int64(t.Second)
-
- // Convert from seconds since 2001 to seconds since 1970.
- sec += days1970To2001 * secondsPerDay
-
- // Account for local time zone.
- sec -= int64(t.ZoneOffset)
- return sec
-}
+ abs := d * secondsPerDay
+ abs += uint64(hour*secondsPerHour + min*secondsPerMinute + sec)
+
+ unix := int64(abs) + (absoluteToInternal + internalToUnix)
+
+ // Look for zone offset for t, so we can adjust to UTC.
+ // The lookup function expects UTC, so we pass t in the
+ // hope that it will not be too close to a zone transition,
+ // and then adjust if it is.
+ _, offset, _, start, end := loc.lookup(unix)
+ if offset != 0 {
+ switch utc := unix - int64(offset); {
+ case utc < start:
+ _, offset, _, _, _ = loc.lookup(start - 1)
+ case utc >= end:
+ _, offset, _, _, _ = loc.lookup(end)
+ }
+ unix -= int64(offset)
+ }
-// Nanoseconds returns the number of nanoseconds since January 1, 1970 represented by the
-// parsed Time value.
-func (t *Time) Nanoseconds() int64 {
- return t.Seconds()*1e9 + int64(t.Nanosecond)
+ return Time{unix + unixToInternal, int32(nsec), loc}
}
diff --git a/src/pkg/time/time_test.go b/src/pkg/time/time_test.go
index dceed491a..3430526b8 100644
--- a/src/pkg/time/time_test.go
+++ b/src/pkg/time/time_test.go
@@ -5,7 +5,11 @@
package time_test
import (
- "os"
+ "bytes"
+ "encoding/gob"
+ "encoding/json"
+ "fmt"
+ "math/rand"
"strconv"
"strings"
"testing"
@@ -13,84 +17,93 @@ import (
. "time"
)
-func init() {
- // Force US Pacific time for daylight-savings
- // tests below (localtests). Needs to be set
- // before the first call into the time library.
- os.Setenv("TZ", "America/Los_Angeles")
-}
-
// We should be in PST/PDT, but if the time zone files are missing we
// won't be. The purpose of this test is to at least explain why some of
// the subsequent tests fail.
func TestZoneData(t *testing.T) {
- lt := LocalTime()
+ lt := Now()
// PST is 8 hours west, PDT is 7 hours west. We could use the name but it's not unique.
- if off := lt.ZoneOffset; off != -8*60*60 && off != -7*60*60 {
- t.Errorf("Unable to find US Pacific time zone data for testing; time zone is %q offset %d", lt.Zone, off)
+ if name, off := lt.Zone(); off != -8*60*60 && off != -7*60*60 {
+ t.Errorf("Unable to find US Pacific time zone data for testing; time zone is %q offset %d", name, off)
t.Error("Likely problem: the time zone files have not been installed.")
}
}
+// parsedTime is the struct representing a parsed time value.
+type parsedTime struct {
+ Year int
+ Month Month
+ Day int
+ Hour, Minute, Second int // 15:04:05 is 15, 4, 5.
+ Nanosecond int // Fractional second.
+ Weekday Weekday
+ ZoneOffset int // seconds east of UTC, e.g. -7*60*60 for -0700
+ Zone string // e.g., "MST"
+}
+
type TimeTest struct {
seconds int64
- golden Time
+ golden parsedTime
}
var utctests = []TimeTest{
- {0, Time{1970, 1, 1, 0, 0, 0, 0, Thursday, 0, "UTC"}},
- {1221681866, Time{2008, 9, 17, 20, 4, 26, 0, Wednesday, 0, "UTC"}},
- {-1221681866, Time{1931, 4, 16, 3, 55, 34, 0, Thursday, 0, "UTC"}},
- {-11644473600, Time{1601, 1, 1, 0, 0, 0, 0, Monday, 0, "UTC"}},
- {599529660, Time{1988, 12, 31, 0, 1, 0, 0, Saturday, 0, "UTC"}},
- {978220860, Time{2000, 12, 31, 0, 1, 0, 0, Sunday, 0, "UTC"}},
- {1e18, Time{31688740476, 10, 23, 1, 46, 40, 0, Friday, 0, "UTC"}},
- {-1e18, Time{-31688736537, 3, 10, 22, 13, 20, 0, Tuesday, 0, "UTC"}},
- {0x7fffffffffffffff, Time{292277026596, 12, 4, 15, 30, 7, 0, Sunday, 0, "UTC"}},
- {-0x8000000000000000, Time{-292277022657, 1, 27, 8, 29, 52, 0, Sunday, 0, "UTC"}},
+ {0, parsedTime{1970, January, 1, 0, 0, 0, 0, Thursday, 0, "UTC"}},
+ {1221681866, parsedTime{2008, September, 17, 20, 4, 26, 0, Wednesday, 0, "UTC"}},
+ {-1221681866, parsedTime{1931, April, 16, 3, 55, 34, 0, Thursday, 0, "UTC"}},
+ {-11644473600, parsedTime{1601, January, 1, 0, 0, 0, 0, Monday, 0, "UTC"}},
+ {599529660, parsedTime{1988, December, 31, 0, 1, 0, 0, Saturday, 0, "UTC"}},
+ {978220860, parsedTime{2000, December, 31, 0, 1, 0, 0, Sunday, 0, "UTC"}},
}
var nanoutctests = []TimeTest{
- {0, Time{1970, 1, 1, 0, 0, 0, 1e8, Thursday, 0, "UTC"}},
- {1221681866, Time{2008, 9, 17, 20, 4, 26, 2e8, Wednesday, 0, "UTC"}},
+ {0, parsedTime{1970, January, 1, 0, 0, 0, 1e8, Thursday, 0, "UTC"}},
+ {1221681866, parsedTime{2008, September, 17, 20, 4, 26, 2e8, Wednesday, 0, "UTC"}},
}
var localtests = []TimeTest{
- {0, Time{1969, 12, 31, 16, 0, 0, 0, Wednesday, -8 * 60 * 60, "PST"}},
- {1221681866, Time{2008, 9, 17, 13, 4, 26, 0, Wednesday, -7 * 60 * 60, "PDT"}},
+ {0, parsedTime{1969, December, 31, 16, 0, 0, 0, Wednesday, -8 * 60 * 60, "PST"}},
+ {1221681866, parsedTime{2008, September, 17, 13, 4, 26, 0, Wednesday, -7 * 60 * 60, "PDT"}},
}
var nanolocaltests = []TimeTest{
- {0, Time{1969, 12, 31, 16, 0, 0, 1e8, Wednesday, -8 * 60 * 60, "PST"}},
- {1221681866, Time{2008, 9, 17, 13, 4, 26, 3e8, Wednesday, -7 * 60 * 60, "PDT"}},
-}
-
-func same(t, u *Time) bool {
- return t.Year == u.Year &&
- t.Month == u.Month &&
- t.Day == u.Day &&
- t.Hour == u.Hour &&
- t.Minute == u.Minute &&
- t.Second == u.Second &&
- t.Nanosecond == u.Nanosecond &&
- t.Weekday == u.Weekday &&
- t.ZoneOffset == u.ZoneOffset &&
- t.Zone == u.Zone
+ {0, parsedTime{1969, December, 31, 16, 0, 0, 1e8, Wednesday, -8 * 60 * 60, "PST"}},
+ {1221681866, parsedTime{2008, September, 17, 13, 4, 26, 3e8, Wednesday, -7 * 60 * 60, "PDT"}},
+}
+
+func same(t Time, u *parsedTime) bool {
+ // Check aggregates.
+ year, month, day := t.Date()
+ hour, min, sec := t.Clock()
+ name, offset := t.Zone()
+ if year != u.Year || month != u.Month || day != u.Day ||
+ hour != u.Hour || min != u.Minute || sec != u.Second ||
+ name != u.Zone || offset != u.ZoneOffset {
+ return false
+ }
+ // Check individual entries.
+ return t.Year() == u.Year &&
+ t.Month() == u.Month &&
+ t.Day() == u.Day &&
+ t.Hour() == u.Hour &&
+ t.Minute() == u.Minute &&
+ t.Second() == u.Second &&
+ t.Nanosecond() == u.Nanosecond &&
+ t.Weekday() == u.Weekday
}
func TestSecondsToUTC(t *testing.T) {
for _, test := range utctests {
sec := test.seconds
golden := &test.golden
- tm := SecondsToUTC(sec)
- newsec := tm.Seconds()
+ tm := Unix(sec, 0).UTC()
+ newsec := tm.Unix()
if newsec != sec {
t.Errorf("SecondsToUTC(%d).Seconds() = %d", sec, newsec)
}
if !same(tm, golden) {
- t.Errorf("SecondsToUTC(%d):", sec)
+ t.Errorf("SecondsToUTC(%d): // %#v", sec, tm)
t.Errorf(" want=%+v", *golden)
- t.Errorf(" have=%+v", *tm)
+ t.Errorf(" have=%v", tm.Format(RFC3339+" MST"))
}
}
}
@@ -99,15 +112,15 @@ func TestNanosecondsToUTC(t *testing.T) {
for _, test := range nanoutctests {
golden := &test.golden
nsec := test.seconds*1e9 + int64(golden.Nanosecond)
- tm := NanosecondsToUTC(nsec)
- newnsec := tm.Nanoseconds()
+ tm := Unix(0, nsec).UTC()
+ newnsec := tm.Unix()*1e9 + int64(tm.Nanosecond())
if newnsec != nsec {
t.Errorf("NanosecondsToUTC(%d).Nanoseconds() = %d", nsec, newnsec)
}
if !same(tm, golden) {
t.Errorf("NanosecondsToUTC(%d):", nsec)
t.Errorf(" want=%+v", *golden)
- t.Errorf(" have=%+v", *tm)
+ t.Errorf(" have=%+v", tm.Format(RFC3339+" MST"))
}
}
}
@@ -116,38 +129,38 @@ func TestSecondsToLocalTime(t *testing.T) {
for _, test := range localtests {
sec := test.seconds
golden := &test.golden
- tm := SecondsToLocalTime(sec)
- newsec := tm.Seconds()
+ tm := Unix(sec, 0)
+ newsec := tm.Unix()
if newsec != sec {
t.Errorf("SecondsToLocalTime(%d).Seconds() = %d", sec, newsec)
}
if !same(tm, golden) {
t.Errorf("SecondsToLocalTime(%d):", sec)
t.Errorf(" want=%+v", *golden)
- t.Errorf(" have=%+v", *tm)
+ t.Errorf(" have=%+v", tm.Format(RFC3339+" MST"))
}
}
}
-func TestNanoecondsToLocalTime(t *testing.T) {
+func TestNanosecondsToLocalTime(t *testing.T) {
for _, test := range nanolocaltests {
golden := &test.golden
nsec := test.seconds*1e9 + int64(golden.Nanosecond)
- tm := NanosecondsToLocalTime(nsec)
- newnsec := tm.Nanoseconds()
+ tm := Unix(0, nsec)
+ newnsec := tm.Unix()*1e9 + int64(tm.Nanosecond())
if newnsec != nsec {
t.Errorf("NanosecondsToLocalTime(%d).Seconds() = %d", nsec, newnsec)
}
if !same(tm, golden) {
t.Errorf("NanosecondsToLocalTime(%d):", nsec)
t.Errorf(" want=%+v", *golden)
- t.Errorf(" have=%+v", *tm)
+ t.Errorf(" have=%+v", tm.Format(RFC3339+" MST"))
}
}
}
func TestSecondsToUTCAndBack(t *testing.T) {
- f := func(sec int64) bool { return SecondsToUTC(sec).Seconds() == sec }
+ f := func(sec int64) bool { return Unix(sec, 0).UTC().Unix() == sec }
f32 := func(sec int32) bool { return f(int64(sec)) }
cfg := &quick.Config{MaxCount: 10000}
@@ -161,7 +174,11 @@ func TestSecondsToUTCAndBack(t *testing.T) {
}
func TestNanosecondsToUTCAndBack(t *testing.T) {
- f := func(nsec int64) bool { return NanosecondsToUTC(nsec).Nanoseconds() == nsec }
+ f := func(nsec int64) bool {
+ t := Unix(0, nsec).UTC()
+ ns := t.Unix()*1e9 + int64(t.Nanosecond())
+ return ns == nsec
+ }
f32 := func(nsec int32) bool { return f(int64(nsec)) }
cfg := &quick.Config{MaxCount: 10000}
@@ -181,9 +198,9 @@ type TimeFormatTest struct {
}
var rfc3339Formats = []TimeFormatTest{
- {Time{2008, 9, 17, 20, 4, 26, 0, Wednesday, 0, "UTC"}, "2008-09-17T20:04:26Z"},
- {Time{1994, 9, 17, 20, 4, 26, 0, Wednesday, -18000, "EST"}, "1994-09-17T20:04:26-05:00"},
- {Time{2000, 12, 26, 1, 15, 6, 0, Wednesday, 15600, "OTO"}, "2000-12-26T01:15:06+04:20"},
+ {Date(2008, 9, 17, 20, 4, 26, 0, UTC), "2008-09-17T20:04:26Z"},
+ {Date(1994, 9, 17, 20, 4, 26, 0, FixedZone("EST", -18000)), "1994-09-17T20:04:26-05:00"},
+ {Date(2000, 12, 26, 1, 15, 6, 0, FixedZone("OTO", 15600)), "2000-12-26T01:15:06+04:20"},
}
func TestRFC3339Conversion(t *testing.T) {
@@ -209,7 +226,9 @@ var formatTests = []FormatTest{
{"RFC822", RFC822, "04 Feb 09 2100 PST"},
{"RFC850", RFC850, "Wednesday, 04-Feb-09 21:00:57 PST"},
{"RFC1123", RFC1123, "Wed, 04 Feb 2009 21:00:57 PST"},
+ {"RFC1123Z", RFC1123Z, "Wed, 04 Feb 2009 21:00:57 -0800"},
{"RFC3339", RFC3339, "2009-02-04T21:00:57-08:00"},
+ {"RFC3339Nano", RFC3339Nano, "2009-02-04T21:00:57.0123456-08:00"},
{"Kitchen", Kitchen, "9:00PM"},
{"am/pm", "3pm", "9pm"},
{"AM/PM", "3PM", "9PM"},
@@ -218,12 +237,12 @@ var formatTests = []FormatTest{
{"Stamp", Stamp, "Feb 4 21:00:57"},
{"StampMilli", StampMilli, "Feb 4 21:00:57.012"},
{"StampMicro", StampMicro, "Feb 4 21:00:57.012345"},
- {"StampNano", StampNano, "Feb 4 21:00:57.012345678"},
+ {"StampNano", StampNano, "Feb 4 21:00:57.012345600"},
}
func TestFormat(t *testing.T) {
- // The numeric time represents Thu Feb 4 21:00:57.012345678 PST 2010
- time := NanosecondsToLocalTime(1233810057012345678)
+ // The numeric time represents Thu Feb 4 21:00:57.012345600 PST 2010
+ time := Unix(0, 1233810057012345600)
for _, test := range formatTests {
result := time.Format(test.format)
if result != test.result {
@@ -232,14 +251,46 @@ func TestFormat(t *testing.T) {
}
}
+func TestFormatShortYear(t *testing.T) {
+ years := []int{
+ -100001, -100000, -99999,
+ -10001, -10000, -9999,
+ -1001, -1000, -999,
+ -101, -100, -99,
+ -11, -10, -9,
+ -1, 0, 1,
+ 9, 10, 11,
+ 99, 100, 101,
+ 999, 1000, 1001,
+ 9999, 10000, 10001,
+ 99999, 100000, 100001,
+ }
+
+ for _, y := range years {
+ time := Date(y, January, 1, 0, 0, 0, 0, UTC)
+ result := time.Format("2006.01.02")
+ var want string
+ if y < 0 {
+ // The 4 in %04d counts the - sign, so print -y instead
+ // and introduce our own - sign.
+ want = fmt.Sprintf("-%04d.%02d.%02d", -y, 1, 1)
+ } else {
+ want = fmt.Sprintf("%04d.%02d.%02d", y, 1, 1)
+ }
+ if result != want {
+ t.Errorf("(jan 1 %d).Format(\"2006.01.02\") = %q, want %q", y, result, want)
+ }
+ }
+}
+
type ParseTest struct {
name string
format string
value string
- hasTZ bool // contains a time zone
- hasWD bool // contains a weekday
- yearSign int64 // sign of year
- fracDigits int // number of digits of fractional second
+ hasTZ bool // contains a time zone
+ hasWD bool // contains a weekday
+ yearSign int // sign of year
+ fracDigits int // number of digits of fractional second
}
var parseTests = []ParseTest{
@@ -248,6 +299,7 @@ var parseTests = []ParseTest{
{"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0800 2010", true, true, 1, 0},
{"RFC850", RFC850, "Thursday, 04-Feb-10 21:00:57 PST", true, true, 1, 0},
{"RFC1123", RFC1123, "Thu, 04 Feb 2010 21:00:57 PST", true, true, 1, 0},
+ {"RFC1123Z", RFC1123Z, "Thu, 04 Feb 2010 21:00:57 -0800", true, true, 1, 0},
{"RFC3339", RFC3339, "2010-02-04T21:00:57-08:00", true, false, 1, 0},
{"custom: \"2006-01-02 15:04:05-07\"", "2006-01-02 15:04:05-07", "2010-02-04 21:00:57-08", true, false, 1, 0},
// Optional fractional seconds.
@@ -256,10 +308,14 @@ var parseTests = []ParseTest{
{"RubyDate", RubyDate, "Thu Feb 04 21:00:57.012 -0800 2010", true, true, 1, 3},
{"RFC850", RFC850, "Thursday, 04-Feb-10 21:00:57.0123 PST", true, true, 1, 4},
{"RFC1123", RFC1123, "Thu, 04 Feb 2010 21:00:57.01234 PST", true, true, 1, 5},
+ {"RFC1123Z", RFC1123Z, "Thu, 04 Feb 2010 21:00:57.01234 -0800", true, true, 1, 5},
{"RFC3339", RFC3339, "2010-02-04T21:00:57.012345678-08:00", true, false, 1, 9},
// Amount of white space should not matter.
{"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1, 0},
{"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1, 0},
+ // Case should not matter
+ {"ANSIC", ANSIC, "THU FEB 4 21:00:57 2010", false, true, 1, 0},
+ {"ANSIC", ANSIC, "thu feb 4 21:00:57 2010", false, true, 1, 0},
// Fractional seconds.
{"millisecond", "Mon Jan _2 15:04:05.000 2006", "Thu Feb 4 21:00:57.012 2010", false, true, 1, 3},
{"microsecond", "Mon Jan _2 15:04:05.000000 2006", "Thu Feb 4 21:00:57.012345 2010", false, true, 1, 6},
@@ -300,47 +356,48 @@ func TestRubyParse(t *testing.T) {
}
}
-func checkTime(time *Time, test *ParseTest, t *testing.T) {
+func checkTime(time Time, test *ParseTest, t *testing.T) {
// The time should be Thu Feb 4 21:00:57 PST 2010
- if test.yearSign*time.Year != 2010 {
- t.Errorf("%s: bad year: %d not %d", test.name, time.Year, 2010)
+ if test.yearSign*time.Year() != 2010 {
+ t.Errorf("%s: bad year: %d not %d", test.name, time.Year(), 2010)
}
- if time.Month != 2 {
- t.Errorf("%s: bad month: %d not %d", test.name, time.Month, 2)
+ if time.Month() != February {
+ t.Errorf("%s: bad month: %s not %s", test.name, time.Month(), February)
}
- if time.Day != 4 {
- t.Errorf("%s: bad day: %d not %d", test.name, time.Day, 4)
+ if time.Day() != 4 {
+ t.Errorf("%s: bad day: %d not %d", test.name, time.Day(), 4)
}
- if time.Hour != 21 {
- t.Errorf("%s: bad hour: %d not %d", test.name, time.Hour, 21)
+ if time.Hour() != 21 {
+ t.Errorf("%s: bad hour: %d not %d", test.name, time.Hour(), 21)
}
- if time.Minute != 0 {
- t.Errorf("%s: bad minute: %d not %d", test.name, time.Minute, 0)
+ if time.Minute() != 0 {
+ t.Errorf("%s: bad minute: %d not %d", test.name, time.Minute(), 0)
}
- if time.Second != 57 {
- t.Errorf("%s: bad second: %d not %d", test.name, time.Second, 57)
+ if time.Second() != 57 {
+ t.Errorf("%s: bad second: %d not %d", test.name, time.Second(), 57)
}
// Nanoseconds must be checked against the precision of the input.
- nanosec, err := strconv.Atoui("012345678"[:test.fracDigits] + "000000000"[:9-test.fracDigits])
+ nanosec, err := strconv.ParseUint("012345678"[:test.fracDigits]+"000000000"[:9-test.fracDigits], 10, 0)
if err != nil {
panic(err)
}
- if time.Nanosecond != int(nanosec) {
- t.Errorf("%s: bad nanosecond: %d not %d", test.name, time.Nanosecond, nanosec)
+ if time.Nanosecond() != int(nanosec) {
+ t.Errorf("%s: bad nanosecond: %d not %d", test.name, time.Nanosecond(), nanosec)
}
- if test.hasTZ && time.ZoneOffset != -28800 {
- t.Errorf("%s: bad tz offset: %d not %d", test.name, time.ZoneOffset, -28800)
+ name, offset := time.Zone()
+ if test.hasTZ && offset != -28800 {
+ t.Errorf("%s: bad tz offset: %s %d not %d", test.name, name, offset, -28800)
}
- if test.hasWD && time.Weekday != 4 {
- t.Errorf("%s: bad weekday: %d not %d", test.name, time.Weekday, 4)
+ if test.hasWD && time.Weekday() != Thursday {
+ t.Errorf("%s: bad weekday: %s not %s", test.name, time.Weekday(), Thursday)
}
}
func TestFormatAndParse(t *testing.T) {
const fmt = "Mon MST " + RFC3339 // all fields
f := func(sec int64) bool {
- t1 := SecondsToLocalTime(sec)
- if t1.Year < 1000 || t1.Year > 9999 {
+ t1 := Unix(sec, 0)
+ if t1.Year() < 1000 || t1.Year() > 9999 {
// not required to work
return true
}
@@ -349,8 +406,8 @@ func TestFormatAndParse(t *testing.T) {
t.Errorf("error: %s", err)
return false
}
- if !same(t1, t2) {
- t.Errorf("different: %q %q", t1, t2)
+ if t1.Unix() != t2.Unix() || t1.Nanosecond() != t2.Nanosecond() {
+ t.Errorf("FormatAndParse %d: %q(%d) %q(%d)", sec, t1, t1.Unix(), t2, t2.Unix())
return false
}
return true
@@ -389,14 +446,14 @@ func TestParseErrors(t *testing.T) {
_, err := Parse(test.format, test.value)
if err == nil {
t.Errorf("expected error for %q %q", test.format, test.value)
- } else if strings.Index(err.String(), test.expect) < 0 {
+ } else if strings.Index(err.Error(), test.expect) < 0 {
t.Errorf("expected error with %q for %q %q; got %s", test.expect, test.format, test.value, err)
}
}
}
func TestNoonIs12PM(t *testing.T) {
- noon := Time{Hour: 12}
+ noon := Date(0, January, 1, 12, 0, 0, 0, UTC)
const expect = "12:00PM"
got := noon.Format("3:04PM")
if got != expect {
@@ -409,7 +466,7 @@ func TestNoonIs12PM(t *testing.T) {
}
func TestMidnightIs12AM(t *testing.T) {
- midnight := Time{Hour: 0}
+ midnight := Date(0, January, 1, 0, 0, 0, 0, UTC)
expect := "12:00AM"
got := midnight.Format("3:04PM")
if got != expect {
@@ -426,15 +483,15 @@ func Test12PMIsNoon(t *testing.T) {
if err != nil {
t.Fatal("error parsing date:", err)
}
- if noon.Hour != 12 {
- t.Errorf("got %d; expect 12", noon.Hour)
+ if noon.Hour() != 12 {
+ t.Errorf("got %d; expect 12", noon.Hour())
}
noon, err = Parse("03:04PM", "12:00PM")
if err != nil {
t.Fatal("error parsing date:", err)
}
- if noon.Hour != 12 {
- t.Errorf("got %d; expect 12", noon.Hour)
+ if noon.Hour() != 12 {
+ t.Errorf("got %d; expect 12", noon.Hour())
}
}
@@ -443,29 +500,29 @@ func Test12AMIsMidnight(t *testing.T) {
if err != nil {
t.Fatal("error parsing date:", err)
}
- if midnight.Hour != 0 {
- t.Errorf("got %d; expect 0", midnight.Hour)
+ if midnight.Hour() != 0 {
+ t.Errorf("got %d; expect 0", midnight.Hour())
}
midnight, err = Parse("03:04PM", "12:00AM")
if err != nil {
t.Fatal("error parsing date:", err)
}
- if midnight.Hour != 0 {
- t.Errorf("got %d; expect 0", midnight.Hour)
+ if midnight.Hour() != 0 {
+ t.Errorf("got %d; expect 0", midnight.Hour())
}
}
// Check that a time without a Zone still produces a (numeric) time zone
// when formatted with MST as a requested zone.
func TestMissingZone(t *testing.T) {
- time, err := Parse(RubyDate, "Tue Feb 02 16:10:03 -0500 2006")
+ time, err := Parse(RubyDate, "Thu Feb 02 16:10:03 -0500 2006")
if err != nil {
t.Fatal("error parsing date:", err)
}
- expect := "Tue Feb 2 16:10:03 -0500 2006" // -0500 not EST
+ expect := "Thu Feb 2 16:10:03 -0500 2006" // -0500 not EST
str := time.Format(UnixDate) // uses MST as its time zone
if str != expect {
- t.Errorf("expected %q got %q", expect, str)
+ t.Errorf("got %s; expect %s", str, expect)
}
}
@@ -475,25 +532,409 @@ func TestMinutesInTimeZone(t *testing.T) {
t.Fatal("error parsing date:", err)
}
expected := (1*60 + 23) * 60
- if time.ZoneOffset != expected {
- t.Errorf("ZoneOffset incorrect, expected %d got %d", expected, time.ZoneOffset)
+ _, offset := time.Zone()
+ if offset != expected {
+ t.Errorf("ZoneOffset = %d, want %d", offset, expected)
+ }
+}
+
+type ISOWeekTest struct {
+ year int // year
+ month, day int // month and day
+ yex int // expected year
+ wex int // expected week
+}
+
+var isoWeekTests = []ISOWeekTest{
+ {1981, 1, 1, 1981, 1}, {1982, 1, 1, 1981, 53}, {1983, 1, 1, 1982, 52},
+ {1984, 1, 1, 1983, 52}, {1985, 1, 1, 1985, 1}, {1986, 1, 1, 1986, 1},
+ {1987, 1, 1, 1987, 1}, {1988, 1, 1, 1987, 53}, {1989, 1, 1, 1988, 52},
+ {1990, 1, 1, 1990, 1}, {1991, 1, 1, 1991, 1}, {1992, 1, 1, 1992, 1},
+ {1993, 1, 1, 1992, 53}, {1994, 1, 1, 1993, 52}, {1995, 1, 2, 1995, 1},
+ {1996, 1, 1, 1996, 1}, {1996, 1, 7, 1996, 1}, {1996, 1, 8, 1996, 2},
+ {1997, 1, 1, 1997, 1}, {1998, 1, 1, 1998, 1}, {1999, 1, 1, 1998, 53},
+ {2000, 1, 1, 1999, 52}, {2001, 1, 1, 2001, 1}, {2002, 1, 1, 2002, 1},
+ {2003, 1, 1, 2003, 1}, {2004, 1, 1, 2004, 1}, {2005, 1, 1, 2004, 53},
+ {2006, 1, 1, 2005, 52}, {2007, 1, 1, 2007, 1}, {2008, 1, 1, 2008, 1},
+ {2009, 1, 1, 2009, 1}, {2010, 1, 1, 2009, 53}, {2010, 1, 1, 2009, 53},
+ {2011, 1, 1, 2010, 52}, {2011, 1, 2, 2010, 52}, {2011, 1, 3, 2011, 1},
+ {2011, 1, 4, 2011, 1}, {2011, 1, 5, 2011, 1}, {2011, 1, 6, 2011, 1},
+ {2011, 1, 7, 2011, 1}, {2011, 1, 8, 2011, 1}, {2011, 1, 9, 2011, 1},
+ {2011, 1, 10, 2011, 2}, {2011, 1, 11, 2011, 2}, {2011, 6, 12, 2011, 23},
+ {2011, 6, 13, 2011, 24}, {2011, 12, 25, 2011, 51}, {2011, 12, 26, 2011, 52},
+ {2011, 12, 27, 2011, 52}, {2011, 12, 28, 2011, 52}, {2011, 12, 29, 2011, 52},
+ {2011, 12, 30, 2011, 52}, {2011, 12, 31, 2011, 52}, {1995, 1, 1, 1994, 52},
+ {2012, 1, 1, 2011, 52}, {2012, 1, 2, 2012, 1}, {2012, 1, 8, 2012, 1},
+ {2012, 1, 9, 2012, 2}, {2012, 12, 23, 2012, 51}, {2012, 12, 24, 2012, 52},
+ {2012, 12, 30, 2012, 52}, {2012, 12, 31, 2013, 1}, {2013, 1, 1, 2013, 1},
+ {2013, 1, 6, 2013, 1}, {2013, 1, 7, 2013, 2}, {2013, 12, 22, 2013, 51},
+ {2013, 12, 23, 2013, 52}, {2013, 12, 29, 2013, 52}, {2013, 12, 30, 2014, 1},
+ {2014, 1, 1, 2014, 1}, {2014, 1, 5, 2014, 1}, {2014, 1, 6, 2014, 2},
+ {2015, 1, 1, 2015, 1}, {2016, 1, 1, 2015, 53}, {2017, 1, 1, 2016, 52},
+ {2018, 1, 1, 2018, 1}, {2019, 1, 1, 2019, 1}, {2020, 1, 1, 2020, 1},
+ {2021, 1, 1, 2020, 53}, {2022, 1, 1, 2021, 52}, {2023, 1, 1, 2022, 52},
+ {2024, 1, 1, 2024, 1}, {2025, 1, 1, 2025, 1}, {2026, 1, 1, 2026, 1},
+ {2027, 1, 1, 2026, 53}, {2028, 1, 1, 2027, 52}, {2029, 1, 1, 2029, 1},
+ {2030, 1, 1, 2030, 1}, {2031, 1, 1, 2031, 1}, {2032, 1, 1, 2032, 1},
+ {2033, 1, 1, 2032, 53}, {2034, 1, 1, 2033, 52}, {2035, 1, 1, 2035, 1},
+ {2036, 1, 1, 2036, 1}, {2037, 1, 1, 2037, 1}, {2038, 1, 1, 2037, 53},
+ {2039, 1, 1, 2038, 52}, {2040, 1, 1, 2039, 52},
+}
+
+func TestISOWeek(t *testing.T) {
+ // Selected dates and corner cases
+ for _, wt := range isoWeekTests {
+ dt := Date(wt.year, Month(wt.month), wt.day, 0, 0, 0, 0, UTC)
+ y, w := dt.ISOWeek()
+ if w != wt.wex || y != wt.yex {
+ t.Errorf("got %d/%d; expected %d/%d for %d-%02d-%02d",
+ y, w, wt.yex, wt.wex, wt.year, wt.month, wt.day)
+ }
+ }
+
+ // The only real invariant: Jan 04 is in week 1
+ for year := 1950; year < 2100; year++ {
+ if y, w := Date(year, January, 4, 0, 0, 0, 0, UTC).ISOWeek(); y != year || w != 1 {
+ t.Errorf("got %d/%d; expected %d/1 for Jan 04", y, w, year)
+ }
}
}
-func BenchmarkSeconds(b *testing.B) {
- for i := 0; i < b.N; i++ {
- Seconds()
+var durationTests = []struct {
+ str string
+ d Duration
+}{
+ {"0", 0},
+ {"1ns", 1 * Nanosecond},
+ {"1.1us", 1100 * Nanosecond},
+ {"2.2ms", 2200 * Microsecond},
+ {"3.3s", 3300 * Millisecond},
+ {"4m5s", 4*Minute + 5*Second},
+ {"4m5.001s", 4*Minute + 5001*Millisecond},
+ {"5h6m7.001s", 5*Hour + 6*Minute + 7001*Millisecond},
+ {"8m0.000000001s", 8*Minute + 1*Nanosecond},
+ {"2562047h47m16.854775807s", 1<<63 - 1},
+ {"-2562047h47m16.854775808s", -1 << 63},
+}
+
+func TestDurationString(t *testing.T) {
+ for _, tt := range durationTests {
+ if str := tt.d.String(); str != tt.str {
+ t.Errorf("Duration(%d).String() = %s, want %s", int64(tt.d), str, tt.str)
+ }
+ if tt.d > 0 {
+ if str := (-tt.d).String(); str != "-"+tt.str {
+ t.Errorf("Duration(%d).String() = %s, want %s", int64(-tt.d), str, "-"+tt.str)
+ }
+ }
+ }
+}
+
+var dateTests = []struct {
+ year, month, day, hour, min, sec, nsec int
+ z *Location
+ unix int64
+}{
+ {2011, 11, 6, 1, 0, 0, 0, Local, 1320566400}, // 1:00:00 PDT
+ {2011, 11, 6, 1, 59, 59, 0, Local, 1320569999}, // 1:59:59 PDT
+ {2011, 11, 6, 2, 0, 0, 0, Local, 1320573600}, // 2:00:00 PST
+
+ {2011, 3, 13, 1, 0, 0, 0, Local, 1300006800}, // 1:00:00 PST
+ {2011, 3, 13, 1, 59, 59, 0, Local, 1300010399}, // 1:59:59 PST
+ {2011, 3, 13, 3, 0, 0, 0, Local, 1300010400}, // 3:00:00 PDT
+ {2011, 3, 13, 2, 30, 0, 0, Local, 1300008600}, // 2:30:00 PDT ≡ 1:30 PST
+
+ // Many names for Fri Nov 18 7:56:35 PST 2011
+ {2011, 11, 18, 7, 56, 35, 0, Local, 1321631795}, // Nov 18 7:56:35
+ {2011, 11, 19, -17, 56, 35, 0, Local, 1321631795}, // Nov 19 -17:56:35
+ {2011, 11, 17, 31, 56, 35, 0, Local, 1321631795}, // Nov 17 31:56:35
+ {2011, 11, 18, 6, 116, 35, 0, Local, 1321631795}, // Nov 18 6:116:35
+ {2011, 10, 49, 7, 56, 35, 0, Local, 1321631795}, // Oct 49 7:56:35
+ {2011, 11, 18, 7, 55, 95, 0, Local, 1321631795}, // Nov 18 7:55:95
+ {2011, 11, 18, 7, 56, 34, 1e9, Local, 1321631795}, // Nov 18 7:56:34 + 10⁹ns
+ {2011, 12, -12, 7, 56, 35, 0, Local, 1321631795}, // Dec -21 7:56:35
+ {2012, 1, -43, 7, 56, 35, 0, Local, 1321631795}, // Jan -52 7:56:35 2012
+ {2012, int(January - 2), 18, 7, 56, 35, 0, Local, 1321631795}, // (Jan-2) 18 7:56:35 2012
+ {2010, int(December + 11), 18, 7, 56, 35, 0, Local, 1321631795}, // (Dec+11) 18 7:56:35 2010
+}
+
+func TestDate(t *testing.T) {
+ for _, tt := range dateTests {
+ time := Date(tt.year, Month(tt.month), tt.day, tt.hour, tt.min, tt.sec, tt.nsec, tt.z)
+ want := Unix(tt.unix, 0)
+ if !time.Equal(want) {
+ t.Errorf("Date(%d, %d, %d, %d, %d, %d, %d, %s) = %v, want %v",
+ tt.year, tt.month, tt.day, tt.hour, tt.min, tt.sec, tt.nsec, tt.z,
+ time, want)
+ }
}
}
-func BenchmarkNanoseconds(b *testing.B) {
+// Several ways of getting from
+// Fri Nov 18 7:56:35 PST 2011
+// to
+// Thu Mar 19 7:56:35 PST 2016
+var addDateTests = []struct {
+ years, months, days int
+}{
+ {4, 4, 1},
+ {3, 16, 1},
+ {3, 15, 30},
+ {5, -6, -18 - 30 - 12},
+}
+
+func TestAddDate(t *testing.T) {
+ t0 := Date(2011, 11, 18, 7, 56, 35, 0, UTC)
+ t1 := Date(2016, 3, 19, 7, 56, 35, 0, UTC)
+ for _, at := range addDateTests {
+ time := t0.AddDate(at.years, at.months, at.days)
+ if !time.Equal(t1) {
+ t.Errorf("AddDate(%d, %d, %d) = %v, want %v",
+ at.years, at.months, at.days,
+ time, t1)
+ }
+ }
+}
+
+var daysInTests = []struct {
+ year, month, di int
+}{
+ {2011, 1, 31}, // January, first month, 31 days
+ {2011, 2, 28}, // February, non-leap year, 28 days
+ {2012, 2, 29}, // February, leap year, 29 days
+ {2011, 6, 30}, // June, 30 days
+ {2011, 12, 31}, // December, last month, 31 days
+}
+
+func TestDaysIn(t *testing.T) {
+ // The daysIn function is not exported.
+ // Test the daysIn function via the `var DaysIn = daysIn`
+ // statement in the internal_test.go file.
+ for _, tt := range daysInTests {
+ di := DaysIn(Month(tt.month), tt.year)
+ if di != tt.di {
+ t.Errorf("got %d; expected %d for %d-%02d",
+ di, tt.di, tt.year, tt.month)
+ }
+ }
+}
+
+func TestAddToExactSecond(t *testing.T) {
+ // Add an amount to the current time to round it up to the next exact second.
+ // This test checks that the nsec field still lies within the range [0, 999999999].
+ t1 := Now()
+ t2 := t1.Add(Second - Duration(t1.Nanosecond()))
+ sec := (t1.Second() + 1) % 60
+ if t2.Second() != sec || t2.Nanosecond() != 0 {
+ t.Errorf("sec = %d, nsec = %d, want sec = %d, nsec = 0", t2.Second(), t2.Nanosecond(), sec)
+ }
+}
+
+func equalTimeAndZone(a, b Time) bool {
+ aname, aoffset := a.Zone()
+ bname, boffset := b.Zone()
+ return a.Equal(b) && aoffset == boffset && aname == bname
+}
+
+var gobTests = []Time{
+ Date(0, 1, 2, 3, 4, 5, 6, UTC),
+ Date(7, 8, 9, 10, 11, 12, 13, FixedZone("", 0)),
+ Unix(81985467080890095, 0x76543210), // Time.sec: 0x0123456789ABCDEF
+ Time{}, // nil location
+ Date(1, 2, 3, 4, 5, 6, 7, FixedZone("", 32767*60)),
+ Date(1, 2, 3, 4, 5, 6, 7, FixedZone("", -32768*60)),
+}
+
+func TestTimeGob(t *testing.T) {
+ var b bytes.Buffer
+ enc := gob.NewEncoder(&b)
+ dec := gob.NewDecoder(&b)
+ for _, tt := range gobTests {
+ var gobtt Time
+ if err := enc.Encode(&tt); err != nil {
+ t.Errorf("%v gob Encode error = %q, want nil", tt, err)
+ } else if err := dec.Decode(&gobtt); err != nil {
+ t.Errorf("%v gob Decode error = %q, want nil", tt, err)
+ } else if !equalTimeAndZone(gobtt, tt) {
+ t.Errorf("Decoded time = %v, want %v", gobtt, tt)
+ }
+ b.Reset()
+ }
+}
+
+var invalidEncodingTests = []struct {
+ bytes []byte
+ want string
+}{
+ {[]byte{}, "Time.GobDecode: no data"},
+ {[]byte{0, 2, 3}, "Time.GobDecode: unsupported version"},
+ {[]byte{1, 2, 3}, "Time.GobDecode: invalid length"},
+}
+
+func TestInvalidTimeGob(t *testing.T) {
+ for _, tt := range invalidEncodingTests {
+ var ignored Time
+ err := ignored.GobDecode(tt.bytes)
+ if err == nil || err.Error() != tt.want {
+ t.Errorf("time.GobDecode(%#v) error = %v, want %v", tt.bytes, err, tt.want)
+ }
+ }
+}
+
+var notEncodableTimes = []struct {
+ time Time
+ want string
+}{
+ {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", 1)), "Time.GobEncode: zone offset has fractional minute"},
+ {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", -1*60)), "Time.GobEncode: unexpected zone offset"},
+ {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", -32769*60)), "Time.GobEncode: unexpected zone offset"},
+ {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", 32768*60)), "Time.GobEncode: unexpected zone offset"},
+}
+
+func TestNotGobEncodableTime(t *testing.T) {
+ for _, tt := range notEncodableTimes {
+ _, err := tt.time.GobEncode()
+ if err == nil || err.Error() != tt.want {
+ t.Errorf("%v GobEncode error = %v, want %v", tt.time, err, tt.want)
+ }
+ }
+}
+
+var jsonTests = []struct {
+ time Time
+ json string
+}{
+ {Date(9999, 4, 12, 23, 20, 50, .52*1e9, UTC), `"9999-04-12T23:20:50.52Z"`},
+ {Date(1996, 12, 19, 16, 39, 57, 0, Local), `"1996-12-19T16:39:57-08:00"`},
+ {Date(0, 1, 1, 0, 0, 0, 1, FixedZone("", 1*60)), `"0000-01-01T00:00:00.000000001+00:01"`},
+}
+
+func TestTimeJSON(t *testing.T) {
+ for _, tt := range jsonTests {
+ var jsonTime Time
+
+ if jsonBytes, err := json.Marshal(tt.time); err != nil {
+ t.Errorf("%v json.Marshal error = %v, want nil", tt.time, err)
+ } else if string(jsonBytes) != tt.json {
+ t.Errorf("%v JSON = %#q, want %#q", tt.time, string(jsonBytes), tt.json)
+ } else if err = json.Unmarshal(jsonBytes, &jsonTime); err != nil {
+ t.Errorf("%v json.Unmarshal error = %v, want nil", tt.time, err)
+ } else if !equalTimeAndZone(jsonTime, tt.time) {
+ t.Errorf("Unmarshaled time = %v, want %v", jsonTime, tt.time)
+ }
+ }
+}
+
+func TestInvalidTimeJSON(t *testing.T) {
+ var tt Time
+ err := json.Unmarshal([]byte(`{"now is the time":"buddy"}`), &tt)
+ _, isParseErr := err.(*ParseError)
+ if !isParseErr {
+ t.Errorf("expected *time.ParseError unmarshaling JSON, got %v", err)
+ }
+}
+
+var notJSONEncodableTimes = []struct {
+ time Time
+ want string
+}{
+ {Date(10000, 1, 1, 0, 0, 0, 0, UTC), "Time.MarshalJSON: year outside of range [0,9999]"},
+ {Date(-1, 1, 1, 0, 0, 0, 0, UTC), "Time.MarshalJSON: year outside of range [0,9999]"},
+}
+
+func TestNotJSONEncodableTime(t *testing.T) {
+ for _, tt := range notJSONEncodableTimes {
+ _, err := tt.time.MarshalJSON()
+ if err == nil || err.Error() != tt.want {
+ t.Errorf("%v MarshalJSON error = %v, want %v", tt.time, err, tt.want)
+ }
+ }
+}
+
+var parseDurationTests = []struct {
+ in string
+ ok bool
+ want Duration
+}{
+ // simple
+ {"0", true, 0},
+ {"5s", true, 5 * Second},
+ {"30s", true, 30 * Second},
+ {"1478s", true, 1478 * Second},
+ // sign
+ {"-5s", true, -5 * Second},
+ {"+5s", true, 5 * Second},
+ {"-0", true, 0},
+ {"+0", true, 0},
+ // decimal
+ {"5.0s", true, 5 * Second},
+ {"5.6s", true, 5*Second + 600*Millisecond},
+ {"5.s", true, 5 * Second},
+ {".5s", true, 500 * Millisecond},
+ {"1.0s", true, 1 * Second},
+ {"1.00s", true, 1 * Second},
+ {"1.004s", true, 1*Second + 4*Millisecond},
+ {"1.0040s", true, 1*Second + 4*Millisecond},
+ {"100.00100s", true, 100*Second + 1*Millisecond},
+ // different units
+ {"10ns", true, 10 * Nanosecond},
+ {"11us", true, 11 * Microsecond},
+ {"12µs", true, 12 * Microsecond}, // U+00B5
+ {"12μs", true, 12 * Microsecond}, // U+03BC
+ {"13ms", true, 13 * Millisecond},
+ {"14s", true, 14 * Second},
+ {"15m", true, 15 * Minute},
+ {"16h", true, 16 * Hour},
+ // composite durations
+ {"3h30m", true, 3*Hour + 30*Minute},
+ {"10.5s4m", true, 4*Minute + 10*Second + 500*Millisecond},
+ {"-2m3.4s", true, -(2*Minute + 3*Second + 400*Millisecond)},
+ {"1h2m3s4ms5us6ns", true, 1*Hour + 2*Minute + 3*Second + 4*Millisecond + 5*Microsecond + 6*Nanosecond},
+ {"39h9m14.425s", true, 39*Hour + 9*Minute + 14*Second + 425*Millisecond},
+
+ // errors
+ {"", false, 0},
+ {"3", false, 0},
+ {"-", false, 0},
+ {"s", false, 0},
+ {".", false, 0},
+ {"-.", false, 0},
+ {".s", false, 0},
+ {"+.s", false, 0},
+}
+
+func TestParseDuration(t *testing.T) {
+ for _, tc := range parseDurationTests {
+ d, err := ParseDuration(tc.in)
+ if tc.ok && (err != nil || d != tc.want) {
+ t.Errorf("ParseDuration(%q) = %v, %v, want %v, nil", tc.in, d, err, tc.want)
+ } else if !tc.ok && err == nil {
+ t.Errorf("ParseDuration(%q) = _, nil, want _, non-nil", tc.in)
+ }
+ }
+}
+
+func TestParseDurationRoundTrip(t *testing.T) {
+ for i := 0; i < 100; i++ {
+ // Resolutions finer than milliseconds will result in
+ // imprecise round-trips.
+ d0 := Duration(rand.Int31()) * Millisecond
+ s := d0.String()
+ d1, err := ParseDuration(s)
+ if err != nil || d0 != d1 {
+ t.Errorf("round-trip failed: %d => %q => %d, %v", d0, s, d1, err)
+ }
+ }
+}
+
+func BenchmarkNow(b *testing.B) {
for i := 0; i < b.N; i++ {
- Nanoseconds()
+ Now()
}
}
func BenchmarkFormat(b *testing.B) {
- time := SecondsToLocalTime(1265346057)
+ time := Unix(1265346057, 0)
for i := 0; i < b.N; i++ {
time.Format("Mon Jan 2 15:04:05 2006")
}
@@ -504,3 +945,31 @@ func BenchmarkParse(b *testing.B) {
Parse(ANSIC, "Mon Jan 2 15:04:05 2006")
}
}
+
+func BenchmarkHour(b *testing.B) {
+ t := Now()
+ for i := 0; i < b.N; i++ {
+ _ = t.Hour()
+ }
+}
+
+func BenchmarkSecond(b *testing.B) {
+ t := Now()
+ for i := 0; i < b.N; i++ {
+ _ = t.Second()
+ }
+}
+
+func BenchmarkYear(b *testing.B) {
+ t := Now()
+ for i := 0; i < b.N; i++ {
+ _ = t.Year()
+ }
+}
+
+func BenchmarkDay(b *testing.B) {
+ t := Now()
+ for i := 0; i < b.N; i++ {
+ _ = t.Day()
+ }
+}
diff --git a/src/pkg/time/zoneinfo.go b/src/pkg/time/zoneinfo.go
new file mode 100644
index 000000000..3c5774404
--- /dev/null
+++ b/src/pkg/time/zoneinfo.go
@@ -0,0 +1,204 @@
+// Copyright 2011 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package time
+
+import (
+ "sync"
+ "syscall"
+)
+
+// A Location maps time instants to the zone in use at that time.
+// Typically, the Location represents the collection of time offsets
+// in use in a geographical area, such as CEST and CET for central Europe.
+type Location struct {
+ name string
+ zone []zone
+ tx []zoneTrans
+
+ // Most lookups will be for the current time.
+ // To avoid the binary search through tx, keep a
+ // static one-element cache that gives the correct
+ // zone for the time when the Location was created.
+ // if cacheStart <= t <= cacheEnd,
+ // lookup can return cacheZone.
+ // The units for cacheStart and cacheEnd are seconds
+ // since January 1, 1970 UTC, to match the argument
+ // to lookup.
+ cacheStart int64
+ cacheEnd int64
+ cacheZone *zone
+}
+
+// A zone represents a single time zone such as CEST or CET.
+type zone struct {
+ name string // abbreviated name, "CET"
+ offset int // seconds east of UTC
+ isDST bool // is this zone Daylight Savings Time?
+}
+
+// A zoneTrans represents a single time zone transition.
+type zoneTrans struct {
+ when int64 // transition time, in seconds since 1970 GMT
+ index uint8 // the index of the zone that goes into effect at that time
+ isstd, isutc bool // ignored - no idea what these mean
+}
+
+// UTC represents Universal Coordinated Time (UTC).
+var UTC *Location = &utcLoc
+
+// utcLoc is separate so that get can refer to &utcLoc
+// and ensure that it never returns a nil *Location,
+// even if a badly behaved client has changed UTC.
+var utcLoc = Location{name: "UTC"}
+
+// Local represents the system's local time zone.
+var Local *Location = &localLoc
+
+// localLoc is separate so that initLocal can initialize
+// it even if a client has changed Local.
+var localLoc Location
+var localOnce sync.Once
+
+func (l *Location) get() *Location {
+ if l == nil {
+ return &utcLoc
+ }
+ if l == &localLoc {
+ localOnce.Do(initLocal)
+ }
+ return l
+}
+
+// String returns a descriptive name for the time zone information,
+// corresponding to the argument to LoadLocation.
+func (l *Location) String() string {
+ return l.get().name
+}
+
+// FixedZone returns a Location that always uses
+// the given zone name and offset (seconds east of UTC).
+func FixedZone(name string, offset int) *Location {
+ l := &Location{
+ name: name,
+ zone: []zone{{name, offset, false}},
+ tx: []zoneTrans{{-1 << 63, 0, false, false}},
+ cacheStart: -1 << 63,
+ cacheEnd: 1<<63 - 1,
+ }
+ l.cacheZone = &l.zone[0]
+ return l
+}
+
+// lookup returns information about the time zone in use at an
+// instant in time expressed as seconds since January 1, 1970 00:00:00 UTC.
+//
+// The returned information gives the name of the zone (such as "CET"),
+// the start and end times bracketing sec when that zone is in effect,
+// the offset in seconds east of UTC (such as -5*60*60), and whether
+// the daylight savings is being observed at that time.
+func (l *Location) lookup(sec int64) (name string, offset int, isDST bool, start, end int64) {
+ l = l.get()
+
+ if len(l.tx) == 0 {
+ name = "UTC"
+ offset = 0
+ isDST = false
+ start = -1 << 63
+ end = 1<<63 - 1
+ return
+ }
+
+ if zone := l.cacheZone; zone != nil && l.cacheStart <= sec && sec < l.cacheEnd {
+ name = zone.name
+ offset = zone.offset
+ isDST = zone.isDST
+ start = l.cacheStart
+ end = l.cacheEnd
+ return
+ }
+
+ // Binary search for entry with largest time <= sec.
+ // Not using sort.Search to avoid dependencies.
+ tx := l.tx
+ end = 1<<63 - 1
+ for len(tx) > 1 {
+ m := len(tx) / 2
+ lim := tx[m].when
+ if sec < lim {
+ end = lim
+ tx = tx[0:m]
+ } else {
+ tx = tx[m:]
+ }
+ }
+ zone := &l.zone[tx[0].index]
+ name = zone.name
+ offset = zone.offset
+ isDST = zone.isDST
+ start = tx[0].when
+ // end = maintained during the search
+ return
+}
+
+// lookupName returns information about the time zone with
+// the given name (such as "EST").
+func (l *Location) lookupName(name string) (offset int, isDST bool, ok bool) {
+ l = l.get()
+ for i := range l.zone {
+ zone := &l.zone[i]
+ if zone.name == name {
+ return zone.offset, zone.isDST, true
+ }
+ }
+ return
+}
+
+// lookupOffset returns information about the time zone with
+// the given offset (such as -5*60*60).
+func (l *Location) lookupOffset(offset int) (name string, isDST bool, ok bool) {
+ l = l.get()
+ for i := range l.zone {
+ zone := &l.zone[i]
+ if zone.offset == offset {
+ return zone.name, zone.isDST, true
+ }
+ }
+ return
+}
+
+// NOTE(rsc): Eventually we will need to accept the POSIX TZ environment
+// syntax too, but I don't feel like implementing it today.
+
+var zoneinfo, _ = syscall.Getenv("ZONEINFO")
+
+// LoadLocation returns the Location with the given name.
+//
+// If the name is "" or "UTC", LoadLocation returns UTC.
+// If the name is "Local", LoadLocation returns Local.
+//
+// Otherwise, the name is taken to be a location name corresponding to a file
+// in the IANA Time Zone database, such as "America/New_York".
+//
+// The time zone database needed by LoadLocation may not be
+// present on all systems, especially non-Unix systems.
+// LoadLocation looks in the directory or uncompressed zip file
+// named by the ZONEINFO environment variable, if any, then looks in
+// known installation locations on Unix systems,
+// and finally looks in $GOROOT/lib/time/zoneinfo.zip.
+func LoadLocation(name string) (*Location, error) {
+ if name == "" || name == "UTC" {
+ return UTC, nil
+ }
+ if name == "Local" {
+ return Local, nil
+ }
+ if zoneinfo != "" {
+ if z, err := loadZoneFile(zoneinfo, name); err == nil {
+ z.name = name
+ return z, nil
+ }
+ }
+ return loadLocation(name)
+}
diff --git a/src/pkg/time/zoneinfo_plan9.go b/src/pkg/time/zoneinfo_plan9.go
index 3c3e7c424..6855238dc 100644
--- a/src/pkg/time/zoneinfo_plan9.go
+++ b/src/pkg/time/zoneinfo_plan9.go
@@ -7,53 +7,150 @@
package time
import (
- "os"
- "strconv"
- "strings"
+ "errors"
+ "runtime"
+ "syscall"
)
-func parseZones(s string) (zt []zonetime) {
- f := strings.Fields(s)
+func isSpace(r rune) bool {
+ return r == ' ' || r == '\t' || r == '\n'
+}
+
+// Copied from strings to avoid a dependency.
+func fields(s string) []string {
+ // First count the fields.
+ n := 0
+ inField := false
+ for _, rune := range s {
+ wasInField := inField
+ inField = !isSpace(rune)
+ if inField && !wasInField {
+ n++
+ }
+ }
+
+ // Now create them.
+ a := make([]string, n)
+ na := 0
+ fieldStart := -1 // Set to -1 when looking for start of field.
+ for i, rune := range s {
+ if isSpace(rune) {
+ if fieldStart >= 0 {
+ a[na] = s[fieldStart:i]
+ na++
+ fieldStart = -1
+ }
+ } else if fieldStart == -1 {
+ fieldStart = i
+ }
+ }
+ if fieldStart >= 0 { // Last field might end at EOF.
+ a[na] = s[fieldStart:]
+ }
+ return a
+}
+
+func loadZoneDataPlan9(s string) (l *Location, err error) {
+ f := fields(s)
if len(f) < 4 {
- return
+ if len(f) == 2 && f[0] == "GMT" {
+ return UTC, nil
+ }
+ return nil, badData
}
+ var zones [2]zone
+
// standard timezone offset
- o, err := strconv.Atoi(f[1])
+ o, err := atoi(f[1])
if err != nil {
- return
+ return nil, badData
}
- std := &zone{name: f[0], utcoff: o, isdst: false}
+ zones[0] = zone{name: f[0], offset: o, isDST: false}
// alternate timezone offset
- o, err = strconv.Atoi(f[3])
+ o, err = atoi(f[3])
if err != nil {
- return
+ return nil, badData
}
- dst := &zone{name: f[2], utcoff: o, isdst: true}
+ zones[1] = zone{name: f[2], offset: o, isDST: true}
// transition time pairs
+ var tx []zoneTrans
f = f[4:]
for i := 0; i < len(f); i++ {
- z := std
+ zi := 0
if i%2 == 0 {
- z = dst
+ zi = 1
}
- t, err := strconv.Atoi(f[i])
+ t, err := atoi(f[i])
if err != nil {
- return nil
+ return nil, badData
+ }
+ t -= zones[0].offset
+ tx = append(tx, zoneTrans{when: int64(t), index: uint8(zi)})
+ }
+
+ // Committed to succeed.
+ l = &Location{zone: zones[:], tx: tx}
+
+ // Fill in the cache with information about right now,
+ // since that will be the most common lookup.
+ sec, _ := now()
+ for i := range tx {
+ if tx[i].when <= sec && (i+1 == len(tx) || sec < tx[i+1].when) {
+ l.cacheStart = tx[i].when
+ l.cacheEnd = 1<<63 - 1
+ if i+1 < len(tx) {
+ l.cacheEnd = tx[i+1].when
+ }
+ l.cacheZone = &l.zone[tx[i].index]
}
- t -= std.utcoff
- zt = append(zt, zonetime{time: int32(t), zone: z})
}
- return
+
+ return l, nil
+}
+
+func loadZoneFilePlan9(name string) (*Location, error) {
+ b, err := readFile(name)
+ if err != nil {
+ return nil, err
+ }
+ return loadZoneDataPlan9(string(b))
}
-func setupZone() {
- t, err := os.Getenverror("timezone")
+func initTestingZone() {
+ z, err := loadLocation("America/Los_Angeles")
if err != nil {
- // do nothing: use UTC
- return
+ panic("cannot load America/Los_Angeles for testing: " + err.Error())
+ }
+ z.name = "Local"
+ localLoc = *z
+}
+
+func initLocal() {
+ t, ok := syscall.Getenv("timezone")
+ if ok {
+ if z, err := loadZoneDataPlan9(t); err == nil {
+ localLoc = *z
+ return
+ }
+ } else {
+ if z, err := loadZoneFilePlan9("/adm/timezone/local"); err == nil {
+ localLoc = *z
+ localLoc.name = "Local"
+ return
+ }
+ }
+
+ // Fall back to UTC.
+ localLoc.name = "UTC"
+}
+
+func loadLocation(name string) (*Location, error) {
+ if z, err := loadZoneFile(runtime.GOROOT()+"/lib/time/zoneinfo.zip", name); err == nil {
+ z.name = name
+ return z, nil
}
- zones = parseZones(t)
+ return nil, errors.New("unknown time zone " + name)
}
diff --git a/src/pkg/time/zoneinfo_posix.go b/src/pkg/time/zoneinfo_posix.go
deleted file mode 100644
index b49216410..000000000
--- a/src/pkg/time/zoneinfo_posix.go
+++ /dev/null
@@ -1,62 +0,0 @@
-// Copyright 2011 The Go Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style
-// license that can be found in the LICENSE file.
-
-package time
-
-import "sync"
-
-// Parsed representation
-type zone struct {
- utcoff int
- isdst bool
- name string
-}
-
-type zonetime struct {
- time int32 // transition time, in seconds since 1970 GMT
- zone *zone // the zone that goes into effect at that time
- isstd, isutc bool // ignored - no idea what these mean
-}
-
-var zones []zonetime
-var onceSetupZone sync.Once
-
-// Look up the correct time zone (daylight savings or not) for the given unix time, in the current location.
-func lookupTimezone(sec int64) (zone string, offset int) {
- onceSetupZone.Do(setupZone)
- if len(zones) == 0 {
- return "UTC", 0
- }
-
- // Binary search for entry with largest time <= sec
- tz := zones
- for len(tz) > 1 {
- m := len(tz) / 2
- if sec < int64(tz[m].time) {
- tz = tz[0:m]
- } else {
- tz = tz[m:]
- }
- }
- z := tz[0].zone
- return z.name, z.utcoff
-}
-
-// lookupByName returns the time offset for the
-// time zone with the given abbreviation. It only considers
-// time zones that apply to the current system.
-// For example, for a system configured as being in New York,
-// it only recognizes "EST" and "EDT".
-// For a system in San Francisco, "PST" and "PDT".
-// For a system in Sydney, "EST" and "EDT", though they have
-// different meanings than they do in New York.
-func lookupByName(name string) (off int, found bool) {
- onceSetupZone.Do(setupZone)
- for _, z := range zones {
- if name == z.zone.name {
- return z.zone.utcoff, true
- }
- }
- return 0, false
-}
diff --git a/src/pkg/time/zoneinfo_read.go b/src/pkg/time/zoneinfo_read.go
new file mode 100644
index 000000000..ebb4205a9
--- /dev/null
+++ b/src/pkg/time/zoneinfo_read.go
@@ -0,0 +1,341 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// Parse "zoneinfo" time zone file.
+// This is a fairly standard file format used on OS X, Linux, BSD, Sun, and others.
+// See tzfile(5), http://en.wikipedia.org/wiki/Zoneinfo,
+// and ftp://munnari.oz.au/pub/oldtz/
+
+package time
+
+import "errors"
+
+const (
+ headerSize = 4 + 16 + 4*7
+)
+
+// Simple I/O interface to binary blob of data.
+type data struct {
+ p []byte
+ error bool
+}
+
+func (d *data) read(n int) []byte {
+ if len(d.p) < n {
+ d.p = nil
+ d.error = true
+ return nil
+ }
+ p := d.p[0:n]
+ d.p = d.p[n:]
+ return p
+}
+
+func (d *data) big4() (n uint32, ok bool) {
+ p := d.read(4)
+ if len(p) < 4 {
+ d.error = true
+ return 0, false
+ }
+ return uint32(p[0])<<24 | uint32(p[1])<<16 | uint32(p[2])<<8 | uint32(p[3]), true
+}
+
+func (d *data) byte() (n byte, ok bool) {
+ p := d.read(1)
+ if len(p) < 1 {
+ d.error = true
+ return 0, false
+ }
+ return p[0], true
+}
+
+// Make a string by stopping at the first NUL
+func byteString(p []byte) string {
+ for i := 0; i < len(p); i++ {
+ if p[i] == 0 {
+ return string(p[0:i])
+ }
+ }
+ return string(p)
+}
+
+var badData = errors.New("malformed time zone information")
+
+func loadZoneData(bytes []byte) (l *Location, err error) {
+ d := data{bytes, false}
+
+ // 4-byte magic "TZif"
+ if magic := d.read(4); string(magic) != "TZif" {
+ return nil, badData
+ }
+
+ // 1-byte version, then 15 bytes of padding
+ var p []byte
+ if p = d.read(16); len(p) != 16 || p[0] != 0 && p[0] != '2' {
+ return nil, badData
+ }
+
+ // six big-endian 32-bit integers:
+ // number of UTC/local indicators
+ // number of standard/wall indicators
+ // number of leap seconds
+ // number of transition times
+ // number of local time zones
+ // number of characters of time zone abbrev strings
+ const (
+ NUTCLocal = iota
+ NStdWall
+ NLeap
+ NTime
+ NZone
+ NChar
+ )
+ var n [6]int
+ for i := 0; i < 6; i++ {
+ nn, ok := d.big4()
+ if !ok {
+ return nil, badData
+ }
+ n[i] = int(nn)
+ }
+
+ // Transition times.
+ txtimes := data{d.read(n[NTime] * 4), false}
+
+ // Time zone indices for transition times.
+ txzones := d.read(n[NTime])
+
+ // Zone info structures
+ zonedata := data{d.read(n[NZone] * 6), false}
+
+ // Time zone abbreviations.
+ abbrev := d.read(n[NChar])
+
+ // Leap-second time pairs
+ d.read(n[NLeap] * 8)
+
+ // Whether tx times associated with local time types
+ // are specified as standard time or wall time.
+ isstd := d.read(n[NStdWall])
+
+ // Whether tx times associated with local time types
+ // are specified as UTC or local time.
+ isutc := d.read(n[NUTCLocal])
+
+ if d.error { // ran out of data
+ return nil, badData
+ }
+
+ // If version == 2, the entire file repeats, this time using
+ // 8-byte ints for txtimes and leap seconds.
+ // We won't need those until 2106.
+
+ // Now we can build up a useful data structure.
+ // First the zone information.
+ // utcoff[4] isdst[1] nameindex[1]
+ zone := make([]zone, n[NZone])
+ for i := range zone {
+ var ok bool
+ var n uint32
+ if n, ok = zonedata.big4(); !ok {
+ return nil, badData
+ }
+ zone[i].offset = int(n)
+ var b byte
+ if b, ok = zonedata.byte(); !ok {
+ return nil, badData
+ }
+ zone[i].isDST = b != 0
+ if b, ok = zonedata.byte(); !ok || int(b) >= len(abbrev) {
+ return nil, badData
+ }
+ zone[i].name = byteString(abbrev[b:])
+ }
+
+ // Now the transition time info.
+ tx := make([]zoneTrans, n[NTime])
+ for i := range tx {
+ var ok bool
+ var n uint32
+ if n, ok = txtimes.big4(); !ok {
+ return nil, badData
+ }
+ tx[i].when = int64(int32(n))
+ if int(txzones[i]) >= len(zone) {
+ return nil, badData
+ }
+ tx[i].index = txzones[i]
+ if i < len(isstd) {
+ tx[i].isstd = isstd[i] != 0
+ }
+ if i < len(isutc) {
+ tx[i].isutc = isutc[i] != 0
+ }
+ }
+
+ // Commited to succeed.
+ l = &Location{zone: zone, tx: tx}
+
+ // Fill in the cache with information about right now,
+ // since that will be the most common lookup.
+ sec, _ := now()
+ for i := range tx {
+ if tx[i].when <= sec && (i+1 == len(tx) || sec < tx[i+1].when) {
+ l.cacheStart = tx[i].when
+ l.cacheEnd = 1<<63 - 1
+ if i+1 < len(tx) {
+ l.cacheEnd = tx[i+1].when
+ }
+ l.cacheZone = &l.zone[tx[i].index]
+ }
+ }
+
+ return l, nil
+}
+
+func loadZoneFile(dir, name string) (l *Location, err error) {
+ if len(dir) > 4 && dir[len(dir)-4:] == ".zip" {
+ return loadZoneZip(dir, name)
+ }
+ if dir != "" {
+ name = dir + "/" + name
+ }
+ buf, err := readFile(name)
+ if err != nil {
+ return
+ }
+ return loadZoneData(buf)
+}
+
+// There are 500+ zoneinfo files. Rather than distribute them all
+// individually, we ship them in an uncompressed zip file.
+// Used this way, the zip file format serves as a commonly readable
+// container for the individual small files. We choose zip over tar
+// because zip files have a contiguous table of contents, making
+// individual file lookups faster, and because the per-file overhead
+// in a zip file is considerably less than tar's 512 bytes.
+
+// get4 returns the little-endian 32-bit value in b.
+func get4(b []byte) int {
+ if len(b) < 4 {
+ return 0
+ }
+ return int(b[0]) | int(b[1])<<8 | int(b[2])<<16 | int(b[3])<<24
+}
+
+// get2 returns the little-endian 16-bit value in b.
+func get2(b []byte) int {
+ if len(b) < 2 {
+ return 0
+ }
+ return int(b[0]) | int(b[1])<<8
+}
+
+func loadZoneZip(zipfile, name string) (l *Location, err error) {
+ fd, err := open(zipfile)
+ if err != nil {
+ return nil, errors.New("open " + zipfile + ": " + err.Error())
+ }
+ defer closefd(fd)
+
+ const (
+ zecheader = 0x06054b50
+ zcheader = 0x02014b50
+ ztailsize = 22
+
+ zheadersize = 30
+ zheader = 0x04034b50
+ )
+
+ buf := make([]byte, ztailsize)
+ if err := preadn(fd, buf, -ztailsize); err != nil || get4(buf) != zecheader {
+ return nil, errors.New("corrupt zip file " + zipfile)
+ }
+ n := get2(buf[10:])
+ size := get4(buf[12:])
+ off := get4(buf[16:])
+
+ buf = make([]byte, size)
+ if err := preadn(fd, buf, off); err != nil {
+ return nil, errors.New("corrupt zip file " + zipfile)
+ }
+
+ for i := 0; i < n; i++ {
+ // zip entry layout:
+ // 0 magic[4]
+ // 4 madevers[1]
+ // 5 madeos[1]
+ // 6 extvers[1]
+ // 7 extos[1]
+ // 8 flags[2]
+ // 10 meth[2]
+ // 12 modtime[2]
+ // 14 moddate[2]
+ // 16 crc[4]
+ // 20 csize[4]
+ // 24 uncsize[4]
+ // 28 namelen[2]
+ // 30 xlen[2]
+ // 32 fclen[2]
+ // 34 disknum[2]
+ // 36 iattr[2]
+ // 38 eattr[4]
+ // 42 off[4]
+ // 46 name[namelen]
+ // 46+namelen+xlen+fclen - next header
+ //
+ if get4(buf) != zcheader {
+ break
+ }
+ meth := get2(buf[10:])
+ size := get4(buf[24:])
+ namelen := get2(buf[28:])
+ xlen := get2(buf[30:])
+ fclen := get2(buf[32:])
+ off := get4(buf[42:])
+ zname := buf[46 : 46+namelen]
+ buf = buf[46+namelen+xlen+fclen:]
+ if string(zname) != name {
+ continue
+ }
+ if meth != 0 {
+ return nil, errors.New("unsupported compression for " + name + " in " + zipfile)
+ }
+
+ // zip per-file header layout:
+ // 0 magic[4]
+ // 4 extvers[1]
+ // 5 extos[1]
+ // 6 flags[2]
+ // 8 meth[2]
+ // 10 modtime[2]
+ // 12 moddate[2]
+ // 14 crc[4]
+ // 18 csize[4]
+ // 22 uncsize[4]
+ // 26 namelen[2]
+ // 28 xlen[2]
+ // 30 name[namelen]
+ // 30+namelen+xlen - file data
+ //
+ buf = make([]byte, zheadersize+namelen)
+ if err := preadn(fd, buf, off); err != nil ||
+ get4(buf) != zheader ||
+ get2(buf[8:]) != meth ||
+ get2(buf[26:]) != namelen ||
+ string(buf[30:30+namelen]) != name {
+ return nil, errors.New("corrupt zip file " + zipfile)
+ }
+ xlen = get2(buf[28:])
+
+ buf = make([]byte, size)
+ if err := preadn(fd, buf, off+30+namelen+xlen); err != nil {
+ return nil, errors.New("corrupt zip file " + zipfile)
+ }
+
+ return loadZoneData(buf)
+ }
+
+ return nil, errors.New("cannot find " + name + " in zip file " + zipfile)
+}
diff --git a/src/pkg/time/zoneinfo_unix.go b/src/pkg/time/zoneinfo_unix.go
index f3ea7b6fd..2c951a983 100644
--- a/src/pkg/time/zoneinfo_unix.go
+++ b/src/pkg/time/zoneinfo_unix.go
@@ -2,6 +2,8 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
+// +build darwin freebsd linux netbsd openbsd
+
// Parse "zoneinfo" time zone file.
// This is a fairly standard file format used on OS X, Linux, BSD, Sun, and others.
// See tzfile(5), http://en.wikipedia.org/wiki/Zoneinfo,
@@ -10,204 +12,61 @@
package time
import (
- "io/ioutil"
- "os"
-)
-
-const (
- headerSize = 4 + 16 + 4*7
+ "errors"
+ "runtime"
+ "syscall"
)
-// Simple I/O interface to binary blob of data.
-type data struct {
- p []byte
- error bool
-}
-
-func (d *data) read(n int) []byte {
- if len(d.p) < n {
- d.p = nil
- d.error = true
- return nil
- }
- p := d.p[0:n]
- d.p = d.p[n:]
- return p
-}
-
-func (d *data) big4() (n uint32, ok bool) {
- p := d.read(4)
- if len(p) < 4 {
- d.error = true
- return 0, false
- }
- return uint32(p[0])<<24 | uint32(p[1])<<16 | uint32(p[2])<<8 | uint32(p[3]), true
-}
-
-func (d *data) byte() (n byte, ok bool) {
- p := d.read(1)
- if len(p) < 1 {
- d.error = true
- return 0, false
+func initTestingZone() {
+ z, err := loadZoneFile(runtime.GOROOT()+"/lib/time/zoneinfo.zip", "America/Los_Angeles")
+ if err != nil {
+ panic("cannot load America/Los_Angeles for testing: " + err.Error())
}
- return p[0], true
+ z.name = "Local"
+ localLoc = *z
}
-// Make a string by stopping at the first NUL
-func byteString(p []byte) string {
- for i := 0; i < len(p); i++ {
- if p[i] == 0 {
- return string(p[0:i])
- }
- }
- return string(p)
+// Many systems use /usr/share/zoneinfo, Solaris 2 has
+// /usr/share/lib/zoneinfo, IRIX 6 has /usr/lib/locale/TZ.
+var zoneDirs = []string{
+ "/usr/share/zoneinfo/",
+ "/usr/share/lib/zoneinfo/",
+ "/usr/lib/locale/TZ/",
+ runtime.GOROOT() + "/lib/time/zoneinfo/",
}
-func parseinfo(bytes []byte) (zt []zonetime, ok bool) {
- d := data{bytes, false}
-
- // 4-byte magic "TZif"
- if magic := d.read(4); string(magic) != "TZif" {
- return nil, false
- }
-
- // 1-byte version, then 15 bytes of padding
- var p []byte
- if p = d.read(16); len(p) != 16 || p[0] != 0 && p[0] != '2' {
- return nil, false
- }
-
- // six big-endian 32-bit integers:
- // number of UTC/local indicators
- // number of standard/wall indicators
- // number of leap seconds
- // number of transition times
- // number of local time zones
- // number of characters of time zone abbrev strings
- const (
- NUTCLocal = iota
- NStdWall
- NLeap
- NTime
- NZone
- NChar
- )
- var n [6]int
- for i := 0; i < 6; i++ {
- nn, ok := d.big4()
- if !ok {
- return nil, false
- }
- n[i] = int(nn)
- }
-
- // Transition times.
- txtimes := data{d.read(n[NTime] * 4), false}
-
- // Time zone indices for transition times.
- txzones := d.read(n[NTime])
-
- // Zone info structures
- zonedata := data{d.read(n[NZone] * 6), false}
-
- // Time zone abbreviations.
- abbrev := d.read(n[NChar])
-
- // Leap-second time pairs
- d.read(n[NLeap] * 8)
-
- // Whether tx times associated with local time types
- // are specified as standard time or wall time.
- isstd := d.read(n[NStdWall])
-
- // Whether tx times associated with local time types
- // are specified as UTC or local time.
- isutc := d.read(n[NUTCLocal])
-
- if d.error { // ran out of data
- return nil, false
- }
-
- // If version == 2, the entire file repeats, this time using
- // 8-byte ints for txtimes and leap seconds.
- // We won't need those until 2106.
-
- // Now we can build up a useful data structure.
- // First the zone information.
- // utcoff[4] isdst[1] nameindex[1]
- z := make([]zone, n[NZone])
- for i := 0; i < len(z); i++ {
- var ok bool
- var n uint32
- if n, ok = zonedata.big4(); !ok {
- return nil, false
- }
- z[i].utcoff = int(n)
- var b byte
- if b, ok = zonedata.byte(); !ok {
- return nil, false
- }
- z[i].isdst = b != 0
- if b, ok = zonedata.byte(); !ok || int(b) >= len(abbrev) {
- return nil, false
- }
- z[i].name = byteString(abbrev[b:])
- }
+func initLocal() {
+ // consult $TZ to find the time zone to use.
+ // no $TZ means use the system default /etc/localtime.
+ // $TZ="" means use UTC.
+ // $TZ="foo" means use /usr/share/zoneinfo/foo.
- // Now the transition time info.
- zt = make([]zonetime, n[NTime])
- for i := 0; i < len(zt); i++ {
- var ok bool
- var n uint32
- if n, ok = txtimes.big4(); !ok {
- return nil, false
- }
- zt[i].time = int32(n)
- if int(txzones[i]) >= len(z) {
- return nil, false
- }
- zt[i].zone = &z[txzones[i]]
- if i < len(isstd) {
- zt[i].isstd = isstd[i] != 0
+ tz, ok := syscall.Getenv("TZ")
+ switch {
+ case !ok:
+ z, err := loadZoneFile("", "/etc/localtime")
+ if err == nil {
+ localLoc = *z
+ localLoc.name = "Local"
+ return
}
- if i < len(isutc) {
- zt[i].isutc = isutc[i] != 0
+ case tz != "" && tz != "UTC":
+ if z, err := loadLocation(tz); err == nil {
+ localLoc = *z
+ return
}
}
- return zt, true
-}
-func readinfofile(name string) ([]zonetime, bool) {
- buf, err := ioutil.ReadFile(name)
- if err != nil {
- return nil, false
- }
- return parseinfo(buf)
+ // Fall back to UTC.
+ localLoc.name = "UTC"
}
-func setupZone() {
- // consult $TZ to find the time zone to use.
- // no $TZ means use the system default /etc/localtime.
- // $TZ="" means use UTC.
- // $TZ="foo" means use /usr/share/zoneinfo/foo.
- // Many systems use /usr/share/zoneinfo, Solaris 2 has
- // /usr/share/lib/zoneinfo, IRIX 6 has /usr/lib/locale/TZ.
- zoneDirs := []string{"/usr/share/zoneinfo/",
- "/usr/share/lib/zoneinfo/",
- "/usr/lib/locale/TZ/"}
-
- tz, err := os.Getenverror("TZ")
- switch {
- case err == os.ENOENV:
- zones, _ = readinfofile("/etc/localtime")
- case len(tz) > 0:
- for _, zoneDir := range zoneDirs {
- var ok bool
- if zones, ok = readinfofile(zoneDir + tz); ok {
- break
- }
+func loadLocation(name string) (*Location, error) {
+ for _, zoneDir := range zoneDirs {
+ if z, err := loadZoneFile(zoneDir, name); err == nil {
+ z.name = name
+ return z, nil
}
- case len(tz) == 0:
- // do nothing: use UTC
}
+ return nil, errors.New("unknown time zone " + name)
}
diff --git a/src/pkg/time/zoneinfo_windows.go b/src/pkg/time/zoneinfo_windows.go
index fabc00601..754e392de 100644
--- a/src/pkg/time/zoneinfo_windows.go
+++ b/src/pkg/time/zoneinfo_windows.go
@@ -5,188 +5,157 @@
package time
import (
+ "errors"
+ "runtime"
"syscall"
- "sync"
- "os"
)
-// BUG(brainman): The Windows implementation assumes that
-// this year's rules for daylight savings time apply to all previous
-// and future years as well.
-
-// TODO(brainman): use GetDynamicTimeZoneInformation, whenever possible (Vista and up),
-// to improve on situation described in the bug above.
-
-type zone struct {
- name string
- offset int
- year int64
- month, day, dayofweek int
- hour, minute, second int
- abssec int64
- prev *zone
-}
+// TODO(rsc): Fall back to copy of zoneinfo files.
-// Populate zone struct with Windows supplied information. Returns true, if data is valid.
-func (z *zone) populate(bias, biasdelta int32, d *syscall.Systemtime, name []uint16) (dateisgood bool) {
- z.name = syscall.UTF16ToString(name)
- z.offset = int(bias)
- z.year = int64(d.Year)
- z.month = int(d.Month)
- z.day = int(d.Day)
- z.dayofweek = int(d.DayOfWeek)
- z.hour = int(d.Hour)
- z.minute = int(d.Minute)
- z.second = int(d.Second)
- dateisgood = d.Month != 0
- if dateisgood {
- z.offset += int(biasdelta)
- }
- z.offset = -z.offset * 60
- return
-}
+// BUG(brainman,rsc): On Windows, the operating system does not provide complete
+// time zone information.
+// The implementation assumes that this year's rules for daylight savings
+// time apply to all previous and future years as well.
+// Also, time zone abbreviations are unavailable. The implementation constructs
+// them using the capital letters from a longer time zone description.
-// Pre-calculate cutoff time in seconds since the Unix epoch, if data is supplied in "absolute" format.
-func (z *zone) preCalculateAbsSec() {
- if z.year != 0 {
- z.abssec = (&Time{z.year, int(z.month), int(z.day), int(z.hour), int(z.minute), int(z.second), 0, 0, 0, ""}).Seconds()
- // Time given is in "local" time. Adjust it for "utc".
- z.abssec -= int64(z.prev.offset)
+// abbrev returns the abbreviation to use for the given zone name.
+func abbrev(name []uint16) string {
+ // name is 'Pacific Standard Time' but we want 'PST'.
+ // Extract just capital letters. It's not perfect but the
+ // information we need is not available from the kernel.
+ // Because time zone abbreviations are not unique,
+ // Windows refuses to expose them.
+ //
+ // http://social.msdn.microsoft.com/Forums/eu/vclanguage/thread/a87e1d25-fb71-4fe0-ae9c-a9578c9753eb
+ // http://stackoverflow.com/questions/4195948/windows-time-zone-abbreviations-in-asp-net
+ var short []rune
+ for _, c := range name {
+ if 'A' <= c && c <= 'Z' {
+ short = append(short, rune(c))
+ }
}
+ return string(short)
}
-// Convert zone cutoff time to sec in number of seconds since the Unix epoch, given particular year.
-func (z *zone) cutoffSeconds(year int64) int64 {
+// pseudoUnix returns the pseudo-Unix time (seconds since Jan 1 1970 *LOCAL TIME*)
+// denoted by the system date+time d in the given year.
+// It is up to the caller to convert this local time into a UTC-based time.
+func pseudoUnix(year int, d *syscall.Systemtime) int64 {
// Windows specifies daylight savings information in "day in month" format:
- // z.month is month number (1-12)
- // z.dayofweek is appropriate weekday (Sunday=0 to Saturday=6)
- // z.day is week within the month (1 to 5, where 5 is last week of the month)
- // z.hour, z.minute and z.second are absolute time
- t := &Time{year, int(z.month), 1, int(z.hour), int(z.minute), int(z.second), 0, 0, 0, ""}
- t = SecondsToUTC(t.Seconds())
- i := int(z.dayofweek) - t.Weekday
+ // d.Month is month number (1-12)
+ // d.DayOfWeek is appropriate weekday (Sunday=0 to Saturday=6)
+ // d.Day is week within the month (1 to 5, where 5 is last week of the month)
+ // d.Hour, d.Minute and d.Second are absolute time
+ day := 1
+ t := Date(year, Month(d.Month), day, int(d.Hour), int(d.Minute), int(d.Second), 0, UTC)
+ i := int(d.DayOfWeek) - int(t.Weekday())
if i < 0 {
i += 7
}
- t.Day += i
- if week := int(z.day) - 1; week < 4 {
- t.Day += week * 7
+ day += i
+ if week := int(d.Day) - 1; week < 4 {
+ day += week * 7
} else {
// "Last" instance of the day.
- t.Day += 4 * 7
- if t.Day > months(year)[t.Month] {
- t.Day -= 7
+ day += 4 * 7
+ if day > daysIn(Month(d.Month), year) {
+ day -= 7
}
}
- // Result is in "local" time. Adjust it for "utc".
- return t.Seconds() - int64(z.prev.offset)
+ return t.sec + int64(day-1)*secondsPerDay + internalToUnix
}
-// Is t before the cutoff for switching to z?
-func (z *zone) isBeforeCutoff(t *Time) bool {
- var coff int64
- if z.year == 0 {
- // "day in month" format used
- coff = z.cutoffSeconds(t.Year)
- } else {
- // "absolute" format used
- coff = z.abssec
- }
- return t.Seconds() < coff
-}
+func initLocalFromTZI(i *syscall.Timezoneinformation) {
+ l := &localLoc
-type zoneinfo struct {
- disabled bool // daylight saving time is not used locally
- offsetIfDisabled int
- januaryIsStd bool // is january 1 standard time?
- std, dst zone
-}
+ nzone := 1
+ if i.StandardDate.Month > 0 {
+ nzone++
+ }
+ l.zone = make([]zone, nzone)
-// Pick zone (std or dst) t time belongs to.
-func (zi *zoneinfo) pickZone(t *Time) *zone {
- z := &zi.std
- if tz.januaryIsStd {
- if !zi.dst.isBeforeCutoff(t) && zi.std.isBeforeCutoff(t) {
- // after switch to daylight time and before the switch back to standard
- z = &zi.dst
- }
- } else {
- if zi.std.isBeforeCutoff(t) || !zi.dst.isBeforeCutoff(t) {
- // before switch to standard time or after the switch back to daylight
- z = &zi.dst
- }
+ std := &l.zone[0]
+ std.name = abbrev(i.StandardName[0:])
+ if nzone == 1 {
+ // No daylight savings.
+ std.offset = -int(i.Bias) * 60
+ l.cacheStart = -1 << 63
+ l.cacheEnd = 1<<63 - 1
+ l.cacheZone = std
+ return
}
- return z
-}
-var tz zoneinfo
-var initError os.Error
-var onceSetupZone sync.Once
+ // StandardBias must be ignored if StandardDate is not set,
+ // so this computation is delayed until after the nzone==1
+ // return above.
+ std.offset = -int(i.Bias+i.StandardBias) * 60
-func setupZone() {
- var i syscall.Timezoneinformation
- if _, e := syscall.GetTimeZoneInformation(&i); e != 0 {
- initError = os.NewSyscallError("GetTimeZoneInformation", e)
- return
+ dst := &l.zone[1]
+ dst.name = abbrev(i.DaylightName[0:])
+ dst.offset = -int(i.Bias+i.DaylightBias) * 60
+ dst.isDST = true
+
+ // Arrange so that d0 is first transition date, d1 second,
+ // i0 is index of zone after first transition, i1 second.
+ d0 := &i.StandardDate
+ d1 := &i.DaylightDate
+ i0 := 0
+ i1 := 1
+ if d0.Month > d1.Month {
+ d0, d1 = d1, d0
+ i0, i1 = i1, i0
}
- if !tz.std.populate(i.Bias, i.StandardBias, &i.StandardDate, i.StandardName[0:]) {
- tz.disabled = true
- tz.offsetIfDisabled = tz.std.offset
- return
+
+ // 2 tx per year, 100 years on each side of this year
+ l.tx = make([]zoneTrans, 400)
+
+ t := Now().UTC()
+ year := t.Year()
+ txi := 0
+ for y := year - 100; y < year+100; y++ {
+ tx := &l.tx[txi]
+ tx.when = pseudoUnix(y, d0) - int64(l.zone[i1].offset)
+ tx.index = uint8(i0)
+ txi++
+
+ tx = &l.tx[txi]
+ tx.when = pseudoUnix(y, d1) - int64(l.zone[i0].offset)
+ tx.index = uint8(i1)
+ txi++
}
- tz.std.prev = &tz.dst
- tz.dst.populate(i.Bias, i.DaylightBias, &i.DaylightDate, i.DaylightName[0:])
- tz.dst.prev = &tz.std
- tz.std.preCalculateAbsSec()
- tz.dst.preCalculateAbsSec()
- // Is january 1 standard time this year?
- t := UTC()
- tz.januaryIsStd = tz.dst.cutoffSeconds(t.Year) < tz.std.cutoffSeconds(t.Year)
}
-// Look up the correct time zone (daylight savings or not) for the given unix time, in the current location.
-func lookupTimezone(sec int64) (zone string, offset int) {
- onceSetupZone.Do(setupZone)
- if initError != nil {
- return "", 0
- }
- if tz.disabled {
- return "", tz.offsetIfDisabled
- }
- t := SecondsToUTC(sec)
- z := &tz.std
- if tz.std.year == 0 {
- // "day in month" format used
- z = tz.pickZone(t)
- } else {
- // "absolute" format used
- if tz.std.year == t.Year {
- // we have rule for the year in question
- z = tz.pickZone(t)
- } else {
- // we do not have any information for that year,
- // will assume standard offset all year around
- }
- }
- return z.name, z.offset
+var usPacific = syscall.Timezoneinformation{
+ Bias: 8 * 60,
+ StandardName: [32]uint16{
+ 'P', 'a', 'c', 'i', 'f', 'i', 'c', ' ', 'S', 't', 'a', 'n', 'd', 'a', 'r', 'd', ' ', 'T', 'i', 'm', 'e',
+ },
+ StandardDate: syscall.Systemtime{Month: 11, Day: 1, Hour: 2},
+ DaylightName: [32]uint16{
+ 'P', 'a', 'c', 'i', 'f', 'i', 'c', ' ', 'D', 'a', 'y', 'l', 'i', 'g', 'h', 't', ' ', 'T', 'i', 'm', 'e',
+ },
+ DaylightDate: syscall.Systemtime{Month: 3, Day: 2, Hour: 2},
+ DaylightBias: -60,
}
-// lookupByName returns the time offset for the
-// time zone with the given abbreviation. It only considers
-// time zones that apply to the current system.
-func lookupByName(name string) (off int, found bool) {
- onceSetupZone.Do(setupZone)
- if initError != nil {
- return 0, false
- }
- if tz.disabled {
- return tz.offsetIfDisabled, false
+func initTestingZone() {
+ initLocalFromTZI(&usPacific)
+}
+
+func initLocal() {
+ var i syscall.Timezoneinformation
+ if _, err := syscall.GetTimeZoneInformation(&i); err != nil {
+ localLoc.name = "UTC"
+ return
}
- switch name {
- case tz.std.name:
- return tz.std.offset, true
- case tz.dst.name:
- return tz.dst.offset, true
+ initLocalFromTZI(&i)
+}
+
+func loadLocation(name string) (*Location, error) {
+ if z, err := loadZoneFile(runtime.GOROOT()+`\lib\time\zoneinfo.zip`, name); err == nil {
+ z.name = name
+ return z, nil
}
- return 0, false
+ return nil, errors.New("unknown time zone " + name)
}