diff options
author | Ondřej Surý <ondrej@sury.org> | 2011-01-17 12:40:45 +0100 |
---|---|---|
committer | Ondřej Surý <ondrej@sury.org> | 2011-01-17 12:40:45 +0100 |
commit | 3e45412327a2654a77944249962b3652e6142299 (patch) | |
tree | bc3bf69452afa055423cbe0c5cfa8ca357df6ccf /src/pkg/time | |
parent | c533680039762cacbc37db8dc7eed074c3e497be (diff) | |
download | golang-upstream/2011.01.12.tar.gz |
Imported Upstream version 2011.01.12upstream/2011.01.12
Diffstat (limited to 'src/pkg/time')
-rw-r--r-- | src/pkg/time/Makefile | 5 | ||||
-rw-r--r-- | src/pkg/time/format.go | 41 | ||||
-rw-r--r-- | src/pkg/time/sleep.go | 135 | ||||
-rw-r--r-- | src/pkg/time/sleep_test.go | 108 | ||||
-rw-r--r-- | src/pkg/time/tick.go | 59 | ||||
-rw-r--r-- | src/pkg/time/tick_test.go | 2 | ||||
-rw-r--r-- | src/pkg/time/time_test.go | 116 | ||||
-rw-r--r-- | src/pkg/time/zoneinfo.go | 243 | ||||
-rw-r--r-- | src/pkg/time/zoneinfo_unix.go | 7 | ||||
-rw-r--r-- | src/pkg/time/zoneinfo_windows.go | 7 |
10 files changed, 371 insertions, 352 deletions
diff --git a/src/pkg/time/Makefile b/src/pkg/time/Makefile index 6732d6a79..5213e4457 100644 --- a/src/pkg/time/Makefile +++ b/src/pkg/time/Makefile @@ -2,7 +2,7 @@ # Use of this source code is governed by a BSD-style # license that can be found in the LICENSE file. -include ../../Make.$(GOARCH) +include ../../Make.inc TARG=time GOFILES=\ @@ -20,9 +20,6 @@ GOFILES_darwin=\ GOFILES_linux=\ zoneinfo_unix.go\ -GOFILES_nacl=\ - zoneinfo_unix.go\ - GOFILES_windows=\ zoneinfo_windows.go\ diff --git a/src/pkg/time/format.go b/src/pkg/time/format.go index c04325126..7b5a8f3b6 100644 --- a/src/pkg/time/format.go +++ b/src/pkg/time/format.go @@ -19,10 +19,12 @@ const ( // 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.) -// 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. +// To define your own format, write down what the standard +// time would look like formatted your way. +// +// Within the format string, an underscore _ represents a space that may be +// replaced by a digit if the following number (a day) has two digits; for +// compatibility with fixed-width Unix time formats. // // Numeric time zone offsets format as follows: // -0700 ±hhmm @@ -41,8 +43,8 @@ const ( RFC822Z = "02 Jan 06 1504 -0700" RFC850 = "Monday, 02-Jan-06 15:04:05 MST" RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" - Kitchen = "3:04PM" RFC3339 = "2006-01-02T15:04:05Z07:00" + Kitchen = "3:04PM" ) const ( @@ -70,6 +72,7 @@ const ( stdISO8601TZ = "Z0700" // prints Z for UTC stdISO8601ColonTZ = "Z07:00" // prints Z for UTC stdNumTZ = "-0700" // always numeric + stdNumShortTZ = "-07" // always numeric stdNumColonTZ = "-07:00" // always numeric ) @@ -134,13 +137,16 @@ func nextStdChunk(layout string) (prefix, std, suffix string) { return layout[0:i], layout[i : i+2], layout[i+2:] } - case '-': // -0700, -07:00 + case '-': // -0700, -07:00, -07 if len(layout) >= i+5 && layout[i:i+5] == stdNumTZ { return layout[0:i], layout[i : i+5], layout[i+5:] } if len(layout) >= i+6 && layout[i:i+6] == stdNumColonTZ { return layout[0:i], layout[i : i+6], layout[i+6:] } + if len(layout) >= i+3 && layout[i:i+3] == stdNumShortTZ { + return layout[0:i], layout[i : i+3], layout[i+3:] + } case 'Z': // Z0700, Z07:00 if len(layout) >= i+5 && layout[i:i+5] == stdISO8601TZ { return layout[0:i], layout[i : i+5], layout[i+5:] @@ -228,7 +234,8 @@ func zeroPad(i int) string { return pad(i, "0") } // 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. +// RFC3339 and others describe standard representations. For more +// information about the formats, see the documentation for ANSIC. func (t *Time) Format(layout string) string { b := new(bytes.Buffer) // Each iteration generates one std value. @@ -331,7 +338,12 @@ func (t *Time) Format(layout string) string { } // String returns a Unix-style representation of the time value. -func (t *Time) String() string { return t.Format(UnixDate) } +func (t *Time) String() string { + if t == nil { + return "<nil>" + } + return t.Format(UnixDate) +} var errBad = os.ErrorString("bad") // just a marker; not returned to user @@ -405,7 +417,8 @@ func skip(value, prefix string) (string, os.Error) { // 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. +// representations.For more information about the formats, see the +// documentation for ANSIC. // // Only those elements present in the value will be set in the returned time // structure. Also, if the input string represents an inconsistent time @@ -496,7 +509,7 @@ func Parse(alayout, avalue string) (*Time, os.Error) { if t.Second < 0 || 60 <= t.Second { rangeErrString = "second" } - case stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumColonTZ: + case stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ: if std[0] == 'Z' && len(value) >= 1 && value[0] == 'Z' { value = value[1:] t.Zone = "UTC" @@ -513,6 +526,12 @@ func Parse(alayout, avalue string) (*Time, os.Error) { break } sign, hh, mm, value = value[0:1], value[1:3], value[4:6], value[6:] + } else if std == stdNumShortTZ { + if len(value) < 3 { + err = errBad + break + } + sign, hh, mm, value = value[0:1], value[1:3], "00", value[3:] } else { if len(value) < 5 { err = errBad @@ -522,7 +541,7 @@ func Parse(alayout, avalue string) (*Time, os.Error) { } var hr, min int hr, err = strconv.Atoi(hh) - if err != nil { + if err == nil { min, err = strconv.Atoi(mm) } t.ZoneOffset = (hr*60 + min) * 60 // offset is in seconds diff --git a/src/pkg/time/sleep.go b/src/pkg/time/sleep.go index 5de5374ce..3538775ad 100644 --- a/src/pkg/time/sleep.go +++ b/src/pkg/time/sleep.go @@ -7,20 +7,145 @@ package time import ( "os" "syscall" + "sync" + "container/heap" ) -// Sleep pauses the current goroutine for at least ns nanoseconds. Higher resolution -// sleeping may be provided by syscall.Nanosleep on some operating systems. +// The event type represents a single After or AfterFunc event. +type event struct { + t int64 // The absolute time that the event should fire. + f func(int64) // The function to call when the event fires. + sleeping bool // A sleeper is sleeping for this event. +} + +type eventHeap []*event + +var events eventHeap +var eventMutex sync.Mutex + +func init() { + events.Push(&event{1 << 62, nil, true}) // sentinel +} + +// 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 - t := Nanoseconds() end := t + ns for t < end { errno := syscall.Sleep(end - t) if errno != 0 && errno != syscall.EINTR { - return os.NewSyscallError("sleep", errno) + return 0, os.NewSyscallError("sleep", errno) } t = Nanoseconds() } - return nil + return t, nil +} + +// After waits at least ns nanoseconds before sending the current time +// on the returned channel. +func After(ns int64) <-chan int64 { + c := make(chan int64, 1) + after(ns, func(t int64) { c <- t }) + return c +} + +// AfterFunc waits at least ns nanoseconds before calling f +// in its own goroutine. +func AfterFunc(ns int64, f func()) { + after(ns, func(_ int64) { + go f() + }) +} + +// 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)) { + t := Nanoseconds() + ns + eventMutex.Lock() + t0 := events[0].t + heap.Push(events, &event{t, f, false}) + if t < t0 { + go sleeper() + } + eventMutex.Unlock() +} + +// sleeper continually looks at the earliest event in the queue, marks it +// as sleeping, waits until it happens, then removes any events +// in the queue that are due. It stops when it finds an event that is +// already marked as sleeping. When an event is inserted before the first item, +// a new sleeper is started. +// +// Scheduling vagaries mean that sleepers may not wake up in +// exactly the order of the events that they are waiting for, +// but this does not matter as long as there are at least as +// many sleepers as events marked sleeping (invariant). This ensures that +// there is always a sleeper to service the remaining events. +// +// A sleeper will remove at least the event it has been waiting for +// unless the event has already been removed by another sleeper. Both +// cases preserve the invariant described above. +func sleeper() { + eventMutex.Lock() + e := events[0] + for !e.sleeping { + t := Nanoseconds() + if dt := e.t - t; dt > 0 { + e.sleeping = true + eventMutex.Unlock() + if nt, err := sleep(t, dt); err != nil { + // If sleep has encountered an error, + // there's not much we can do. We pretend + // that time really has advanced by the required + // amount and lie to the rest of the system. + t = e.t + } else { + t = nt + } + eventMutex.Lock() + e = events[0] + } + for t >= e.t { + e.f(t) + heap.Pop(events) + e = events[0] + } + } + eventMutex.Unlock() +} + +func (eventHeap) Len() int { + return len(events) +} + +func (eventHeap) Less(i, j int) bool { + return events[i].t < events[j].t +} + +func (eventHeap) Swap(i, j int) { + events[i], events[j] = events[j], events[i] +} + +func (eventHeap) Push(x interface{}) { + events = append(events, x.(*event)) +} + +func (eventHeap) Pop() interface{} { + // TODO: possibly shrink array. + n := len(events) - 1 + e := events[n] + events[n] = nil + events = events[0:n] + return e } diff --git a/src/pkg/time/sleep_test.go b/src/pkg/time/sleep_test.go index 7ec6c4943..9e36288f8 100644 --- a/src/pkg/time/sleep_test.go +++ b/src/pkg/time/sleep_test.go @@ -8,6 +8,7 @@ import ( "os" "syscall" "testing" + "sort" . "time" ) @@ -24,3 +25,110 @@ func TestSleep(t *testing.T) { t.Fatalf("Sleep(%d) slept for only %d ns", delay, duration) } } + +// Test the basic function calling behavior. Correct queueing +// behavior is tested elsewhere, since After and AfterFunc share +// the same code. +func TestAfterFunc(t *testing.T) { + i := 10 + c := make(chan bool) + var f func() + f = func() { + i-- + if i >= 0 { + AfterFunc(0, f) + Sleep(1e9) + } else { + c <- true + } + } + + AfterFunc(0, f) + <-c +} + +func BenchmarkAfterFunc(b *testing.B) { + i := b.N + c := make(chan bool) + var f func() + f = func() { + i-- + if i >= 0 { + AfterFunc(0, f) + } else { + c <- true + } + } + + AfterFunc(0, f) + <-c +} + +func TestAfter(t *testing.T) { + const delay = int64(100e6) + start := Nanoseconds() + end := <-After(delay) + if duration := Nanoseconds() - start; duration < delay { + t.Fatalf("After(%d) slept for only %d ns", delay, duration) + } + if min := start + delay; end < min { + t.Fatalf("After(%d) expect >= %d, got %d", delay, min, end) + } +} + +func TestAfterTick(t *testing.T) { + const ( + Delta = 100 * 1e6 + Count = 10 + ) + t0 := Nanoseconds() + for i := 0; i < Count; i++ { + <-After(Delta) + } + t1 := Nanoseconds() + ns := t1 - t0 + target := int64(Delta * Count) + slop := target * 2 / 10 + if ns < target-slop || ns > target+slop { + t.Fatalf("%d ticks of %g ns took %g ns, expected %g", Count, float64(Delta), float64(ns), float64(target)) + } +} + +var slots = []int{5, 3, 6, 6, 6, 1, 1, 2, 7, 9, 4, 8, 0} + +type afterResult struct { + slot int + t int64 +} + +func await(slot int, result chan<- afterResult, ac <-chan int64) { + result <- afterResult{slot, <-ac} +} + +func TestAfterQueuing(t *testing.T) { + const ( + Delta = 100 * 1e6 + ) + // make the result channel buffered because we don't want + // to depend on channel queueing semantics that might + // possibly change in the future. + result := make(chan afterResult, len(slots)) + + t0 := Nanoseconds() + for _, slot := range slots { + go await(slot, result, After(int64(slot)*Delta)) + } + sort.SortInts(slots) + for _, slot := range slots { + r := <-result + if r.slot != slot { + t.Fatalf("after queue got slot %d, expected %d", r.slot, slot) + } + ns := r.t - t0 + target := int64(slot * Delta) + slop := int64(Delta) / 4 + if ns < target-slop || ns > target+slop { + t.Fatalf("after queue slot %d arrived at %g, expected [%g,%g]", slot, float64(ns), float64(target-slop), float64(target+slop)) + } + } +} diff --git a/src/pkg/time/tick.go b/src/pkg/time/tick.go index 05023d4d0..ddd727270 100644 --- a/src/pkg/time/tick.go +++ b/src/pkg/time/tick.go @@ -5,7 +5,8 @@ package time import ( - "once" + "os" + "sync" ) // A Ticker holds a synchronous channel that delivers `ticks' of a clock @@ -14,13 +15,16 @@ 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 bool + shutdown chan bool // Buffered channel used to signal shutdown. nextTick int64 next *Ticker } // Stop turns off a ticker. After Stop, no more ticks will be sent. -func (t *Ticker) Stop() { t.shutdown = true } +func (t *Ticker) Stop() { + // Make it non-blocking so multiple Stops don't block. + _ = t.shutdown <- true +} // 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. @@ -43,11 +47,12 @@ func (a *alarmer) set(ns int64) { case a.wakeTime > ns: // Next tick we expect is too late; shut down the late runner // and (after fallthrough) start a new wakeLoop. - a.wakeMeAt <- -1 + close(a.wakeMeAt) fallthrough case a.wakeMeAt == nil: // There's no wakeLoop, start one. - a.wakeMeAt = make(chan int64, 10) + a.wakeMeAt = make(chan int64) + a.wakeUp = make(chan bool, 1) go wakeLoop(a.wakeMeAt, a.wakeUp) fallthrough case a.wakeTime == 0: @@ -69,19 +74,10 @@ func startTickerLoop() { // 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 but they will share the wakeUp channel and signal that this one -// is done by giving it a negative time request. +// wakeLoop and signal that this one is done by closing the wakeMeAt channel. func wakeLoop(wakeMeAt chan int64, wakeUp chan bool) { - for { - wakeAt := <-wakeMeAt - if wakeAt < 0 { // tickerLoop has started another wakeLoop - return - } - now := Nanoseconds() - if wakeAt > now { - Sleep(wakeAt - now) - now = Nanoseconds() - } + for wakeAt := range wakeMeAt { + Sleep(wakeAt - Nanoseconds()) wakeUp <- true } } @@ -92,9 +88,7 @@ func wakeLoop(wakeMeAt chan int64, wakeUp chan bool) { func tickerLoop() { // Represents the next alarm to be delivered. var alarm alarmer - // All wakeLoops deliver wakeups to this channel. - alarm.wakeUp = make(chan bool, 10) - var now, prevTime, wakeTime int64 + var now, wakeTime int64 var tickers *Ticker for { select { @@ -106,17 +100,13 @@ func tickerLoop() { alarm.set(t.nextTick) case <-alarm.wakeUp: now = Nanoseconds() - // Ignore an old time due to a dying wakeLoop - if now < prevTime { - continue - } 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 { - if t.shutdown { + if _, ok := <-t.shutdown; ok { // Ticker is done; remove it from list. if prev == nil { tickers = t.next @@ -147,25 +137,34 @@ func tickerLoop() { 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 } } - prevTime = now } } +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. +// 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 { - return nil + panic(os.ErrorString("non-positive interval for NewTicker")) } c := make(chan int64, 1) // See comment on send in tickerLoop - t := &Ticker{c, c, ns, false, Nanoseconds() + ns, nil} - once.Do(startTickerLoop) + t := &Ticker{ + C: c, + c: c, + ns: ns, + shutdown: make(chan bool, 1), + nextTick: Nanoseconds() + ns, + } + onceStartTickerLoop.Do(startTickerLoop) // must be run in background so global Tickers can be created go func() { newTicker <- t }() return t diff --git a/src/pkg/time/tick_test.go b/src/pkg/time/tick_test.go index d089a9b98..2a63a0f2b 100644 --- a/src/pkg/time/tick_test.go +++ b/src/pkg/time/tick_test.go @@ -31,7 +31,7 @@ func TestTicker(t *testing.T) { Sleep(2 * Delta) _, received := <-ticker.C if received { - t.Fatalf("Ticker did not shut down") + t.Fatal("Ticker did not shut down") } } diff --git a/src/pkg/time/time_test.go b/src/pkg/time/time_test.go index 32bf9652e..c86bca1b4 100644 --- a/src/pkg/time/time_test.go +++ b/src/pkg/time/time_test.go @@ -25,21 +25,21 @@ type TimeTest struct { } var utctests = []TimeTest{ - TimeTest{0, Time{1970, 1, 1, 0, 0, 0, Thursday, 0, "UTC"}}, - TimeTest{1221681866, Time{2008, 9, 17, 20, 4, 26, Wednesday, 0, "UTC"}}, - TimeTest{-1221681866, Time{1931, 4, 16, 3, 55, 34, Thursday, 0, "UTC"}}, - TimeTest{-11644473600, Time{1601, 1, 1, 0, 0, 0, Monday, 0, "UTC"}}, - TimeTest{599529660, Time{1988, 12, 31, 0, 1, 0, Saturday, 0, "UTC"}}, - TimeTest{978220860, Time{2000, 12, 31, 0, 1, 0, Sunday, 0, "UTC"}}, - TimeTest{1e18, Time{31688740476, 10, 23, 1, 46, 40, Friday, 0, "UTC"}}, - TimeTest{-1e18, Time{-31688736537, 3, 10, 22, 13, 20, Tuesday, 0, "UTC"}}, - TimeTest{0x7fffffffffffffff, Time{292277026596, 12, 4, 15, 30, 7, Sunday, 0, "UTC"}}, - TimeTest{-0x8000000000000000, Time{-292277022657, 1, 27, 8, 29, 52, Sunday, 0, "UTC"}}, + {0, Time{1970, 1, 1, 0, 0, 0, Thursday, 0, "UTC"}}, + {1221681866, Time{2008, 9, 17, 20, 4, 26, Wednesday, 0, "UTC"}}, + {-1221681866, Time{1931, 4, 16, 3, 55, 34, Thursday, 0, "UTC"}}, + {-11644473600, Time{1601, 1, 1, 0, 0, 0, Monday, 0, "UTC"}}, + {599529660, Time{1988, 12, 31, 0, 1, 0, Saturday, 0, "UTC"}}, + {978220860, Time{2000, 12, 31, 0, 1, 0, Sunday, 0, "UTC"}}, + {1e18, Time{31688740476, 10, 23, 1, 46, 40, Friday, 0, "UTC"}}, + {-1e18, Time{-31688736537, 3, 10, 22, 13, 20, Tuesday, 0, "UTC"}}, + {0x7fffffffffffffff, Time{292277026596, 12, 4, 15, 30, 7, Sunday, 0, "UTC"}}, + {-0x8000000000000000, Time{-292277022657, 1, 27, 8, 29, 52, Sunday, 0, "UTC"}}, } var localtests = []TimeTest{ - TimeTest{0, Time{1969, 12, 31, 16, 0, 0, Wednesday, -8 * 60 * 60, "PST"}}, - TimeTest{1221681866, Time{2008, 9, 17, 13, 4, 26, Wednesday, -7 * 60 * 60, "PDT"}}, + {0, Time{1969, 12, 31, 16, 0, 0, Wednesday, -8 * 60 * 60, "PST"}}, + {1221681866, Time{2008, 9, 17, 13, 4, 26, Wednesday, -7 * 60 * 60, "PDT"}}, } func same(t, u *Time) bool { @@ -108,9 +108,9 @@ type TimeFormatTest struct { } var rfc3339Formats = []TimeFormatTest{ - TimeFormatTest{Time{2008, 9, 17, 20, 4, 26, Wednesday, 0, "UTC"}, "2008-09-17T20:04:26Z"}, - TimeFormatTest{Time{1994, 9, 17, 20, 4, 26, Wednesday, -18000, "EST"}, "1994-09-17T20:04:26-05:00"}, - TimeFormatTest{Time{2000, 12, 26, 1, 15, 6, Wednesday, 15600, "OTO"}, "2000-12-26T01:15:06+04:20"}, + {Time{2008, 9, 17, 20, 4, 26, Wednesday, 0, "UTC"}, "2008-09-17T20:04:26Z"}, + {Time{1994, 9, 17, 20, 4, 26, Wednesday, -18000, "EST"}, "1994-09-17T20:04:26-05:00"}, + {Time{2000, 12, 26, 1, 15, 6, Wednesday, 15600, "OTO"}, "2000-12-26T01:15:06+04:20"}, } func TestRFC3339Conversion(t *testing.T) { @@ -130,16 +130,16 @@ type FormatTest struct { } var formatTests = []FormatTest{ - FormatTest{"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010"}, - FormatTest{"UnixDate", UnixDate, "Thu Feb 4 21:00:57 PST 2010"}, - FormatTest{"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0800 2010"}, - FormatTest{"RFC822", RFC822, "04 Feb 10 2100 PST"}, - FormatTest{"RFC850", RFC850, "Thursday, 04-Feb-10 21:00:57 PST"}, - FormatTest{"RFC1123", RFC1123, "Thu, 04 Feb 2010 21:00:57 PST"}, - FormatTest{"RFC3339", RFC3339, "2010-02-04T21:00:57-08:00"}, - FormatTest{"Kitchen", Kitchen, "9:00PM"}, - FormatTest{"am/pm", "3pm", "9pm"}, - FormatTest{"AM/PM", "3PM", "9PM"}, + {"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010"}, + {"UnixDate", UnixDate, "Thu Feb 4 21:00:57 PST 2010"}, + {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0800 2010"}, + {"RFC822", RFC822, "04 Feb 10 2100 PST"}, + {"RFC850", RFC850, "Thursday, 04-Feb-10 21:00:57 PST"}, + {"RFC1123", RFC1123, "Thu, 04 Feb 2010 21:00:57 PST"}, + {"RFC3339", RFC3339, "2010-02-04T21:00:57-08:00"}, + {"Kitchen", Kitchen, "9:00PM"}, + {"am/pm", "3pm", "9pm"}, + {"AM/PM", "3PM", "9PM"}, } func TestFormat(t *testing.T) { @@ -163,15 +163,16 @@ type ParseTest struct { } var parseTests = []ParseTest{ - ParseTest{"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1}, - ParseTest{"UnixDate", UnixDate, "Thu Feb 4 21:00:57 PST 2010", true, true, 1}, - ParseTest{"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0800 2010", true, true, 1}, - ParseTest{"RFC850", RFC850, "Thursday, 04-Feb-10 21:00:57 PST", true, true, 1}, - ParseTest{"RFC1123", RFC1123, "Thu, 04 Feb 2010 21:00:57 PST", true, true, 1}, - ParseTest{"RFC3339", RFC3339, "2010-02-04T21:00:57-08:00", true, false, 1}, + {"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1}, + {"UnixDate", UnixDate, "Thu Feb 4 21:00:57 PST 2010", true, true, 1}, + {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0800 2010", true, true, 1}, + {"RFC850", RFC850, "Thursday, 04-Feb-10 21:00:57 PST", true, true, 1}, + {"RFC1123", RFC1123, "Thu, 04 Feb 2010 21:00:57 PST", true, true, 1}, + {"RFC3339", RFC3339, "2010-02-04T21:00:57-08:00", true, false, 1}, + {"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}, // Amount of white space should not matter. - ParseTest{"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1}, - ParseTest{"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1}, + {"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1}, + {"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1}, } func TestParse(t *testing.T) { @@ -186,11 +187,11 @@ func TestParse(t *testing.T) { } var rubyTests = []ParseTest{ - ParseTest{"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0800 2010", true, true, 1}, + {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0800 2010", true, true, 1}, // Ignore the time zone in the test. If it parses, it'll be OK. - ParseTest{"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0000 2010", false, true, 1}, - ParseTest{"RubyDate", RubyDate, "Thu Feb 04 21:00:57 +0000 2010", false, true, 1}, - ParseTest{"RubyDate", RubyDate, "Thu Feb 04 21:00:57 +1130 2010", false, true, 1}, + {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0000 2010", false, true, 1}, + {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 +0000 2010", false, true, 1}, + {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 +1130 2010", false, true, 1}, } // Problematic time zone format needs special tests. @@ -208,28 +209,28 @@ func TestRubyParse(t *testing.T) { func checkTime(time *Time, test *ParseTest, t *testing.T) { // The time should be Thu Feb 4 21:00:57 PST 2010 if test.yearSign*time.Year != 2010 { - t.Errorf("%s: bad year: %d not %d\n", test.name, 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\n", test.name, time.Month, 2) + t.Errorf("%s: bad month: %d not %d", test.name, time.Month, 2) } if time.Day != 4 { - t.Errorf("%s: bad day: %d not %d\n", test.name, 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\n", test.name, 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\n", test.name, 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\n", test.name, time.Second, 57) + t.Errorf("%s: bad second: %d not %d", test.name, time.Second, 57) } if test.hasTZ && time.ZoneOffset != -28800 { - t.Errorf("%s: bad tz offset: %d not %d\n", test.name, time.ZoneOffset, -28800) + t.Errorf("%s: bad tz offset: %d not %d", test.name, time.ZoneOffset, -28800) } if test.hasWD && time.Weekday != 4 { - t.Errorf("%s: bad weekday: %d not %d\n", test.name, time.Weekday, 4) + t.Errorf("%s: bad weekday: %d not %d", test.name, time.Weekday, 4) } } @@ -271,20 +272,20 @@ type ParseErrorTest struct { } var parseErrorTests = []ParseErrorTest{ - ParseErrorTest{ANSIC, "Feb 4 21:00:60 2010", "parse"}, // cannot parse Feb as Mon - ParseErrorTest{ANSIC, "Thu Feb 4 21:00:57 @2010", "parse"}, - ParseErrorTest{ANSIC, "Thu Feb 4 21:00:60 2010", "second out of range"}, - ParseErrorTest{ANSIC, "Thu Feb 4 21:61:57 2010", "minute out of range"}, - ParseErrorTest{ANSIC, "Thu Feb 4 24:00:60 2010", "hour out of range"}, + {ANSIC, "Feb 4 21:00:60 2010", "parse"}, // cannot parse Feb as Mon + {ANSIC, "Thu Feb 4 21:00:57 @2010", "parse"}, + {ANSIC, "Thu Feb 4 21:00:60 2010", "second out of range"}, + {ANSIC, "Thu Feb 4 21:61:57 2010", "minute out of range"}, + {ANSIC, "Thu Feb 4 24:00:60 2010", "hour out of range"}, } func TestParseErrors(t *testing.T) { for _, test := range parseErrorTests { _, err := Parse(test.format, test.value) if err == nil { - t.Errorf("expected error for %q %q\n", test.format, test.value) + t.Errorf("expected error for %q %q", test.format, test.value) } else if strings.Index(err.String(), test.expect) < 0 { - t.Errorf("expected error with %q for %q %q; got %s\n", test.expect, test.format, test.value, err) + t.Errorf("expected error with %q for %q %q; got %s", test.expect, test.format, test.value, err) } } } @@ -303,6 +304,17 @@ func TestMissingZone(t *testing.T) { } } +func TestMinutesInTimeZone(t *testing.T) { + time, err := Parse(RubyDate, "Mon Jan 02 15:04:05 +0123 2006") + if err != nil { + t.Fatal("error parsing date:", err) + } + expected := (1*60 + 23) * 60 + if time.ZoneOffset != expected { + t.Errorf("ZoneOffset incorrect, expected %d got %d", expected, time.ZoneOffset) + } +} + func BenchmarkSeconds(b *testing.B) { for i := 0; i < b.N; i++ { Seconds() diff --git a/src/pkg/time/zoneinfo.go b/src/pkg/time/zoneinfo.go deleted file mode 100644 index 7884898f7..000000000 --- a/src/pkg/time/zoneinfo.go +++ /dev/null @@ -1,243 +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. - -// Parse "zoneinfo" time zone file. -// This is a fairly standard file format used on OS X, Linux, BSD, Sun, and others. -// See tzfile(5), http://en.wikipedia.org/wiki/Zoneinfo, -// and ftp://munnari.oz.au/pub/oldtz/ - -package time - -import ( - "io/ioutil" - "once" - "os" -) - -const ( - headerSize = 4 + 16 + 4*7 - zoneDir = "/usr/share/zoneinfo/" -) - -// 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) -} - -// 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 -} - -func parseinfo(bytes []byte) (zt []zonetime, ok bool) { - d := data{bytes, false} - - // 4-byte magic "TZif" - if magic := d.read(4); string(magic) != "TZif" { - return nil, false - } - - // 1-byte version, then 15 bytes of padding - var p []byte - if p = d.read(16); len(p) != 16 || p[0] != 0 && p[0] != '2' { - return nil, false - } - - // six big-endian 32-bit integers: - // number of UTC/local indicators - // number of standard/wall indicators - // number of leap seconds - // number of transition times - // number of local time zones - // number of characters of time zone abbrev strings - const ( - NUTCLocal = iota - NStdWall - NLeap - NTime - NZone - NChar - ) - var n [6]int - for i := 0; i < 6; i++ { - nn, ok := d.big4() - if !ok { - return nil, false - } - n[i] = int(nn) - } - - // Transition times. - txtimes := data{d.read(n[NTime] * 4), false} - - // Time zone indices for transition times. - txzones := d.read(n[NTime]) - - // Zone info structures - zonedata := data{d.read(n[NZone] * 6), false} - - // Time zone abbreviations. - abbrev := d.read(n[NChar]) - - // Leap-second time pairs - d.read(n[NLeap] * 8) - - // Whether tx times associated with local time types - // are specified as standard time or wall time. - isstd := d.read(n[NStdWall]) - - // Whether tx times associated with local time types - // are specified as UTC or local time. - isutc := d.read(n[NUTCLocal]) - - if d.error { // ran out of data - return nil, false - } - - // If version == 2, the entire file repeats, this time using - // 8-byte ints for txtimes and leap seconds. - // We won't need those until 2106. - - // Now we can build up a useful data structure. - // First the zone information. - // utcoff[4] isdst[1] nameindex[1] - z := make([]zone, n[NZone]) - for i := 0; i < len(z); i++ { - var ok bool - var n uint32 - if n, ok = zonedata.big4(); !ok { - return nil, false - } - z[i].utcoff = int(n) - var b byte - if b, ok = zonedata.byte(); !ok { - return nil, false - } - z[i].isdst = b != 0 - if b, ok = zonedata.byte(); !ok || int(b) >= len(abbrev) { - return nil, false - } - z[i].name = byteString(abbrev[b:]) - } - - // Now the transition time info. - zt = make([]zonetime, n[NTime]) - for i := 0; i < len(zt); i++ { - var ok bool - var n uint32 - if n, ok = txtimes.big4(); !ok { - return nil, false - } - zt[i].time = int32(n) - if int(txzones[i]) >= len(z) { - return nil, false - } - zt[i].zone = &z[txzones[i]] - if i < len(isstd) { - zt[i].isstd = isstd[i] != 0 - } - if i < len(isutc) { - zt[i].isutc = isutc[i] != 0 - } - } - return zt, true -} - -func readinfofile(name string) ([]zonetime, bool) { - buf, err := ioutil.ReadFile(name) - if err != nil { - return nil, false - } - return parseinfo(buf) -} - -var zones []zonetime - -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. - - tz, err := os.Getenverror("TZ") - switch { - case err == os.ENOENV: - zones, _ = readinfofile("/etc/localtime") - case len(tz) > 0: - zones, _ = readinfofile(zoneDir + tz) - case len(tz) == 0: - // do nothing: use UTC - } -} - -// 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) { - once.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 -} diff --git a/src/pkg/time/zoneinfo_unix.go b/src/pkg/time/zoneinfo_unix.go index 5a8c94aaf..26c86ab03 100644 --- a/src/pkg/time/zoneinfo_unix.go +++ b/src/pkg/time/zoneinfo_unix.go @@ -11,8 +11,8 @@ package time import ( "io/ioutil" - "once" "os" + "sync" ) const ( @@ -203,6 +203,7 @@ func readinfofile(name string) ([]zonetime, bool) { } var zones []zonetime +var onceSetupZone sync.Once func setupZone() { // consult $TZ to find the time zone to use. @@ -223,7 +224,7 @@ func setupZone() { // 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) { - once.Do(setupZone) + onceSetupZone.Do(setupZone) if len(zones) == 0 { return "UTC", 0 } @@ -251,7 +252,7 @@ func lookupTimezone(sec int64) (zone string, offset int) { // 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) { - once.Do(setupZone) + onceSetupZone.Do(setupZone) for _, z := range zones { if name == z.zone.name { return z.zone.utcoff, true diff --git a/src/pkg/time/zoneinfo_windows.go b/src/pkg/time/zoneinfo_windows.go index d249165c1..c357eec62 100644 --- a/src/pkg/time/zoneinfo_windows.go +++ b/src/pkg/time/zoneinfo_windows.go @@ -6,8 +6,8 @@ package time import ( "syscall" + "sync" "os" - "once" ) // BUG(brainman): The Windows implementation assumes that @@ -121,6 +121,7 @@ func (zi *zoneinfo) pickZone(t *Time) *zone { var tz zoneinfo var initError os.Error +var onceSetupZone sync.Once func setupZone() { var i syscall.Timezoneinformation @@ -145,7 +146,7 @@ func setupZone() { // 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) { - once.Do(setupZone) + onceSetupZone.Do(setupZone) if initError != nil { return "", 0 } @@ -174,7 +175,7 @@ func lookupTimezone(sec int64) (zone string, offset int) { // 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) { - once.Do(setupZone) + onceSetupZone.Do(setupZone) if initError != nil { return 0, false } |