diff options
Diffstat (limited to 'libgo/go/time')
-rw-r--r-- | libgo/go/time/format.go | 618 | ||||
-rw-r--r-- | libgo/go/time/sleep.go | 151 | ||||
-rw-r--r-- | libgo/go/time/sleep_test.go | 134 | ||||
-rw-r--r-- | libgo/go/time/tick.go | 171 | ||||
-rw-r--r-- | libgo/go/time/tick_test.go | 45 | ||||
-rw-r--r-- | libgo/go/time/time.go | 229 | ||||
-rw-r--r-- | libgo/go/time/time_test.go | 341 | ||||
-rw-r--r-- | libgo/go/time/zoneinfo_unix.go | 267 | ||||
-rw-r--r-- | libgo/go/time/zoneinfo_windows.go | 192 |
9 files changed, 2148 insertions, 0 deletions
diff --git a/libgo/go/time/format.go b/libgo/go/time/format.go new file mode 100644 index 000000000..7b5a8f3b6 --- /dev/null +++ b/libgo/go/time/format.go @@ -0,0 +1,618 @@ +package time + +import ( + "bytes" + "os" + "strconv" +) + +const ( + numeric = iota + alphabetic + separator + plus + minus +) + +// These are predefined layouts for use in Time.Format. +// The standard time used in the layouts is: +// Mon Jan 2 15:04:05 MST 2006 (MST is GMT-0700) +// which is Unix time 1136243045. +// (Think of it as 01/02 03:04:05PM '06 -0700.) +// To define your own format, write down what the standard +// time would look like formatted your way. +// +// Within the format string, an underscore _ represents a space that may be +// replaced by a digit if the following number (a day) has two digits; for +// compatibility with fixed-width Unix time formats. +// +// Numeric time zone offsets format as follows: +// -0700 ±hhmm +// -07:00 ±hh:mm +// Replacing the sign in the format with a Z triggers +// the ISO 8601 behavior of printing Z instead of an +// offset for the UTC zone. Thus: +// Z0700 Z or ±hhmm +// Z07:00 Z or ±hh:mm +const ( + ANSIC = "Mon Jan _2 15:04:05 2006" + UnixDate = "Mon Jan _2 15:04:05 MST 2006" + RubyDate = "Mon Jan 02 15:04:05 -0700 2006" + RFC822 = "02 Jan 06 1504 MST" + // RFC822 with Zulu time. + RFC822Z = "02 Jan 06 1504 -0700" + RFC850 = "Monday, 02-Jan-06 15:04:05 MST" + RFC1123 = "Mon, 02 Jan 2006 15:04:05 MST" + RFC3339 = "2006-01-02T15:04:05Z07:00" + Kitchen = "3:04PM" +) + +const ( + stdLongMonth = "January" + stdMonth = "Jan" + stdNumMonth = "1" + stdZeroMonth = "01" + stdLongWeekDay = "Monday" + stdWeekDay = "Mon" + stdDay = "2" + stdUnderDay = "_2" + stdZeroDay = "02" + stdHour = "15" + stdHour12 = "3" + stdZeroHour12 = "03" + stdMinute = "4" + stdZeroMinute = "04" + stdSecond = "5" + stdZeroSecond = "05" + stdLongYear = "2006" + stdYear = "06" + stdPM = "PM" + stdpm = "pm" + stdTZ = "MST" + stdISO8601TZ = "Z0700" // prints Z for UTC + stdISO8601ColonTZ = "Z07:00" // prints Z for UTC + stdNumTZ = "-0700" // always numeric + stdNumShortTZ = "-07" // always numeric + stdNumColonTZ = "-07:00" // always numeric +) + +// nextStdChunk finds the first occurrence of a std string in +// layout and returns the text before, the std string, and the text after. +func nextStdChunk(layout string) (prefix, std, suffix string) { + for i := 0; i < len(layout); i++ { + switch layout[i] { + case 'J': // January, Jan + if len(layout) >= i+7 && layout[i:i+7] == stdLongMonth { + return layout[0:i], stdLongMonth, layout[i+7:] + } + if len(layout) >= i+3 && layout[i:i+3] == stdMonth { + return layout[0:i], stdMonth, layout[i+3:] + } + + case 'M': // Monday, Mon, MST + if len(layout) >= i+6 && layout[i:i+6] == stdLongWeekDay { + return layout[0:i], stdLongWeekDay, layout[i+6:] + } + if len(layout) >= i+3 { + if layout[i:i+3] == stdWeekDay { + return layout[0:i], stdWeekDay, layout[i+3:] + } + if layout[i:i+3] == stdTZ { + return layout[0:i], stdTZ, layout[i+3:] + } + } + + case '0': // 01, 02, 03, 04, 05, 06 + if len(layout) >= i+2 && '1' <= layout[i+1] && layout[i+1] <= '6' { + return layout[0:i], layout[i : i+2], layout[i+2:] + } + + case '1': // 15, 1 + if len(layout) >= i+2 && layout[i+1] == '5' { + return layout[0:i], stdHour, layout[i+2:] + } + return layout[0:i], stdNumMonth, layout[i+1:] + + case '2': // 2006, 2 + if len(layout) >= i+4 && layout[i:i+4] == stdLongYear { + return layout[0:i], stdLongYear, layout[i+4:] + } + return layout[0:i], stdDay, layout[i+1:] + + case '_': // _2 + if len(layout) >= i+2 && layout[i+1] == '2' { + return layout[0:i], stdUnderDay, layout[i+2:] + } + + case '3', '4', '5': // 3, 4, 5 + return layout[0:i], layout[i : i+1], layout[i+1:] + + case 'P': // PM + if len(layout) >= i+2 && layout[i+1] == 'M' { + return layout[0:i], layout[i : i+2], layout[i+2:] + } + + case 'p': // pm + if len(layout) >= i+2 && layout[i+1] == 'm' { + return layout[0:i], layout[i : i+2], layout[i+2:] + } + + case '-': // -0700, -07:00, -07 + if len(layout) >= i+5 && layout[i:i+5] == stdNumTZ { + return layout[0:i], layout[i : i+5], layout[i+5:] + } + if len(layout) >= i+6 && layout[i:i+6] == stdNumColonTZ { + return layout[0:i], layout[i : i+6], layout[i+6:] + } + if len(layout) >= i+3 && layout[i:i+3] == stdNumShortTZ { + return layout[0:i], layout[i : i+3], layout[i+3:] + } + case 'Z': // Z0700, Z07:00 + if len(layout) >= i+5 && layout[i:i+5] == stdISO8601TZ { + return layout[0:i], layout[i : i+5], layout[i+5:] + } + if len(layout) >= i+6 && layout[i:i+6] == stdISO8601ColonTZ { + return layout[0:i], layout[i : i+6], layout[i+6:] + } + } + } + return layout, "", "" +} + +var longDayNames = []string{ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", +} + +var shortDayNames = []string{ + "Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", +} + +var shortMonthNames = []string{ + "---", + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", +} + +var longMonthNames = []string{ + "---", + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +} + +func lookup(tab []string, val string) (int, string, os.Error) { + for i, v := range tab { + if len(val) >= len(v) && val[0:len(v)] == v { + return i, val[len(v):], nil + } + } + return -1, val, errBad +} + +func pad(i int, padding string) string { + s := strconv.Itoa(i) + if i < 10 { + s = padding + s + } + return s +} + +func zeroPad(i int) string { return pad(i, "0") } + +// Format returns a textual representation of the time value formatted +// according to layout. The layout defines the format by showing the +// representation of a standard time, which is then used to describe +// the time to be formatted. Predefined layouts ANSIC, UnixDate, +// RFC3339 and others describe standard representations. For more +// information about the formats, see the documentation for ANSIC. +func (t *Time) Format(layout string) string { + b := new(bytes.Buffer) + // Each iteration generates one std value. + for { + prefix, std, suffix := nextStdChunk(layout) + b.WriteString(prefix) + if std == "" { + break + } + var p string + switch std { + case stdYear: + p = strconv.Itoa64(t.Year % 100) + case stdLongYear: + p = strconv.Itoa64(t.Year) + case stdMonth: + p = shortMonthNames[t.Month] + case stdLongMonth: + p = longMonthNames[t.Month] + case stdNumMonth: + p = strconv.Itoa(t.Month) + case stdZeroMonth: + p = zeroPad(t.Month) + case stdWeekDay: + p = shortDayNames[t.Weekday] + case stdLongWeekDay: + p = longDayNames[t.Weekday] + case stdDay: + p = strconv.Itoa(t.Day) + case stdUnderDay: + p = pad(t.Day, " ") + case stdZeroDay: + p = zeroPad(t.Day) + case stdHour: + p = zeroPad(t.Hour) + case stdHour12: + p = strconv.Itoa(t.Hour % 12) + case stdZeroHour12: + p = zeroPad(t.Hour % 12) + case stdMinute: + p = strconv.Itoa(t.Minute) + case stdZeroMinute: + p = zeroPad(t.Minute) + case stdSecond: + p = strconv.Itoa(t.Second) + case stdZeroSecond: + p = zeroPad(t.Second) + case stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumColonTZ: + // Ugly special case. We cheat and take the "Z" variants + // to mean "the time zone as formatted for ISO 8601". + if t.ZoneOffset == 0 && std[0] == 'Z' { + p = "Z" + break + } + zone := t.ZoneOffset / 60 // convert to minutes + if zone < 0 { + p = "-" + zone = -zone + } else { + p = "+" + } + p += zeroPad(zone / 60) + if std == stdISO8601ColonTZ || std == stdNumColonTZ { + p += ":" + } + p += zeroPad(zone % 60) + case stdPM: + if t.Hour >= 12 { + p = "PM" + } else { + p = "AM" + } + case stdpm: + if t.Hour >= 12 { + p = "pm" + } else { + p = "am" + } + case stdTZ: + if t.Zone != "" { + p = t.Zone + } else { + // No time zone known for this time, but we must print one. + // Use the -0700 format. + zone := t.ZoneOffset / 60 // convert to minutes + if zone < 0 { + p = "-" + zone = -zone + } else { + p = "+" + } + p += zeroPad(zone / 60) + p += zeroPad(zone % 60) + } + } + b.WriteString(p) + layout = suffix + } + return b.String() +} + +// String returns a Unix-style representation of the time value. +func (t *Time) String() string { + if t == nil { + return "<nil>" + } + return t.Format(UnixDate) +} + +var errBad = os.ErrorString("bad") // just a marker; not returned to user + +// ParseError describes a problem parsing a time string. +type ParseError struct { + Layout string + Value string + LayoutElem string + ValueElem string + Message string +} + +// String is the string representation of a ParseError. +func (e *ParseError) String() string { + if e.Message == "" { + return "parsing time " + + strconv.Quote(e.Value) + " as " + + strconv.Quote(e.Layout) + ": cannot parse " + + strconv.Quote(e.ValueElem) + " as " + + strconv.Quote(e.LayoutElem) + } + return "parsing time " + + strconv.Quote(e.Value) + e.Message +} + +// getnum parses s[0:1] or s[0:2] (fixed forces the latter) +// as a decimal integer and returns the integer and the +// remainder of the string. +func getnum(s string, fixed bool) (int, string, os.Error) { + if len(s) == 0 || s[0] < '0' || s[0] > '9' { + return 0, s, errBad + } + if len(s) == 1 || s[1] < '0' || s[1] > '9' { + if fixed { + return 0, s, errBad + } + return int(s[0] - '0'), s[1:], nil + } + return int(s[0]-'0')*10 + int(s[1]-'0'), s[2:], nil +} + +func cutspace(s string) string { + for len(s) > 0 && s[0] == ' ' { + s = s[1:] + } + return s +} + +// skip removes the given prefix from value, +// treating runs of space characters as equivalent. +func skip(value, prefix string) (string, os.Error) { + for len(prefix) > 0 { + if prefix[0] == ' ' { + if len(value) > 0 && value[0] != ' ' { + return "", errBad + } + prefix = cutspace(prefix) + value = cutspace(value) + continue + } + if len(value) == 0 || value[0] != prefix[0] { + return "", errBad + } + prefix = prefix[1:] + value = value[1:] + } + return value, nil +} + +// Parse parses a formatted string and returns the time value it represents. +// The layout defines the format by showing the representation of a standard +// time, which is then used to describe the string to be parsed. Predefined +// layouts ANSIC, UnixDate, RFC3339 and others describe standard +// representations.For more information about the formats, see the +// documentation for ANSIC. +// +// Only those elements present in the value will be set in the returned time +// structure. Also, if the input string represents an inconsistent time +// (such as having the wrong day of the week), the returned value will also +// be inconsistent. In any case, the elements of the returned time will be +// sane: hours in 0..23, minutes in 0..59, day of month in 0..31, etc. +// Years must be in the range 0000..9999. +func Parse(alayout, avalue string) (*Time, os.Error) { + var t Time + rangeErrString := "" // set if a value is out of range + pmSet := false // do we need to add 12 to the hour? + layout, value := alayout, avalue + // Each iteration processes one std value. + for { + var err os.Error + prefix, std, suffix := nextStdChunk(layout) + value, err = skip(value, prefix) + if err != nil { + return nil, &ParseError{alayout, avalue, prefix, value, ""} + } + if len(std) == 0 { + if len(value) != 0 { + return nil, &ParseError{alayout, avalue, "", value, ": extra text: " + value} + } + break + } + layout = suffix + var p string + switch std { + case stdYear: + if len(value) < 2 { + err = errBad + break + } + p, value = value[0:2], value[2:] + t.Year, err = strconv.Atoi64(p) + if t.Year >= 69 { // Unix time starts Dec 31 1969 in some time zones + t.Year += 1900 + } else { + t.Year += 2000 + } + case stdLongYear: + if len(value) < 4 || value[0] < '0' || value[0] > '9' { + err = errBad + break + } + p, value = value[0:4], value[4:] + t.Year, err = strconv.Atoi64(p) + case stdMonth: + t.Month, value, err = lookup(shortMonthNames, value) + case stdLongMonth: + t.Month, value, err = lookup(longMonthNames, value) + case stdNumMonth, stdZeroMonth: + t.Month, value, err = getnum(value, std == stdZeroMonth) + if t.Month <= 0 || 12 < t.Month { + rangeErrString = "month" + } + case stdWeekDay: + t.Weekday, value, err = lookup(shortDayNames, value) + case stdLongWeekDay: + t.Weekday, value, err = lookup(longDayNames, value) + case stdDay, stdUnderDay, stdZeroDay: + if std == stdUnderDay && len(value) > 0 && value[0] == ' ' { + value = value[1:] + } + t.Day, value, err = getnum(value, std == stdZeroDay) + if t.Day < 0 || 31 < t.Day { + // TODO: be more thorough in date check? + rangeErrString = "day" + } + case stdHour: + t.Hour, value, err = getnum(value, false) + if t.Hour < 0 || 24 <= t.Hour { + rangeErrString = "hour" + } + case stdHour12, stdZeroHour12: + t.Hour, value, err = getnum(value, std == stdZeroHour12) + if t.Hour < 0 || 12 < t.Hour { + rangeErrString = "hour" + } + case stdMinute, stdZeroMinute: + t.Minute, value, err = getnum(value, std == stdZeroMinute) + if t.Minute < 0 || 60 <= t.Minute { + rangeErrString = "minute" + } + case stdSecond, stdZeroSecond: + t.Second, value, err = getnum(value, std == stdZeroSecond) + if t.Second < 0 || 60 <= t.Second { + rangeErrString = "second" + } + case stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ: + if std[0] == 'Z' && len(value) >= 1 && value[0] == 'Z' { + value = value[1:] + t.Zone = "UTC" + break + } + var sign, hh, mm string + if std == stdISO8601ColonTZ || std == stdNumColonTZ { + if len(value) < 6 { + err = errBad + break + } + if value[3] != ':' { + err = errBad + break + } + sign, hh, mm, value = value[0:1], value[1:3], value[4:6], value[6:] + } else if std == stdNumShortTZ { + if len(value) < 3 { + err = errBad + break + } + sign, hh, mm, value = value[0:1], value[1:3], "00", value[3:] + } else { + if len(value) < 5 { + err = errBad + break + } + sign, hh, mm, value = value[0:1], value[1:3], value[3:5], value[5:] + } + var hr, min int + hr, err = strconv.Atoi(hh) + if err == nil { + min, err = strconv.Atoi(mm) + } + t.ZoneOffset = (hr*60 + min) * 60 // offset is in seconds + switch sign[0] { + case '+': + case '-': + t.ZoneOffset = -t.ZoneOffset + default: + err = errBad + } + case stdPM: + if len(value) < 2 { + err = errBad + break + } + p, value = value[0:2], value[2:] + if p == "PM" { + pmSet = true + } else if p != "AM" { + err = errBad + } + case stdpm: + if len(value) < 2 { + err = errBad + break + } + p, value = value[0:2], value[2:] + if p == "pm" { + pmSet = true + } else if p != "am" { + err = errBad + } + case stdTZ: + // Does it look like a time zone? + if len(value) >= 3 && value[0:3] == "UTC" { + t.Zone, value = value[0:3], value[3:] + break + } + + if len(value) >= 3 && value[2] == 'T' { + p, value = value[0:3], value[3:] + } else if len(value) >= 4 && value[3] == 'T' { + p, value = value[0:4], value[4:] + } else { + err = errBad + break + } + for i := 0; i < len(p); i++ { + if p[i] < 'A' || 'Z' < p[i] { + err = errBad + } + } + if err != nil { + break + } + // It's a valid format. + t.Zone = p + // Can we find its offset? + if offset, found := lookupByName(p); found { + t.ZoneOffset = offset + } + } + if rangeErrString != "" { + return nil, &ParseError{alayout, avalue, std, value, ": " + rangeErrString + " out of range"} + } + if err != nil { + return nil, &ParseError{alayout, avalue, std, value, ""} + } + } + if pmSet && t.Hour < 12 { + t.Hour += 12 + } + return &t, nil +} diff --git a/libgo/go/time/sleep.go b/libgo/go/time/sleep.go new file mode 100644 index 000000000..3538775ad --- /dev/null +++ b/libgo/go/time/sleep.go @@ -0,0 +1,151 @@ +// 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" + "syscall" + "sync" + "container/heap" +) + +// 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 + end := t + ns + for t < end { + errno := syscall.Sleep(end - t) + if errno != 0 && errno != syscall.EINTR { + return 0, os.NewSyscallError("sleep", errno) + } + t = Nanoseconds() + } + 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/libgo/go/time/sleep_test.go b/libgo/go/time/sleep_test.go new file mode 100644 index 000000000..9e36288f8 --- /dev/null +++ b/libgo/go/time/sleep_test.go @@ -0,0 +1,134 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package time_test + +import ( + "os" + "syscall" + "testing" + "sort" + . "time" +) + +func TestSleep(t *testing.T) { + const delay = int64(100e6) + go func() { + Sleep(delay / 2) + syscall.Kill(os.Getpid(), syscall.SIGCHLD) + }() + start := Nanoseconds() + Sleep(delay) + duration := Nanoseconds() - start + if duration < delay { + t.Fatalf("Sleep(%d) slept for only %d ns", delay, duration) + } +} + +// Test the basic function calling behavior. Correct queueing +// behavior is tested elsewhere, since After and AfterFunc share +// the same code. +func TestAfterFunc(t *testing.T) { + i := 10 + c := make(chan bool) + var f func() + f = func() { + i-- + if i >= 0 { + AfterFunc(0, f) + Sleep(1e9) + } else { + c <- true + } + } + + AfterFunc(0, f) + <-c +} + +func BenchmarkAfterFunc(b *testing.B) { + i := b.N + c := make(chan bool) + var f func() + f = func() { + i-- + if i >= 0 { + AfterFunc(0, f) + } else { + c <- true + } + } + + AfterFunc(0, f) + <-c +} + +func 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/libgo/go/time/tick.go b/libgo/go/time/tick.go new file mode 100644 index 000000000..ddd727270 --- /dev/null +++ b/libgo/go/time/tick.go @@ -0,0 +1,171 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package time + +import ( + "os" + "sync" +) + +// A Ticker holds a synchronous channel that delivers `ticks' of a clock +// at intervals. +type Ticker struct { + C <-chan int64 // The channel on which the ticks are delivered. + c chan<- int64 // The same channel, but the end we use. + ns int64 + shutdown chan bool // Buffered channel used to signal shutdown. + nextTick int64 + next *Ticker +} + +// Stop turns off a ticker. After Stop, no more ticks will be sent. +func (t *Ticker) Stop() { + // 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. +func Tick(ns int64) <-chan int64 { + if ns <= 0 { + return nil + } + return NewTicker(ns).C +} + +type alarmer struct { + wakeUp chan bool // wakeup signals sent/received here + wakeMeAt chan int64 + wakeTime int64 +} + +// Set alarm to go off at time ns, if not already set earlier. +func (a *alarmer) set(ns int64) { + switch { + case a.wakeTime > ns: + // Next tick we expect is too late; shut down the late runner + // and (after fallthrough) start a new wakeLoop. + close(a.wakeMeAt) + fallthrough + case a.wakeMeAt == nil: + // There's no wakeLoop, start one. + a.wakeMeAt = make(chan int64) + a.wakeUp = make(chan bool, 1) + go wakeLoop(a.wakeMeAt, a.wakeUp) + fallthrough + case a.wakeTime == 0: + // Nobody else is waiting; it's just us. + a.wakeTime = ns + a.wakeMeAt <- ns + default: + // There's already someone scheduled. + } +} + +// Channel to notify tickerLoop of new Tickers being created. +var newTicker chan *Ticker + +func startTickerLoop() { + newTicker = make(chan *Ticker) + go tickerLoop() +} + +// wakeLoop delivers ticks at scheduled times, sleeping until the right moment. +// If another, earlier Ticker is created while it sleeps, tickerLoop() will start a new +// wakeLoop and signal that this one is done by closing the wakeMeAt channel. +func wakeLoop(wakeMeAt chan int64, wakeUp chan bool) { + for wakeAt := range wakeMeAt { + Sleep(wakeAt - Nanoseconds()) + wakeUp <- true + } +} + +// A single tickerLoop serves all ticks to Tickers. It waits for two events: +// either the creation of a new Ticker or a tick from the alarm, +// signalling 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 { + if _, ok := <-t.shutdown; ok { + // Ticker is done; remove it from list. + if prev == nil { + tickers = t.next + } else { + prev.next = t.next + } + continue + } + 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.ErrorString("non-positive interval for NewTicker")) + } + c := make(chan int64, 1) // See comment on send in tickerLoop + t := &Ticker{ + C: c, + c: c, + ns: ns, + shutdown: make(chan bool, 1), + nextTick: Nanoseconds() + ns, + } + onceStartTickerLoop.Do(startTickerLoop) + // must be run in background so global Tickers can be created + go func() { newTicker <- t }() + return t +} diff --git a/libgo/go/time/tick_test.go b/libgo/go/time/tick_test.go new file mode 100644 index 000000000..2a63a0f2b --- /dev/null +++ b/libgo/go/time/tick_test.go @@ -0,0 +1,45 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package time_test + +import ( + "testing" + . "time" +) + +func TestTicker(t *testing.T) { + const ( + Delta = 100 * 1e6 + Count = 10 + ) + ticker := NewTicker(Delta) + t0 := Nanoseconds() + for i := 0; i < Count; i++ { + <-ticker.C + } + ticker.Stop() + t1 := Nanoseconds() + ns := t1 - t0 + target := int64(Delta * Count) + slop := target * 2 / 10 + if ns < target-slop || ns > target+slop { + t.Fatalf("%d ticks of %g ns took %g ns, expected %g", Count, float64(Delta), float64(ns), float64(target)) + } + // Now test that the ticker stopped + Sleep(2 * Delta) + _, received := <-ticker.C + if received { + t.Fatal("Ticker did not shut down") + } +} + +// Test that a bug tearing down a ticker has been fixed. This routine should not deadlock. +func TestTeardown(t *testing.T) { + for i := 0; i < 3; i++ { + ticker := NewTicker(1e8) + <-ticker.C + ticker.Stop() + } +} diff --git a/libgo/go/time/time.go b/libgo/go/time/time.go new file mode 100644 index 000000000..4abd11230 --- /dev/null +++ b/libgo/go/time/time.go @@ -0,0 +1,229 @@ +// 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. + +// The time package provides functionality for measuring and +// displaying time. +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 +} + +// Days of the week. +const ( + Sunday = iota + Monday + Tuesday + Wednesday + Thursday + Friday + Saturday +) + +// Time is the struct representing a parsed time value. +type Time struct { + Year int64 // 2006 is 2006 + Month, Day int // Jan-2 is 1, 2 + Hour, Minute, Second int // 15:04:05 is 15, 4, 5. + Weekday int // Sunday, Monday, ... + ZoneOffset int // seconds east of UTC, e.g. -7*60 for -0700 + Zone string // e.g., "MST" +} + +var nonleapyear = []int{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} +var leapyear = []int{31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31} + +func months(year int64) []int { + if year%4 == 0 && (year%100 != 0 || year%400 == 0) { + return leapyear + } + return nonleapyear +} + +const ( + secondsPerDay = 24 * 60 * 60 + daysPer400Years = 365*400 + 97 + daysPer100Years = 365*100 + 24 + daysPer4Years = 365*4 + 1 + days1970To2001 = 31*365 + 8 +) + +// SecondsToUTC converts sec, in number of seconds since the Unix epoch, +// into a parsed Time value in the UTC time zone. +func SecondsToUTC(sec int64) *Time { + t := new(Time) + + // Split into time and day. + day := sec / secondsPerDay + sec -= day * secondsPerDay + if sec < 0 { + day-- + sec += secondsPerDay + } + + // Time + t.Hour = int(sec / 3600) + t.Minute = int((sec / 60) % 60) + t.Second = int(sec % 60) + + // Day 0 = January 1, 1970 was a Thursday + t.Weekday = int((day + Thursday) % 7) + if t.Weekday < 0 { + t.Weekday += 7 + } + + // Change day from 0 = 1970 to 0 = 2001, + // to make leap year calculations easier + // (2001 begins 4-, 100-, and 400-year cycles ending in a leap year.) + day -= days1970To2001 + + year := int64(2001) + if day < 0 { + // Go back enough 400 year cycles to make day positive. + n := -day/daysPer400Years + 1 + year -= 400 * n + day += daysPer400Years * n + } + + // Cut off 400 year cycles. + n := day / daysPer400Years + year += 400 * n + day -= daysPer400Years * n + + // Cut off 100-year cycles + n = day / daysPer100Years + if n > 3 { // happens on last day of 400th year + n = 3 + } + year += 100 * n + day -= daysPer100Years * n + + // Cut off 4-year cycles + n = day / daysPer4Years + if n > 24 { // happens on last day of 100th year + n = 24 + } + year += 4 * n + day -= daysPer4Years * n + + // Cut off non-leap years. + n = day / 365 + if n > 3 { // happens on last day of 4th year + n = 3 + } + year += n + day -= 365 * n + + t.Year = year + + // If someone ever needs yearday, + // tyearday = day (+1?) + + months := months(year) + var m int + yday := int(day) + for m = 0; m < 12 && yday >= months[m]; m++ { + yday -= months[m] + } + t.Month = m + 1 + t.Day = yday + 1 + t.Zone = "UTC" + + return t +} + +// UTC returns the current time as a parsed Time value in the UTC time zone. +func UTC() *Time { return SecondsToUTC(Seconds()) } + +// SecondsToLocalTime converts sec, in number of seconds since the Unix epoch, +// into a parsed Time value in the local time zone. +func SecondsToLocalTime(sec int64) *Time { + z, offset := lookupTimezone(sec) + t := SecondsToUTC(sec + int64(offset)) + t.Zone = z + t.ZoneOffset = offset + return t +} + +// LocalTime returns the current time as a parsed Time value in the local time zone. +func LocalTime() *Time { return SecondsToLocalTime(Seconds()) } + +// Seconds returns the number of seconds since January 1, 1970 represented by the +// parsed Time value. +func (t *Time) Seconds() int64 { + // First, accumulate days since January 1, 2001. + // Using 2001 instead of 1970 makes the leap-year + // handling easier (see SecondsToUTC), because + // it is at the beginning of the 4-, 100-, and 400-year cycles. + day := int64(0) + + // Rewrite year to be >= 2001. + year := t.Year + if year < 2001 { + n := (2001-year)/400 + 1 + year += 400 * n + day -= daysPer400Years * n + } + + // Add in days from 400-year cycles. + n := (year - 2001) / 400 + year -= 400 * n + day += daysPer400Years * n + + // Add in 100-year cycles. + n = (year - 2001) / 100 + year -= 100 * n + day += daysPer100Years * n + + // Add in 4-year cycles. + n = (year - 2001) / 4 + year -= 4 * n + day += daysPer4Years * n + + // Add in non-leap years. + n = year - 2001 + day += 365 * n + + // Add in days this year. + months := months(t.Year) + for m := 0; m < t.Month-1; m++ { + day += int64(months[m]) + } + day += int64(t.Day - 1) + + // Convert days to seconds since January 1, 2001. + sec := day * secondsPerDay + + // Add in time elapsed today. + sec += int64(t.Hour) * 3600 + sec += int64(t.Minute) * 60 + sec += int64(t.Second) + + // Convert from seconds since 2001 to seconds since 1970. + sec += days1970To2001 * secondsPerDay + + // Account for local time zone. + sec -= int64(t.ZoneOffset) + return sec +} diff --git a/libgo/go/time/time_test.go b/libgo/go/time/time_test.go new file mode 100644 index 000000000..c86bca1b4 --- /dev/null +++ b/libgo/go/time/time_test.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. + +package time_test + +import ( + "os" + "strings" + "testing" + "testing/quick" + . "time" +) + +func init() { + // Force US Pacific time for daylight-savings + // tests below (localtests). Needs to be set + // before the first call into the time library. + os.Setenv("TZ", "America/Los_Angeles") +} + +type TimeTest struct { + seconds int64 + golden Time +} + +var utctests = []TimeTest{ + {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{ + {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 { + 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.Weekday == u.Weekday && + t.ZoneOffset == u.ZoneOffset && + t.Zone == u.Zone +} + +func TestSecondsToUTC(t *testing.T) { + for i := 0; i < len(utctests); i++ { + sec := utctests[i].seconds + golden := &utctests[i].golden + tm := SecondsToUTC(sec) + newsec := tm.Seconds() + if newsec != sec { + t.Errorf("SecondsToUTC(%d).Seconds() = %d", sec, newsec) + } + if !same(tm, golden) { + t.Errorf("SecondsToUTC(%d):", sec) + t.Errorf(" want=%+v", *golden) + t.Errorf(" have=%+v", *tm) + } + } +} + +func TestSecondsToLocalTime(t *testing.T) { + for i := 0; i < len(localtests); i++ { + sec := localtests[i].seconds + golden := &localtests[i].golden + tm := SecondsToLocalTime(sec) + newsec := tm.Seconds() + if newsec != sec { + t.Errorf("SecondsToLocalTime(%d).Seconds() = %d", sec, newsec) + } + if !same(tm, golden) { + t.Errorf("SecondsToLocalTime(%d):", sec) + t.Errorf(" want=%+v", *golden) + t.Errorf(" have=%+v", *tm) + } + } +} + +func TestSecondsToUTCAndBack(t *testing.T) { + f := func(sec int64) bool { return SecondsToUTC(sec).Seconds() == sec } + f32 := func(sec int32) bool { return f(int64(sec)) } + cfg := &quick.Config{MaxCount: 10000} + + // Try a reasonable date first, then the huge ones. + if err := quick.Check(f32, cfg); err != nil { + t.Fatal(err) + } + if err := quick.Check(f, cfg); err != nil { + t.Fatal(err) + } +} + +type TimeFormatTest struct { + time Time + formattedValue string +} + +var rfc3339Formats = []TimeFormatTest{ + {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) { + for _, f := range rfc3339Formats { + if f.time.Format(RFC3339) != f.formattedValue { + t.Error("RFC3339:") + t.Errorf(" want=%+v", f.formattedValue) + t.Errorf(" have=%+v", f.time.Format(RFC3339)) + } + } +} + +type FormatTest struct { + name string + format string + result string +} + +var formatTests = []FormatTest{ + {"ANSIC", ANSIC, "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) { + // The numeric time represents Thu Feb 4 21:00:57 PST 2010 + time := SecondsToLocalTime(1265346057) + for _, test := range formatTests { + result := time.Format(test.format) + if result != test.result { + t.Errorf("%s expected %q got %q", test.name, test.result, result) + } + } +} + +type ParseTest struct { + name string + format string + value string + hasTZ bool // contains a time zone + hasWD bool // contains a weekday + yearSign int64 // sign of year +} + +var parseTests = []ParseTest{ + {"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. + {"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) { + for _, test := range parseTests { + time, err := Parse(test.format, test.value) + if err != nil { + t.Errorf("%s error: %v", test.name, err) + } else { + checkTime(time, &test, t) + } + } +} + +var rubyTests = []ParseTest{ + {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0800 2010", true, true, 1}, + // Ignore the time zone in the test. If it parses, it'll be OK. + {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0000 2010", false, true, 1}, + {"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. +func TestRubyParse(t *testing.T) { + for _, test := range rubyTests { + time, err := Parse(test.format, test.value) + if err != nil { + t.Errorf("%s error: %v", test.name, err) + } else { + checkTime(time, &test, t) + } + } +} + +func checkTime(time *Time, test *ParseTest, t *testing.T) { + // The time should be Thu Feb 4 21:00:57 PST 2010 + if test.yearSign*time.Year != 2010 { + t.Errorf("%s: bad year: %d not %d", test.name, time.Year, 2010) + } + if time.Month != 2 { + t.Errorf("%s: bad month: %d not %d", test.name, time.Month, 2) + } + if time.Day != 4 { + t.Errorf("%s: bad day: %d not %d", test.name, time.Day, 4) + } + if time.Hour != 21 { + t.Errorf("%s: bad hour: %d not %d", test.name, time.Hour, 21) + } + if time.Minute != 0 { + t.Errorf("%s: bad minute: %d not %d", test.name, time.Minute, 0) + } + if time.Second != 57 { + t.Errorf("%s: bad second: %d not %d", test.name, time.Second, 57) + } + if test.hasTZ && time.ZoneOffset != -28800 { + t.Errorf("%s: bad tz offset: %d not %d", test.name, time.ZoneOffset, -28800) + } + if test.hasWD && time.Weekday != 4 { + t.Errorf("%s: bad weekday: %d not %d", test.name, time.Weekday, 4) + } +} + +func TestFormatAndParse(t *testing.T) { + const fmt = "Mon MST " + RFC3339 // all fields + f := func(sec int64) bool { + t1 := SecondsToLocalTime(sec) + if t1.Year < 1000 || t1.Year > 9999 { + // not required to work + return true + } + t2, err := Parse(fmt, t1.Format(fmt)) + if err != nil { + t.Errorf("error: %s", err) + return false + } + if !same(t1, t2) { + t.Errorf("different: %q %q", t1, t2) + return false + } + return true + } + f32 := func(sec int32) bool { return f(int64(sec)) } + cfg := &quick.Config{MaxCount: 10000} + + // Try a reasonable date first, then the huge ones. + if err := quick.Check(f32, cfg); err != nil { + t.Fatal(err) + } + if err := quick.Check(f, cfg); err != nil { + t.Fatal(err) + } +} + +type ParseErrorTest struct { + format string + value string + expect string // must appear within the error +} + +var parseErrorTests = []ParseErrorTest{ + {ANSIC, "Feb 4 21:00:60 2010", "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", test.format, test.value) + } else if strings.Index(err.String(), test.expect) < 0 { + t.Errorf("expected error with %q for %q %q; got %s", test.expect, test.format, test.value, err) + } + } +} + +// Check that a time without a Zone still produces a (numeric) time zone +// when formatted with MST as a requested zone. +func TestMissingZone(t *testing.T) { + time, err := Parse(RubyDate, "Tue Feb 02 16:10:03 -0500 2006") + if err != nil { + t.Fatal("error parsing date:", err) + } + expect := "Tue Feb 2 16:10:03 -0500 2006" // -0500 not EST + str := time.Format(UnixDate) // uses MST as its time zone + if str != expect { + t.Errorf("expected %q got %q", expect, str) + } +} + +func TestMinutesInTimeZone(t *testing.T) { + time, err := Parse(RubyDate, "Mon Jan 02 15:04:05 +0123 2006") + if err != nil { + t.Fatal("error parsing date:", err) + } + expected := (1*60 + 23) * 60 + if time.ZoneOffset != expected { + t.Errorf("ZoneOffset incorrect, expected %d got %d", expected, time.ZoneOffset) + } +} + +func BenchmarkSeconds(b *testing.B) { + for i := 0; i < b.N; i++ { + Seconds() + } +} + +func BenchmarkNanoseconds(b *testing.B) { + for i := 0; i < b.N; i++ { + Nanoseconds() + } +} + +func BenchmarkFormat(b *testing.B) { + time := SecondsToLocalTime(1265346057) + for i := 0; i < b.N; i++ { + time.Format("Mon Jan 2 15:04:05 2006") + } +} + +func BenchmarkParse(b *testing.B) { + for i := 0; i < b.N; i++ { + Parse(ANSIC, "Mon Jan 2 15:04:05 2006") + } +} diff --git a/libgo/go/time/zoneinfo_unix.go b/libgo/go/time/zoneinfo_unix.go new file mode 100644 index 000000000..6685da747 --- /dev/null +++ b/libgo/go/time/zoneinfo_unix.go @@ -0,0 +1,267 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// Parse "zoneinfo" time zone file. +// This is a fairly standard file format used on OS X, Linux, BSD, Sun, and others. +// See tzfile(5), http://en.wikipedia.org/wiki/Zoneinfo, +// and ftp://munnari.oz.au/pub/oldtz/ + +package time + +import ( + "io/ioutil" + "os" + "sync" +) + +const ( + headerSize = 4 + 16 + 4*7 + zoneDir = "/usr/share/zoneinfo/" + zoneDir2 = "/usr/share/lib/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 +var onceSetupZone sync.Once + +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: + var ok bool + zones, ok = readinfofile(zoneDir + tz) + if !ok { + zones, _ = readinfofile(zoneDir2 + 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) { + 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/libgo/go/time/zoneinfo_windows.go b/libgo/go/time/zoneinfo_windows.go new file mode 100644 index 000000000..c357eec62 --- /dev/null +++ b/libgo/go/time/zoneinfo_windows.go @@ -0,0 +1,192 @@ +// Copyright 2009 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package time + +import ( + "syscall" + "sync" + "os" +) + +// BUG(brainman): The Windows implementation assumes that +// this year's rules for daylight savings time apply to all previous +// and future years as well. + +// TODO(brainman): use GetDynamicTimeZoneInformation, whenever posible (Vista and up), +// to improve on situation described in the bug above. + +type zone struct { + name string + offset int + year int64 + month, day, dayofweek int + hour, minute, second int + abssec int64 + prev *zone +} + +// Populate zone struct with Windows supplied information. Returns true, if data is valid. +func (z *zone) populate(bias, biasdelta int32, d *syscall.Systemtime, name []uint16) (dateisgood bool) { + z.name = syscall.UTF16ToString(name) + z.offset = int(bias) + z.year = int64(d.Year) + z.month = int(d.Month) + z.day = int(d.Day) + z.dayofweek = int(d.DayOfWeek) + z.hour = int(d.Hour) + z.minute = int(d.Minute) + z.second = int(d.Second) + dateisgood = d.Month != 0 + if dateisgood { + z.offset += int(biasdelta) + } + z.offset = -z.offset * 60 + return +} + +// Pre-calculte 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, ""}).Seconds() + // Time given is in "local" time. Adjust it for "utc". + z.abssec -= int64(z.prev.offset) + } +} + +// Convert zone cutoff time to sec in number of seconds since the Unix epoch, given particualar year. +func (z *zone) cutoffSeconds(year int64) int64 { + // Windows specifies daylight savings information in "day in month" format: + // z.month is month number (1-12) + // z.dayofweek is appropriate weekday (Sunday=0 to Saturday=6) + // z.day is week within the month (1 to 5, where 5 is last week of the month) + // z.hour, z.minute and z.second are absolute time + t := &Time{year, int(z.month), 1, int(z.hour), int(z.minute), int(z.second), 0, 0, ""} + t = SecondsToUTC(t.Seconds()) + i := int(z.dayofweek) - t.Weekday + if i < 0 { + i += 7 + } + t.Day += i + if week := int(z.day) - 1; week < 4 { + t.Day += week * 7 + } else { + // "Last" instance of the day. + t.Day += 4 * 7 + if t.Day > months(year)[t.Month] { + t.Day -= 7 + } + } + // Result is in "local" time. Adjust it for "utc". + return t.Seconds() - int64(z.prev.offset) +} + +// Is t before the cutoff for switching to z? +func (z *zone) isBeforeCutoff(t *Time) bool { + var coff int64 + if z.year == 0 { + // "day in month" format used + coff = z.cutoffSeconds(t.Year) + } else { + // "absolute" format used + coff = z.abssec + } + return t.Seconds() < coff +} + +type zoneinfo struct { + disabled bool // daylight saving time is not used localy + offsetIfDisabled int + januaryIsStd bool // is january 1 standard time? + std, dst zone +} + +// Pick zone (std or dst) t time belongs to. +func (zi *zoneinfo) pickZone(t *Time) *zone { + z := &zi.std + if tz.januaryIsStd { + if !zi.dst.isBeforeCutoff(t) && zi.std.isBeforeCutoff(t) { + // after switch to daylight time and before the switch back to standard + z = &zi.dst + } + } else { + if zi.std.isBeforeCutoff(t) || !zi.dst.isBeforeCutoff(t) { + // before switch to standard time or after the switch back to daylight + z = &zi.dst + } + } + return z +} + +var tz zoneinfo +var initError os.Error +var onceSetupZone sync.Once + +func setupZone() { + var i syscall.Timezoneinformation + if _, e := syscall.GetTimeZoneInformation(&i); e != 0 { + initError = os.NewSyscallError("GetTimeZoneInformation", e) + return + } + if !tz.std.populate(i.Bias, i.StandardBias, &i.StandardDate, i.StandardName[0:]) { + tz.disabled = true + tz.offsetIfDisabled = tz.std.offset + return + } + tz.std.prev = &tz.dst + tz.dst.populate(i.Bias, i.DaylightBias, &i.DaylightDate, i.DaylightName[0:]) + tz.dst.prev = &tz.std + tz.std.preCalculateAbsSec() + tz.dst.preCalculateAbsSec() + // Is january 1 standard time this year? + t := UTC() + tz.januaryIsStd = tz.dst.cutoffSeconds(t.Year) < tz.std.cutoffSeconds(t.Year) +} + +// Look up the correct time zone (daylight savings or not) for the given unix time, in the current location. +func lookupTimezone(sec int64) (zone string, offset int) { + onceSetupZone.Do(setupZone) + if initError != nil { + return "", 0 + } + if tz.disabled { + return "", tz.offsetIfDisabled + } + t := SecondsToUTC(sec) + z := &tz.std + if tz.std.year == 0 { + // "day in month" format used + z = tz.pickZone(t) + } else { + // "absolute" format used + if tz.std.year == t.Year { + // we have rule for the year in question + z = tz.pickZone(t) + } else { + // we do not have any information for that year, + // will assume standard offset all year around + } + } + return z.name, z.offset +} + +// lookupByName returns the time offset for the +// time zone with the given abbreviation. It only considers +// time zones that apply to the current system. +func lookupByName(name string) (off int, found bool) { + onceSetupZone.Do(setupZone) + if initError != nil { + return 0, false + } + if tz.disabled { + return tz.offsetIfDisabled, false + } + switch name { + case tz.std.name: + return tz.std.offset, true + case tz.dst.name: + return tz.dst.offset, true + } + return 0, false +} |