summaryrefslogtreecommitdiff
path: root/src/pkg/time
diff options
context:
space:
mode:
authorOndřej Surý <ondrej@sury.org>2011-01-17 12:40:45 +0100
committerOndřej Surý <ondrej@sury.org>2011-01-17 12:40:45 +0100
commit3e45412327a2654a77944249962b3652e6142299 (patch)
treebc3bf69452afa055423cbe0c5cfa8ca357df6ccf /src/pkg/time
parentc533680039762cacbc37db8dc7eed074c3e497be (diff)
downloadgolang-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/Makefile5
-rw-r--r--src/pkg/time/format.go41
-rw-r--r--src/pkg/time/sleep.go135
-rw-r--r--src/pkg/time/sleep_test.go108
-rw-r--r--src/pkg/time/tick.go59
-rw-r--r--src/pkg/time/tick_test.go2
-rw-r--r--src/pkg/time/time_test.go116
-rw-r--r--src/pkg/time/zoneinfo.go243
-rw-r--r--src/pkg/time/zoneinfo_unix.go7
-rw-r--r--src/pkg/time/zoneinfo_windows.go7
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
}