From f038dae646bac2b31be98ab592c0e5206d2d96f5 Mon Sep 17 00:00:00 2001 From: Ian Lance Taylor Date: Wed, 6 Nov 2013 19:49:01 +0000 Subject: libgo: Update to October 24 version of master library. From-SVN: r204466 --- libgo/go/time/export_test.go | 2 + libgo/go/time/format.go | 256 ++++++++++++++++++++++++-------- libgo/go/time/genzabbrs.go | 145 ++++++++++++++++++ libgo/go/time/internal_test.go | 67 +++++++++ libgo/go/time/sleep.go | 3 +- libgo/go/time/sleep_test.go | 124 +++++++++++++--- libgo/go/time/sys_unix.go | 2 +- libgo/go/time/time.go | 124 +++++++++++----- libgo/go/time/time_test.go | 138 ++++++++++++++++- libgo/go/time/zoneinfo.go | 13 -- libgo/go/time/zoneinfo_abbrs_windows.go | 115 ++++++++++++++ libgo/go/time/zoneinfo_read.go | 4 - libgo/go/time/zoneinfo_unix.go | 2 +- libgo/go/time/zoneinfo_windows.go | 134 +++++++++++++++-- 14 files changed, 970 insertions(+), 159 deletions(-) create mode 100644 libgo/go/time/genzabbrs.go create mode 100644 libgo/go/time/zoneinfo_abbrs_windows.go (limited to 'libgo/go/time') diff --git a/libgo/go/time/export_test.go b/libgo/go/time/export_test.go index 130ca8f7..dbd553a 100644 --- a/libgo/go/time/export_test.go +++ b/libgo/go/time/export_test.go @@ -17,3 +17,5 @@ func ForceUSPacificForTesting() { ResetLocalOnceForTest() localOnce.Do(initTestingZone) } + +var ParseTimeZone = parseTimeZone diff --git a/libgo/go/time/format.go b/libgo/go/time/format.go index 7fe0402..6f92c12 100644 --- a/libgo/go/time/format.go +++ b/libgo/go/time/format.go @@ -59,35 +59,39 @@ const ( ) const ( - _ = iota - stdLongMonth = iota + stdNeedDate // "January" - stdMonth // "Jan" - stdNumMonth // "1" - stdZeroMonth // "01" - stdLongWeekDay // "Monday" - stdWeekDay // "Mon" - stdDay // "2" - stdUnderDay // "_2" - stdZeroDay // "02" - stdHour = iota + stdNeedClock // "15" - stdHour12 // "3" - stdZeroHour12 // "03" - stdMinute // "4" - stdZeroMinute // "04" - stdSecond // "5" - stdZeroSecond // "05" - stdLongYear = iota + stdNeedDate // "2006" - stdYear // "06" - stdPM = iota + stdNeedClock // "PM" - stdpm // "pm" - stdTZ = iota // "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 - stdFracSecond0 // ".0", ".00", ... , trailing zeros included - stdFracSecond9 // ".9", ".99", ..., trailing zeros omitted + _ = iota + stdLongMonth = iota + stdNeedDate // "January" + stdMonth // "Jan" + stdNumMonth // "1" + stdZeroMonth // "01" + stdLongWeekDay // "Monday" + stdWeekDay // "Mon" + stdDay // "2" + stdUnderDay // "_2" + stdZeroDay // "02" + stdHour = iota + stdNeedClock // "15" + stdHour12 // "3" + stdZeroHour12 // "03" + stdMinute // "4" + stdZeroMinute // "04" + stdSecond // "5" + stdZeroSecond // "05" + stdLongYear = iota + stdNeedDate // "2006" + stdYear // "06" + stdPM = iota + stdNeedClock // "PM" + stdpm // "pm" + stdTZ = iota // "MST" + stdISO8601TZ // "Z0700" // prints Z for UTC + stdISO8601SecondsTZ // "Z070000" + stdISO8601ColonTZ // "Z07:00" // prints Z for UTC + stdISO8601ColonSecondsTZ // "Z07:00:00" + stdNumTZ // "-0700" // always numeric + stdNumSecondsTz // "-070000" + stdNumShortTZ // "-07" // always numeric + stdNumColonTZ // "-07:00" // always numeric + stdNumColonSecondsTZ // "-07:00:00" + stdFracSecond0 // ".0", ".00", ... , trailing zeros included + stdFracSecond9 // ".9", ".99", ..., trailing zeros omitted stdNeedDate = 1 << 8 // need month, day, year stdNeedClock = 2 << 8 // need hour, minute, second @@ -98,6 +102,16 @@ const ( // std0x records the std values for "01", "02", ..., "06". var std0x = [...]int{stdZeroMonth, stdZeroDay, stdZeroHour12, stdZeroMinute, stdZeroSecond, stdYear} +// startsWithLowerCase reports whether the the string has a lower-case letter at the beginning. +// Its purpose is to prevent matching strings like "Month" when looking for "Mon". +func startsWithLowerCase(str string) bool { + if len(str) == 0 { + return false + } + c := str[0] + return 'a' <= c && c <= 'z' +} + // 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 string, std int, suffix string) { @@ -108,7 +122,9 @@ func nextStdChunk(layout string) (prefix string, std int, suffix string) { if len(layout) >= i+7 && layout[i:i+7] == "January" { return layout[0:i], stdLongMonth, layout[i+7:] } - return layout[0:i], stdMonth, layout[i+3:] + if !startsWithLowerCase(layout[i+3:]) { + return layout[0:i], stdMonth, layout[i+3:] + } } case 'M': // Monday, Mon, MST @@ -117,7 +133,9 @@ func nextStdChunk(layout string) (prefix string, std int, suffix string) { if len(layout) >= i+6 && layout[i:i+6] == "Monday" { return layout[0:i], stdLongWeekDay, layout[i+6:] } - return layout[0:i], stdWeekDay, layout[i+3:] + if !startsWithLowerCase(layout[i+3:]) { + return layout[0:i], stdWeekDay, layout[i+3:] + } } if layout[i:i+3] == "MST" { return layout[0:i], stdTZ, layout[i+3:] @@ -165,7 +183,13 @@ func nextStdChunk(layout string) (prefix string, std int, suffix string) { return layout[0:i], stdpm, layout[i+2:] } - case '-': // -0700, -07:00, -07 + case '-': // -070000, -07:00:00, -0700, -07:00, -07 + if len(layout) >= i+7 && layout[i:i+7] == "-070000" { + return layout[0:i], stdNumSecondsTz, layout[i+7:] + } + if len(layout) >= i+9 && layout[i:i+9] == "-07:00:00" { + return layout[0:i], stdNumColonSecondsTZ, layout[i+9:] + } if len(layout) >= i+5 && layout[i:i+5] == "-0700" { return layout[0:i], stdNumTZ, layout[i+5:] } @@ -175,13 +199,21 @@ func nextStdChunk(layout string) (prefix string, std int, suffix string) { if len(layout) >= i+3 && layout[i:i+3] == "-07" { return layout[0:i], stdNumShortTZ, layout[i+3:] } - case 'Z': // Z0700, Z07:00 + + case 'Z': // Z070000, Z07:00:00, Z0700, Z07:00, + if len(layout) >= i+7 && layout[i:i+7] == "Z070000" { + return layout[0:i], stdISO8601SecondsTZ, layout[i+7:] + } + if len(layout) >= i+9 && layout[i:i+9] == "Z07:00:00" { + return layout[0:i], stdISO8601ColonSecondsTZ, layout[i+9:] + } if len(layout) >= i+5 && layout[i:i+5] == "Z0700" { return layout[0:i], stdISO8601TZ, layout[i+5:] } if len(layout) >= i+6 && layout[i:i+6] == "Z07:00" { return layout[0:i], stdISO8601ColonTZ, layout[i+6:] } + case '.': // .000 or .999 - repeated digits for fractional seconds. if i+1 < len(layout) && (layout[i+1] == '0' || layout[i+1] == '9') { ch := layout[i+1] @@ -321,8 +353,8 @@ var atoiError = errors.New("time: invalid number") // Duplicates functionality in strconv, but avoids dependency. func atoi(s string) (x int, err error) { neg := false - if s != "" && s[0] == '-' { - neg = true + if s != "" && (s[0] == '-' || s[0] == '+') { + neg = s[0] == '-' s = s[1:] } q, rem, err := leadingInt(s) @@ -507,17 +539,19 @@ func (t Time) Format(layout string) string { } else { b = append(b, "am"...) } - case stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumColonTZ: + case stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumColonTZ, stdNumSecondsTz, stdNumColonSecondsTZ: // Ugly special case. We cheat and take the "Z" variants // to mean "the time zone as formatted for ISO 8601". - if offset == 0 && (std == stdISO8601TZ || std == stdISO8601ColonTZ) { + if offset == 0 && (std == stdISO8601TZ || std == stdISO8601ColonTZ || std == stdISO8601SecondsTZ || std == stdISO8601ColonSecondsTZ) { b = append(b, 'Z') break } zone := offset / 60 // convert to minutes + absoffset := offset if zone < 0 { b = append(b, '-') zone = -zone + absoffset = -absoffset } else { b = append(b, '+') } @@ -526,6 +560,15 @@ func (t Time) Format(layout string) string { b = append(b, ':') } b = appendUint(b, uint(zone%60), '0') + + // append seconds if appropriate + if std == stdISO8601SecondsTZ || std == stdNumSecondsTz || std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ { + if std == stdNumColonSecondsTZ || std == stdISO8601ColonSecondsTZ { + b = append(b, ':') + } + b = appendUint(b, uint(absoffset%60), '0') + } + case stdTZ: if name != "" { b = append(b, name...) @@ -780,7 +823,7 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) // Special case: do we have a fractional second but no // fractional second in the format? if len(value) >= 2 && value[0] == '.' && isDigit(value, 1) { - _, std, _ := nextStdChunk(layout) + _, std, _ = nextStdChunk(layout) std &= stdMask if std == stdFracSecond0 || std == stdFracSecond9 { // Fractional second in the layout; proceed normally @@ -821,13 +864,13 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) default: err = errBad } - case stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ: + case stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ, stdNumSecondsTz, stdNumColonSecondsTZ: if (std == stdISO8601TZ || std == stdISO8601ColonTZ) && len(value) >= 1 && value[0] == 'Z' { value = value[1:] z = UTC break } - var sign, hour, min string + var sign, hour, min, seconds string if std == stdISO8601ColonTZ || std == stdNumColonTZ { if len(value) < 6 { err = errBad @@ -837,26 +880,45 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) err = errBad break } - sign, hour, min, value = value[0:1], value[1:3], value[4:6], value[6:] + sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], "00", value[6:] } else if std == stdNumShortTZ { if len(value) < 3 { err = errBad break } - sign, hour, min, value = value[0:1], value[1:3], "00", value[3:] + sign, hour, min, seconds, value = value[0:1], value[1:3], "00", "00", value[3:] + } else if std == stdISO8601ColonSecondsTZ || std == stdNumColonSecondsTZ { + if len(value) < 9 { + err = errBad + break + } + if value[3] != ':' || value[6] != ':' { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], value[7:9], value[9:] + } else if std == stdISO8601SecondsTZ || std == stdNumSecondsTz { + if len(value) < 7 { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], value[5:7], value[7:] } else { if len(value) < 5 { err = errBad break } - sign, hour, min, value = value[0:1], value[1:3], value[3:5], value[5:] + sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], "00", value[5:] } - var hr, mm int + var hr, mm, ss int hr, err = atoi(hour) if err == nil { mm, err = atoi(min) } - zoneOffset = (hr*60 + mm) * 60 // offset is in seconds + if err == nil { + ss, err = atoi(seconds) + } + zoneOffset = (hr*60+mm)*60 + ss // offset is in seconds switch sign[0] { case '+': case '-': @@ -871,25 +933,12 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) value = 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 { + n, ok := parseTimeZone(value) + if !ok { 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. - zoneName = p + zoneName, value = value[:n], value[n:] case stdFracSecond0: // stdFracSecond0 requires the exact number of digits as specified in @@ -962,7 +1011,11 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) } // Otherwise, create fake zone with unknown offset. - t.loc = FixedZone(zoneName, 0) + if len(zoneName) > 3 && zoneName[:3] == "GMT" { + offset, _ = atoi(zoneName[3:]) // Guaranteed OK by parseGMT. + offset *= 3600 + } + t.loc = FixedZone(zoneName, offset) return t, nil } @@ -970,6 +1023,81 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) return Date(year, Month(month), day, hour, min, sec, nsec, defaultLocation), nil } +// parseTimeZone parses a time zone string and returns its length. Time zones +// are human-generated and unpredictable. We can't do precise error checking. +// On the other hand, for a correct parse there must be a time zone at the +// beginning of the string, so it's almost always true that there's one +// there. We look at the beginning of the string for a run of upper-case letters. +// If there are more than 5, it's an error. +// If there are 4 or 5 and the last is a T, it's a time zone. +// If there are 3, it's a time zone. +// Otherwise, other than special cases, it's not a time zone. +// GMT is special because it can have an hour offset. +func parseTimeZone(value string) (length int, ok bool) { + if len(value) < 3 { + return 0, false + } + // Special case 1: This is the only zone with a lower-case letter. + if len(value) >= 4 && value[:4] == "ChST" { + return 4, true + } + // Special case 2: GMT may have an hour offset; treat it specially. + if value[:3] == "GMT" { + length = parseGMT(value) + return length, true + } + // How many upper-case letters are there? Need at least three, at most five. + var nUpper int + for nUpper = 0; nUpper < 6; nUpper++ { + if nUpper >= len(value) { + break + } + if c := value[nUpper]; c < 'A' || 'Z' < c { + break + } + } + switch nUpper { + case 0, 1, 2, 6: + return 0, false + case 5: // Must end in T to match. + if value[4] == 'T' { + return 5, true + } + case 4: // Must end in T to match. + if value[3] == 'T' { + return 4, true + } + case 3: + return 3, true + } + return 0, false +} + +// parseGMT parses a GMT time zone. The input string is known to start "GMT". +// The function checks whether that is followed by a sign and a number in the +// range -14 through 12 excluding zero. +func parseGMT(value string) int { + value = value[3:] + if len(value) == 0 { + return 3 + } + sign := value[0] + if sign != '-' && sign != '+' { + return 3 + } + x, rem, err := leadingInt(value[1:]) + if err != nil { + return 3 + } + if sign == '-' { + x = -x + } + if x == 0 || x < -14 || 12 < x { + return 3 + } + return 3 + len(value) - len(rem) +} + func parseNanoseconds(value string, nbytes int) (ns int, rangeErrString string, err error) { if value[0] != '.' { err = errBad @@ -1076,11 +1204,11 @@ func ParseDuration(s string) (Duration, error) { if err != nil { return 0, errors.New("time: invalid duration " + orig) } - scale := 1 + scale := 1.0 for n := pl - len(s); n > 0; n-- { scale *= 10 } - g += float64(x) / float64(scale) + g += float64(x) / scale post = pl != len(s) } if !pre && !post { diff --git a/libgo/go/time/genzabbrs.go b/libgo/go/time/genzabbrs.go new file mode 100644 index 0000000..7c637cb --- /dev/null +++ b/libgo/go/time/genzabbrs.go @@ -0,0 +1,145 @@ +// Copyright 2013 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build ignore + +// +// usage: +// +// go run genzabbrs.go | gofmt > $GOROOT/src/pkg/time/zoneinfo_abbrs_windows.go +// + +package main + +import ( + "encoding/xml" + "io/ioutil" + "log" + "net/http" + "os" + "sort" + "text/template" + "time" +) + +// getAbbrs finds timezone abbreviations (standard and daylight saving time) +// for location l. +func getAbbrs(l *time.Location) (st, dt string) { + t := time.Date(time.Now().Year(), 0, 0, 0, 0, 0, 0, l) + abbr1, off1 := t.Zone() + for i := 0; i < 12; i++ { + t = t.AddDate(0, 1, 0) + abbr2, off2 := t.Zone() + if abbr1 != abbr2 { + if off2-off1 < 0 { // southern hemisphere + abbr1, abbr2 = abbr2, abbr1 + } + return abbr1, abbr2 + } + } + return abbr1, abbr1 +} + +type zone struct { + WinName string + UnixName string + StTime string + DSTime string +} + +type zones []*zone + +func (zs zones) Len() int { return len(zs) } +func (zs zones) Swap(i, j int) { zs[i], zs[j] = zs[j], zs[i] } +func (zs zones) Less(i, j int) bool { return zs[i].UnixName < zs[j].UnixName } + +const wzURL = "http://unicode.org/cldr/data/common/supplemental/windowsZones.xml" + +type MapZone struct { + Other string `xml:"other,attr"` + Territory string `xml:"territory,attr"` + Type string `xml:"type,attr"` +} + +type SupplementalData struct { + Zones []MapZone `xml:"windowsZones>mapTimezones>mapZone"` +} + +func readWindowsZones() (zones, error) { + r, err := http.Get(wzURL) + if err != nil { + return nil, err + } + defer r.Body.Close() + + data, err := ioutil.ReadAll(r.Body) + if err != nil { + return nil, err + } + + var sd SupplementalData + err = xml.Unmarshal(data, &sd) + if err != nil { + return nil, err + } + zs := make(zones, 0) + for _, z := range sd.Zones { + if z.Territory != "001" { + // to avoid dups. I don't know why. + continue + } + l, err := time.LoadLocation(z.Type) + if err != nil { + return nil, err + } + st, dt := getAbbrs(l) + zs = append(zs, &zone{ + WinName: z.Other, + UnixName: z.Type, + StTime: st, + DSTime: dt, + }) + } + return zs, nil +} + +func main() { + zs, err := readWindowsZones() + if err != nil { + log.Fatal(err) + } + sort.Sort(zs) + var v = struct { + URL string + Zs zones + }{ + wzURL, + zs, + } + err = template.Must(template.New("prog").Parse(prog)).Execute(os.Stdout, v) + if err != nil { + log.Fatal(err) + } +} + +const prog = ` +// Copyright 2013 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. + +// generated by genzabbrs.go from +// {{.URL}} + +package time + +type abbr struct { + std string + dst string +} + +var abbrs = map[string]abbr{ +{{range .Zs}} "{{.WinName}}": {"{{.StTime}}", "{{.DSTime}}"}, // {{.UnixName}} +{{end}}} + +` diff --git a/libgo/go/time/internal_test.go b/libgo/go/time/internal_test.go index 918a9f3..87fdd32 100644 --- a/libgo/go/time/internal_test.go +++ b/libgo/go/time/internal_test.go @@ -4,6 +4,11 @@ package time +import ( + "errors" + "runtime" +) + func init() { // force US/Pacific for time zone tests ForceUSPacificForTesting() @@ -11,3 +16,65 @@ func init() { var Interrupt = interrupt var DaysIn = daysIn + +func empty(now int64, arg interface{}) {} + +// Test that a runtimeTimer with a duration so large it overflows +// does not cause other timers to hang. +// +// This test has to be in internal_test.go since it fiddles with +// unexported data structures. +func CheckRuntimeTimerOverflow() error { + // We manually create a runtimeTimer to bypass the overflow + // detection logic in NewTimer: we're testing the underlying + // runtime.addtimer function. + r := &runtimeTimer{ + when: nano() + (1<<63 - 1), + f: empty, + arg: nil, + } + startTimer(r) + + timeout := 100 * Millisecond + if runtime.GOOS == "windows" { + // Allow more time for gobuilder to succeed. + timeout = Second + } + + // Start a goroutine that should send on t.C before the timeout. + t := NewTimer(1) + + defer func() { + // Subsequent tests won't work correctly if we don't stop the + // overflow timer and kick the timer proc back into service. + // + // The timer proc is now sleeping and can only be awoken by + // adding a timer to the *beginning* of the heap. We can't + // wake it up by calling NewTimer since other tests may have + // left timers running that should have expired before ours. + // Instead we zero the overflow timer duration and start it + // once more. + stopTimer(r) + t.Stop() + r.when = 0 + startTimer(r) + }() + + // Try to receive from t.C before the timeout. It will succeed + // iff the previous sleep was able to finish. We're forced to + // spin and yield after trying to receive since we can't start + // any more timers (they might hang due to the same bug we're + // now testing). + stop := Now().Add(timeout) + for { + select { + case <-t.C: + return nil // It worked! + default: + if Now().After(stop) { + return errors.New("runtime timer stuck: overflow in addtimer") + } + runtime.Gosched() + } + } +} diff --git a/libgo/go/time/sleep.go b/libgo/go/time/sleep.go index 591fa27..4f55beb 100644 --- a/libgo/go/time/sleep.go +++ b/libgo/go/time/sleep.go @@ -4,7 +4,8 @@ package time -// Sleep pauses the current goroutine for the duration d. +// Sleep pauses the current goroutine for at least the duration d. +// A negative or zero duration causes Sleep to return immediately. func Sleep(d Duration) func nano() int64 { diff --git a/libgo/go/time/sleep_test.go b/libgo/go/time/sleep_test.go index 762549d..cb09a84 100644 --- a/libgo/go/time/sleep_test.go +++ b/libgo/go/time/sleep_test.go @@ -9,6 +9,7 @@ import ( "fmt" "runtime" "sort" + "sync" "sync/atomic" "testing" . "time" @@ -68,33 +69,94 @@ func TestAfterStress(t *testing.T) { atomic.StoreUint32(&stop, 1) } +func benchmark(b *testing.B, bench func(n int)) { + garbage := make([]*Timer, 1<<17) + for i := 0; i < len(garbage); i++ { + garbage[i] = AfterFunc(Hour, nil) + } + + const batch = 1000 + P := runtime.GOMAXPROCS(-1) + N := int32(b.N / batch) + + b.ResetTimer() + + var wg sync.WaitGroup + wg.Add(P) + + for p := 0; p < P; p++ { + go func() { + for atomic.AddInt32(&N, -1) >= 0 { + bench(batch) + } + wg.Done() + }() + } + + wg.Wait() + + b.StopTimer() + for i := 0; i < len(garbage); i++ { + garbage[i].Stop() + } +} + 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 + benchmark(b, func(n int) { + c := make(chan bool) + var f func() + f = func() { + n-- + if n >= 0 { + AfterFunc(0, f) + } else { + c <- true + } } - } - AfterFunc(0, f) - <-c + AfterFunc(0, f) + <-c + }) } func BenchmarkAfter(b *testing.B) { - for i := 0; i < b.N; i++ { - <-After(1) - } + benchmark(b, func(n int) { + for i := 0; i < n; i++ { + <-After(1) + } + }) } func BenchmarkStop(b *testing.B) { - for i := 0; i < b.N; i++ { - NewTimer(1 * Second).Stop() - } + benchmark(b, func(n int) { + for i := 0; i < n; i++ { + NewTimer(1 * Second).Stop() + } + }) +} + +func BenchmarkSimultaneousAfterFunc(b *testing.B) { + benchmark(b, func(n int) { + var wg sync.WaitGroup + wg.Add(n) + for i := 0; i < n; i++ { + AfterFunc(0, wg.Done) + } + wg.Wait() + }) +} + +func BenchmarkStartStop(b *testing.B) { + benchmark(b, func(n int) { + timers := make([]*Timer, n) + for i := 0; i < n; i++ { + timers[i] = AfterFunc(Hour, nil) + } + + for i := 0; i < n; i++ { + timers[i].Stop() + } + }) } func TestAfter(t *testing.T) { @@ -315,3 +377,29 @@ func TestOverflowSleep(t *testing.T) { t.Fatalf("negative timeout didn't fire") } } + +// Test that a panic while deleting a timer does not leave +// the timers mutex held, deadlocking a ticker.Stop in a defer. +func TestIssue5745(t *testing.T) { + ticker := NewTicker(Hour) + defer func() { + // would deadlock here before the fix due to + // lock taken before the segfault. + ticker.Stop() + + if r := recover(); r == nil { + t.Error("Expected panic, but none happened.") + } + }() + + // cause a panic due to a segfault + var timer *Timer + timer.Stop() + t.Error("Should be unreachable.") +} + +func TestOverflowRuntimeTimer(t *testing.T) { + if err := CheckRuntimeTimerOverflow(); err != nil { + t.Fatalf(err.Error()) + } +} diff --git a/libgo/go/time/sys_unix.go b/libgo/go/time/sys_unix.go index 7f69b49..60a3ce0 100644 --- a/libgo/go/time/sys_unix.go +++ b/libgo/go/time/sys_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin freebsd linux netbsd openbsd +// +build darwin dragonfly freebsd linux netbsd openbsd package time diff --git a/libgo/go/time/time.go b/libgo/go/time/time.go index d291672..c504df7 100644 --- a/libgo/go/time/time.go +++ b/libgo/go/time/time.go @@ -39,7 +39,14 @@ type Time struct { // nsec specifies a non-negative nanosecond // offset within the second named by Seconds. // It must be in the range [0, 999999999]. - nsec int32 + // + // It is declared as uintptr instead of int32 or uint32 + // to avoid garbage collector aliasing in the case where + // on a 64-bit system the int32 or uint32 field is written + // over the low half of a pointer, creating another pointer. + // TODO(rsc): When the garbage collector is completely + // precise, change back to int32. + nsec uintptr // loc specifies the Location that should be used to // determine the minute, hour, month, day, and year @@ -424,6 +431,11 @@ func (t Time) YearDay() int { // largest representable duration to approximately 290 years. type Duration int64 +const ( + minDuration Duration = -1 << 63 + maxDuration Duration = 1<<63 - 1 +) + // Common durations. There is no definition for units of Day or larger // to avoid confusion across daylight savings time zone transitions. // @@ -600,21 +612,33 @@ func (d Duration) Hours() float64 { // Add returns the time t+d. func (t Time) Add(d Duration) Time { t.sec += int64(d / 1e9) - t.nsec += int32(d % 1e9) - if t.nsec >= 1e9 { + nsec := int32(t.nsec) + int32(d%1e9) + if nsec >= 1e9 { t.sec++ - t.nsec -= 1e9 - } else if t.nsec < 0 { + nsec -= 1e9 + } else if nsec < 0 { t.sec-- - t.nsec += 1e9 + nsec += 1e9 } + t.nsec = uintptr(nsec) return t } -// Sub returns the duration t-u. +// Sub returns the duration t-u. If the result exceeds the maximum (or minimum) +// value that can be stored in a Duration, the maximum (or minimum) duration +// will be returned. // To compute t-d for a duration d, use t.Add(-d). func (t Time) Sub(u Time) Duration { - return Duration(t.sec-u.sec)*Second + Duration(t.nsec-u.nsec) + d := Duration(t.sec-u.sec)*Second + Duration(int32(t.nsec)-int32(u.nsec)) + // Check for overflow or underflow. + switch { + case u.Add(d).Equal(t): + return d // d is correct + case t.Before(u): + return minDuration // t - u is negative out of range + default: + return maxDuration // t - u is positive out of range + } } // Since returns the time elapsed since t. @@ -645,7 +669,6 @@ const ( daysPer400Years = 365*400 + 97 daysPer100Years = 365*100 + 24 daysPer4Years = 365*4 + 1 - days1970To2001 = 31*365 + 8 ) // date computes the year, day of year, and when full=true, @@ -760,7 +783,7 @@ func now() (sec int64, nsec int32) // Now returns the current local time. func Now() Time { sec, nsec := now() - return Time{sec + unixToInternal, nsec, Local} + return Time{sec + unixToInternal, uintptr(nsec), Local} } // UTC returns t with the location set to UTC. @@ -816,10 +839,10 @@ func (t Time) UnixNano() int64 { return (t.sec+internalToUnix)*1e9 + int64(t.nsec) } -const timeGobVersion byte = 1 +const timeBinaryVersion byte = 1 -// GobEncode implements the gob.GobEncoder interface. -func (t Time) GobEncode() ([]byte, error) { +// MarshalBinary implements the encoding.BinaryMarshaler interface. +func (t Time) MarshalBinary() ([]byte, error) { var offsetMin int16 // minutes east of UTC. -1 is UTC. if t.Location() == &utcLoc { @@ -827,17 +850,17 @@ func (t Time) GobEncode() ([]byte, error) { } else { _, offset := t.Zone() if offset%60 != 0 { - return nil, errors.New("Time.GobEncode: zone offset has fractional minute") + return nil, errors.New("Time.MarshalBinary: zone offset has fractional minute") } offset /= 60 if offset < -32768 || offset == -1 || offset > 32767 { - return nil, errors.New("Time.GobEncode: unexpected zone offset") + return nil, errors.New("Time.MarshalBinary: unexpected zone offset") } offsetMin = int16(offset) } enc := []byte{ - timeGobVersion, // byte 0 : version + timeBinaryVersion, // byte 0 : version byte(t.sec >> 56), // bytes 1-8: seconds byte(t.sec >> 48), byte(t.sec >> 40), @@ -857,18 +880,19 @@ func (t Time) GobEncode() ([]byte, error) { return enc, nil } -// GobDecode implements the gob.GobDecoder interface. -func (t *Time) GobDecode(buf []byte) error { +// UnmarshalBinary implements the encoding.BinaryUnmarshaler interface. +func (t *Time) UnmarshalBinary(data []byte) error { + buf := data if len(buf) == 0 { - return errors.New("Time.GobDecode: no data") + return errors.New("Time.UnmarshalBinary: no data") } - if buf[0] != timeGobVersion { - return errors.New("Time.GobDecode: unsupported version") + if buf[0] != timeBinaryVersion { + return errors.New("Time.UnmarshalBinary: unsupported version") } if len(buf) != /*version*/ 1+ /*sec*/ 8+ /*nsec*/ 4+ /*zone offset*/ 2 { - return errors.New("Time.GobDecode: invalid length") + return errors.New("Time.UnmarshalBinary: invalid length") } buf = buf[1:] @@ -876,7 +900,7 @@ func (t *Time) GobDecode(buf []byte) error { int64(buf[3])<<32 | int64(buf[2])<<40 | int64(buf[1])<<48 | int64(buf[0])<<56 buf = buf[8:] - t.nsec = int32(buf[3]) | int32(buf[2])<<8 | int32(buf[1])<<16 | int32(buf[0])<<24 + t.nsec = uintptr(int32(buf[3]) | int32(buf[2])<<8 | int32(buf[1])<<16 | int32(buf[0])<<24) buf = buf[4:] offset := int(int16(buf[1])|int16(buf[0])<<8) * 60 @@ -892,8 +916,22 @@ func (t *Time) GobDecode(buf []byte) error { return nil } +// TODO(rsc): Remove GobEncoder, GobDecoder, MarshalJSON, UnmarshalJSON in Go 2. +// The same semantics will be provided by the generic MarshalBinary, MarshalText, +// UnmarshalBinary, UnmarshalText. + +// GobEncode implements the gob.GobEncoder interface. +func (t Time) GobEncode() ([]byte, error) { + return t.MarshalBinary() +} + +// GobDecode implements the gob.GobDecoder interface. +func (t *Time) GobDecode(data []byte) error { + return t.UnmarshalBinary(data) +} + // MarshalJSON implements the json.Marshaler interface. -// Time is formatted as RFC3339. +// The time is a quoted string in RFC 3339 format, with sub-second precision added if present. func (t Time) MarshalJSON() ([]byte, error) { if y := t.Year(); y < 0 || y >= 10000 { return nil, errors.New("Time.MarshalJSON: year outside of range [0,9999]") @@ -902,13 +940,30 @@ func (t Time) MarshalJSON() ([]byte, error) { } // UnmarshalJSON implements the json.Unmarshaler interface. -// Time is expected in RFC3339 format. +// The time is expected to be a quoted string in RFC 3339 format. func (t *Time) UnmarshalJSON(data []byte) (err error) { // Fractional seconds are handled implicitly by Parse. *t, err = Parse(`"`+RFC3339+`"`, string(data)) return } +// MarshalText implements the encoding.TextMarshaler interface. +// The time is formatted in RFC 3339 format, with sub-second precision added if present. +func (t Time) MarshalText() ([]byte, error) { + if y := t.Year(); y < 0 || y >= 10000 { + return nil, errors.New("Time.MarshalText: year outside of range [0,9999]") + } + return []byte(t.Format(RFC3339Nano)), nil +} + +// UnmarshalText implements the encoding.TextUnmarshaler interface. +// The time is expected to be in RFC 3339 format. +func (t *Time) UnmarshalText(data []byte) (err error) { + // Fractional seconds are handled implicitly by Parse. + *t, err = Parse(RFC3339, string(data)) + return +} + // Unix returns the local Time corresponding to the given Unix time, // sec seconds and nsec nanoseconds since January 1, 1970 UTC. // It is valid to pass nsec outside the range [0, 999999999]. @@ -922,7 +977,7 @@ func Unix(sec int64, nsec int64) Time { sec-- } } - return Time{sec + unixToInternal, int32(nsec), Local} + return Time{sec + unixToInternal, uintptr(nsec), Local} } func isLeap(year int) bool { @@ -1031,7 +1086,7 @@ func Date(year int, month Month, day, hour, min, sec, nsec int, loc *Location) T unix -= int64(offset) } - return Time{unix + unixToInternal, int32(nsec), loc} + return Time{unix + unixToInternal, uintptr(nsec), loc} } // Truncate returns the result of rounding t down to a multiple of d (since the zero time). @@ -1063,13 +1118,14 @@ func (t Time) Round(d Duration) Time { // but it's still here in case we change our minds. func div(t Time, d Duration) (qmod2 int, r Duration) { neg := false + nsec := int32(t.nsec) if t.sec < 0 { // Operate on absolute value. neg = true t.sec = -t.sec - t.nsec = -t.nsec - if t.nsec < 0 { - t.nsec += 1e9 + nsec = -nsec + if nsec < 0 { + nsec += 1e9 t.sec-- // t.sec >= 1 before the -- so safe } } @@ -1077,14 +1133,14 @@ func div(t Time, d Duration) (qmod2 int, r Duration) { switch { // Special case: 2d divides 1 second. case d < Second && Second%(d+d) == 0: - qmod2 = int(t.nsec/int32(d)) & 1 - r = Duration(t.nsec % int32(d)) + qmod2 = int(nsec/int32(d)) & 1 + r = Duration(nsec % int32(d)) // Special case: d is a multiple of 1 second. case d%Second == 0: d1 := int64(d / Second) qmod2 = int(t.sec/d1) & 1 - r = Duration(t.sec%d1)*Second + Duration(t.nsec) + r = Duration(t.sec%d1)*Second + Duration(nsec) // General case. // This could be faster if more cleverness were applied, @@ -1101,7 +1157,7 @@ func div(t Time, d Duration) (qmod2 int, r Duration) { if u0 < u0x { u1++ } - u0x, u0 = u0, u0+uint64(t.nsec) + u0x, u0 = u0, u0+uint64(nsec) if u0 < u0x { u1++ } diff --git a/libgo/go/time/time_test.go b/libgo/go/time/time_test.go index a0ee37a..22b751c 100644 --- a/libgo/go/time/time_test.go +++ b/libgo/go/time/time_test.go @@ -413,6 +413,8 @@ var formatTests = []FormatTest{ {"am/pm", "3pm", "9pm"}, {"AM/PM", "3PM", "9PM"}, {"two-digit year", "06 01 02", "09 02 04"}, + // Three-letter months and days must not be followed by lower-case letter. + {"Janet", "Hi Janet, the Month is January", "Hi Janet, the Month is February"}, // Time stamps, Fractional seconds. {"Stamp", Stamp, "Feb 4 21:00:57"}, {"StampMilli", StampMilli, "Feb 4 21:00:57.012"}, @@ -505,6 +507,11 @@ var parseTests = []ParseTest{ // Leading zeros in other places should not be taken as fractional seconds. {"zero1", "2006.01.02.15.04.05.0", "2010.02.04.21.00.57.0", false, false, 1, 1}, {"zero2", "2006.01.02.15.04.05.00", "2010.02.04.21.00.57.01", false, false, 1, 2}, + // Month and day names only match when not followed by a lower-case letter. + {"Janet", "Hi Janet, the Month is January: Jan _2 15:04:05 2006", "Hi Janet, the Month is February: Feb 4 21:00:57 2010", false, true, 1, 0}, + + // GMT with offset. + {"GMT-8", UnixDate, "Fri Feb 5 05:00:57 GMT-8 2010", true, true, 1, 0}, // Accept any number of fractional second digits (including none) for .999... // In Go 1, .999... was completely ignored in the format, meaning the first two @@ -659,6 +666,38 @@ func TestFormatAndParse(t *testing.T) { } } +type ParseTimeZoneTest struct { + value string + length int + ok bool +} + +var parseTimeZoneTests = []ParseTimeZoneTest{ + {"gmt hi there", 0, false}, + {"GMT hi there", 3, true}, + {"GMT+12 hi there", 6, true}, + {"GMT+00 hi there", 3, true}, // 0 or 00 is not a legal offset. + {"GMT-5 hi there", 5, true}, + {"GMT-51 hi there", 3, true}, + {"ChST hi there", 4, true}, + {"MSDx", 3, true}, + {"MSDY", 0, false}, // four letters must end in T. + {"ESAST hi", 5, true}, + {"ESASTT hi", 0, false}, // run of upper-case letters too long. + {"ESATY hi", 0, false}, // five letters must end in T. +} + +func TestParseTimeZone(t *testing.T) { + for _, test := range parseTimeZoneTests { + length, ok := ParseTimeZone(test.value) + if ok != test.ok { + t.Errorf("expected %t for %q got %t", test.ok, test.value, ok) + } else if length != test.length { + t.Errorf("expected %d for %q got %d", test.length, test.value, length) + } + } +} + type ParseErrorTest struct { format string value string @@ -781,6 +820,44 @@ func TestMinutesInTimeZone(t *testing.T) { } } +type SecondsTimeZoneOffsetTest struct { + format string + value string + expectedoffset int +} + +var secondsTimeZoneOffsetTests = []SecondsTimeZoneOffsetTest{ + {"2006-01-02T15:04:05-070000", "1871-01-01T05:33:02-003408", -(34*60 + 8)}, + {"2006-01-02T15:04:05-07:00:00", "1871-01-01T05:33:02-00:34:08", -(34*60 + 8)}, + {"2006-01-02T15:04:05-070000", "1871-01-01T05:33:02+003408", 34*60 + 8}, + {"2006-01-02T15:04:05-07:00:00", "1871-01-01T05:33:02+00:34:08", 34*60 + 8}, + {"2006-01-02T15:04:05Z070000", "1871-01-01T05:33:02-003408", -(34*60 + 8)}, + {"2006-01-02T15:04:05Z07:00:00", "1871-01-01T05:33:02+00:34:08", 34*60 + 8}, +} + +func TestParseSecondsInTimeZone(t *testing.T) { + // should accept timezone offsets with seconds like: Zone America/New_York -4:56:02 - LMT 1883 Nov 18 12:03:58 + for _, test := range secondsTimeZoneOffsetTests { + time, err := Parse(test.format, test.value) + if err != nil { + t.Fatal("error parsing date:", err) + } + _, offset := time.Zone() + if offset != test.expectedoffset { + t.Errorf("ZoneOffset = %d, want %d", offset, test.expectedoffset) + } + } +} + +func TestFormatSecondsInTimeZone(t *testing.T) { + d := Date(1871, 9, 17, 20, 4, 26, 0, FixedZone("LMT", -(34*60+8))) + timestr := d.Format("2006-01-02T15:04:05Z070000") + expected := "1871-09-17T20:04:26-003408" + if timestr != expected { + t.Errorf("Got %s, want %s", timestr, expected) + } +} + type ISOWeekTest struct { year int // year month, day int // month and day @@ -1106,9 +1183,9 @@ var invalidEncodingTests = []struct { bytes []byte want string }{ - {[]byte{}, "Time.GobDecode: no data"}, - {[]byte{0, 2, 3}, "Time.GobDecode: unsupported version"}, - {[]byte{1, 2, 3}, "Time.GobDecode: invalid length"}, + {[]byte{}, "Time.UnmarshalBinary: no data"}, + {[]byte{0, 2, 3}, "Time.UnmarshalBinary: unsupported version"}, + {[]byte{1, 2, 3}, "Time.UnmarshalBinary: invalid length"}, } func TestInvalidTimeGob(t *testing.T) { @@ -1118,6 +1195,10 @@ func TestInvalidTimeGob(t *testing.T) { if err == nil || err.Error() != tt.want { t.Errorf("time.GobDecode(%#v) error = %v, want %v", tt.bytes, err, tt.want) } + err = ignored.UnmarshalBinary(tt.bytes) + if err == nil || err.Error() != tt.want { + t.Errorf("time.UnmarshalBinary(%#v) error = %v, want %v", tt.bytes, err, tt.want) + } } } @@ -1125,10 +1206,10 @@ var notEncodableTimes = []struct { time Time want string }{ - {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", 1)), "Time.GobEncode: zone offset has fractional minute"}, - {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", -1*60)), "Time.GobEncode: unexpected zone offset"}, - {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", -32769*60)), "Time.GobEncode: unexpected zone offset"}, - {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", 32768*60)), "Time.GobEncode: unexpected zone offset"}, + {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", 1)), "Time.MarshalBinary: zone offset has fractional minute"}, + {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", -1*60)), "Time.MarshalBinary: unexpected zone offset"}, + {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", -32769*60)), "Time.MarshalBinary: unexpected zone offset"}, + {Date(0, 1, 2, 3, 4, 5, 6, FixedZone("", 32768*60)), "Time.MarshalBinary: unexpected zone offset"}, } func TestNotGobEncodableTime(t *testing.T) { @@ -1137,6 +1218,10 @@ func TestNotGobEncodableTime(t *testing.T) { if err == nil || err.Error() != tt.want { t.Errorf("%v GobEncode error = %v, want %v", tt.time, err, tt.want) } + _, err = tt.time.MarshalBinary() + if err == nil || err.Error() != tt.want { + t.Errorf("%v MarshalBinary error = %v, want %v", tt.time, err, tt.want) + } } } @@ -1233,6 +1318,8 @@ var parseDurationTests = []struct { {"39h9m14.425s", true, 39*Hour + 9*Minute + 14*Second + 425*Millisecond}, // large value {"52763797000ns", true, 52763797000 * Nanosecond}, + // more than 9 digits after decimal point, see http://golang.org/issue/6617 + {"0.3333333333333333333h", true, 20 * Minute}, // errors {"", false, 0}, @@ -1300,6 +1387,9 @@ var mallocTest = []struct { } func TestCountMallocs(t *testing.T) { + if testing.Short() { + t.Skip("skipping malloc count in short mode") + } if runtime.GOMAXPROCS(0) > 1 { t.Skip("skipping; GOMAXPROCS>1") } @@ -1327,6 +1417,40 @@ func TestLoadFixed(t *testing.T) { } } +const ( + minDuration Duration = -1 << 63 + maxDuration Duration = 1<<63 - 1 +) + +var subTests = []struct { + t Time + u Time + d Duration +}{ + {Time{}, Time{}, Duration(0)}, + {Date(2009, 11, 23, 0, 0, 0, 1, UTC), Date(2009, 11, 23, 0, 0, 0, 0, UTC), Duration(1)}, + {Date(2009, 11, 23, 0, 0, 0, 0, UTC), Date(2009, 11, 24, 0, 0, 0, 0, UTC), -24 * Hour}, + {Date(2009, 11, 24, 0, 0, 0, 0, UTC), Date(2009, 11, 23, 0, 0, 0, 0, UTC), 24 * Hour}, + {Date(-2009, 11, 24, 0, 0, 0, 0, UTC), Date(-2009, 11, 23, 0, 0, 0, 0, UTC), 24 * Hour}, + {Time{}, Date(2109, 11, 23, 0, 0, 0, 0, UTC), Duration(minDuration)}, + {Date(2109, 11, 23, 0, 0, 0, 0, UTC), Time{}, Duration(maxDuration)}, + {Time{}, Date(-2109, 11, 23, 0, 0, 0, 0, UTC), Duration(maxDuration)}, + {Date(-2109, 11, 23, 0, 0, 0, 0, UTC), Time{}, Duration(minDuration)}, + {Date(2290, 1, 1, 0, 0, 0, 0, UTC), Date(2000, 1, 1, 0, 0, 0, 0, UTC), 290*365*24*Hour + 71*24*Hour}, + {Date(2300, 1, 1, 0, 0, 0, 0, UTC), Date(2000, 1, 1, 0, 0, 0, 0, UTC), Duration(maxDuration)}, + {Date(2000, 1, 1, 0, 0, 0, 0, UTC), Date(2290, 1, 1, 0, 0, 0, 0, UTC), -290*365*24*Hour - 71*24*Hour}, + {Date(2000, 1, 1, 0, 0, 0, 0, UTC), Date(2300, 1, 1, 0, 0, 0, 0, UTC), Duration(minDuration)}, +} + +func TestSub(t *testing.T) { + for i, st := range subTests { + got := st.t.Sub(st.u) + if got != st.d { + t.Errorf("#%d: Sub(%v, %v): got %v; want %v", i, st.t, st.u, got, st.d) + } + } +} + func BenchmarkNow(b *testing.B) { for i := 0; i < b.N; i++ { t = Now() diff --git a/libgo/go/time/zoneinfo.go b/libgo/go/time/zoneinfo.go index c44477f..1c61862 100644 --- a/libgo/go/time/zoneinfo.go +++ b/libgo/go/time/zoneinfo.go @@ -178,19 +178,6 @@ func (l *Location) lookupName(name string, unix int64) (offset int, isDST bool, return } -// lookupOffset returns information about the time zone with -// the given offset (such as -5*60*60). -func (l *Location) lookupOffset(offset int) (name string, isDST bool, ok bool) { - l = l.get() - for i := range l.zone { - zone := &l.zone[i] - if zone.offset == offset { - return zone.name, zone.isDST, true - } - } - return -} - // NOTE(rsc): Eventually we will need to accept the POSIX TZ environment // syntax too, but I don't feel like implementing it today. diff --git a/libgo/go/time/zoneinfo_abbrs_windows.go b/libgo/go/time/zoneinfo_abbrs_windows.go new file mode 100644 index 0000000..8033437 --- /dev/null +++ b/libgo/go/time/zoneinfo_abbrs_windows.go @@ -0,0 +1,115 @@ +// Copyright 2013 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. + +// generated by genzabbrs.go from +// http://unicode.org/cldr/data/common/supplemental/windowsZones.xml + +package time + +type abbr struct { + std string + dst string +} + +var abbrs = map[string]abbr{ + "Egypt Standard Time": {"EET", "EET"}, // Africa/Cairo + "Morocco Standard Time": {"WET", "WEST"}, // Africa/Casablanca + "South Africa Standard Time": {"SAST", "SAST"}, // Africa/Johannesburg + "W. Central Africa Standard Time": {"WAT", "WAT"}, // Africa/Lagos + "E. Africa Standard Time": {"EAT", "EAT"}, // Africa/Nairobi + "Namibia Standard Time": {"WAT", "WAST"}, // Africa/Windhoek + "Alaskan Standard Time": {"AKST", "AKDT"}, // America/Anchorage + "Paraguay Standard Time": {"PYT", "PYST"}, // America/Asuncion + "Bahia Standard Time": {"BRT", "BRST"}, // America/Bahia + "SA Pacific Standard Time": {"COT", "COT"}, // America/Bogota + "Argentina Standard Time": {"ART", "ART"}, // America/Buenos_Aires + "Venezuela Standard Time": {"VET", "VET"}, // America/Caracas + "SA Eastern Standard Time": {"GFT", "GFT"}, // America/Cayenne + "Central Standard Time": {"CST", "CDT"}, // America/Chicago + "Mountain Standard Time (Mexico)": {"MST", "MDT"}, // America/Chihuahua + "Central Brazilian Standard Time": {"AMT", "AMST"}, // America/Cuiaba + "Mountain Standard Time": {"MST", "MDT"}, // America/Denver + "Greenland Standard Time": {"WGT", "WGST"}, // America/Godthab + "Central America Standard Time": {"CST", "CST"}, // America/Guatemala + "Atlantic Standard Time": {"AST", "ADT"}, // America/Halifax + "US Eastern Standard Time": {"EST", "EDT"}, // America/Indianapolis + "SA Western Standard Time": {"BOT", "BOT"}, // America/La_Paz + "Pacific Standard Time": {"PST", "PDT"}, // America/Los_Angeles + "Central Standard Time (Mexico)": {"CST", "CDT"}, // America/Mexico_City + "Montevideo Standard Time": {"UYT", "UYST"}, // America/Montevideo + "Eastern Standard Time": {"EST", "EDT"}, // America/New_York + "US Mountain Standard Time": {"MST", "MST"}, // America/Phoenix + "Canada Central Standard Time": {"CST", "CST"}, // America/Regina + "Pacific Standard Time (Mexico)": {"PST", "PDT"}, // America/Santa_Isabel + "Pacific SA Standard Time": {"CLT", "CLST"}, // America/Santiago + "E. South America Standard Time": {"BRT", "BRST"}, // America/Sao_Paulo + "Newfoundland Standard Time": {"NST", "NDT"}, // America/St_Johns + "Central Asia Standard Time": {"ALMT", "ALMT"}, // Asia/Almaty + "Jordan Standard Time": {"EET", "EEST"}, // Asia/Amman + "Arabic Standard Time": {"AST", "AST"}, // Asia/Baghdad + "Azerbaijan Standard Time": {"AZT", "AZST"}, // Asia/Baku + "SE Asia Standard Time": {"ICT", "ICT"}, // Asia/Bangkok + "Middle East Standard Time": {"EET", "EEST"}, // Asia/Beirut + "India Standard Time": {"IST", "IST"}, // Asia/Calcutta + "Sri Lanka Standard Time": {"IST", "IST"}, // Asia/Colombo + "Syria Standard Time": {"EET", "EEST"}, // Asia/Damascus + "Bangladesh Standard Time": {"BDT", "BDT"}, // Asia/Dhaka + "Arabian Standard Time": {"GST", "GST"}, // Asia/Dubai + "North Asia East Standard Time": {"IRKT", "IRKT"}, // Asia/Irkutsk + "Israel Standard Time": {"IST", "IDT"}, // Asia/Jerusalem + "Afghanistan Standard Time": {"AFT", "AFT"}, // Asia/Kabul + "Pakistan Standard Time": {"PKT", "PKT"}, // Asia/Karachi + "Nepal Standard Time": {"NPT", "NPT"}, // Asia/Katmandu + "North Asia Standard Time": {"KRAT", "KRAT"}, // Asia/Krasnoyarsk + "Magadan Standard Time": {"MAGT", "MAGT"}, // Asia/Magadan + "E. Europe Standard Time": {"EET", "EEST"}, // Asia/Nicosia + "N. Central Asia Standard Time": {"NOVT", "NOVT"}, // Asia/Novosibirsk + "Myanmar Standard Time": {"MMT", "MMT"}, // Asia/Rangoon + "Arab Standard Time": {"AST", "AST"}, // Asia/Riyadh + "Korea Standard Time": {"KST", "KST"}, // Asia/Seoul + "China Standard Time": {"CST", "CST"}, // Asia/Shanghai + "Singapore Standard Time": {"SGT", "SGT"}, // Asia/Singapore + "Taipei Standard Time": {"CST", "CST"}, // Asia/Taipei + "West Asia Standard Time": {"UZT", "UZT"}, // Asia/Tashkent + "Georgian Standard Time": {"GET", "GET"}, // Asia/Tbilisi + "Iran Standard Time": {"IRST", "IRDT"}, // Asia/Tehran + "Tokyo Standard Time": {"JST", "JST"}, // Asia/Tokyo + "Ulaanbaatar Standard Time": {"ULAT", "ULAT"}, // Asia/Ulaanbaatar + "Vladivostok Standard Time": {"VLAT", "VLAT"}, // Asia/Vladivostok + "Yakutsk Standard Time": {"YAKT", "YAKT"}, // Asia/Yakutsk + "Ekaterinburg Standard Time": {"YEKT", "YEKT"}, // Asia/Yekaterinburg + "Caucasus Standard Time": {"AMT", "AMT"}, // Asia/Yerevan + "Azores Standard Time": {"AZOT", "AZOST"}, // Atlantic/Azores + "Cape Verde Standard Time": {"CVT", "CVT"}, // Atlantic/Cape_Verde + "Greenwich Standard Time": {"GMT", "GMT"}, // Atlantic/Reykjavik + "Cen. Australia Standard Time": {"CST", "CST"}, // Australia/Adelaide + "E. Australia Standard Time": {"EST", "EST"}, // Australia/Brisbane + "AUS Central Standard Time": {"CST", "CST"}, // Australia/Darwin + "Tasmania Standard Time": {"EST", "EST"}, // Australia/Hobart + "W. Australia Standard Time": {"WST", "WST"}, // Australia/Perth + "AUS Eastern Standard Time": {"EST", "EST"}, // Australia/Sydney + "UTC": {"GMT", "GMT"}, // Etc/GMT + "UTC-11": {"GMT+11", "GMT+11"}, // Etc/GMT+11 + "Dateline Standard Time": {"GMT+12", "GMT+12"}, // Etc/GMT+12 + "UTC-02": {"GMT+2", "GMT+2"}, // Etc/GMT+2 + "UTC+12": {"GMT-12", "GMT-12"}, // Etc/GMT-12 + "W. Europe Standard Time": {"CET", "CEST"}, // Europe/Berlin + "GTB Standard Time": {"EET", "EEST"}, // Europe/Bucharest + "Central Europe Standard Time": {"CET", "CEST"}, // Europe/Budapest + "Turkey Standard Time": {"EET", "EEST"}, // Europe/Istanbul + "Kaliningrad Standard Time": {"FET", "FET"}, // Europe/Kaliningrad + "FLE Standard Time": {"EET", "EEST"}, // Europe/Kiev + "GMT Standard Time": {"GMT", "BST"}, // Europe/London + "Russian Standard Time": {"MSK", "MSK"}, // Europe/Moscow + "Romance Standard Time": {"CET", "CEST"}, // Europe/Paris + "Central European Standard Time": {"CET", "CEST"}, // Europe/Warsaw + "Mauritius Standard Time": {"MUT", "MUT"}, // Indian/Mauritius + "Samoa Standard Time": {"WST", "WST"}, // Pacific/Apia + "New Zealand Standard Time": {"NZST", "NZDT"}, // Pacific/Auckland + "Fiji Standard Time": {"FJT", "FJT"}, // Pacific/Fiji + "Central Pacific Standard Time": {"SBT", "SBT"}, // Pacific/Guadalcanal + "Hawaiian Standard Time": {"HST", "HST"}, // Pacific/Honolulu + "West Pacific Standard Time": {"PGT", "PGT"}, // Pacific/Port_Moresby + "Tonga Standard Time": {"TOT", "TOT"}, // Pacific/Tongatapu +} diff --git a/libgo/go/time/zoneinfo_read.go b/libgo/go/time/zoneinfo_read.go index 4519c99..7714aa9 100644 --- a/libgo/go/time/zoneinfo_read.go +++ b/libgo/go/time/zoneinfo_read.go @@ -11,10 +11,6 @@ package time import "errors" -const ( - headerSize = 4 + 16 + 4*7 -) - // Simple I/O interface to binary blob of data. type data struct { p []byte diff --git a/libgo/go/time/zoneinfo_unix.go b/libgo/go/time/zoneinfo_unix.go index 1bf1f11..7207025 100644 --- a/libgo/go/time/zoneinfo_unix.go +++ b/libgo/go/time/zoneinfo_unix.go @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. -// +build darwin freebsd linux netbsd openbsd +// +build darwin dragonfly freebsd linux netbsd openbsd // Parse "zoneinfo" time zone file. // This is a fairly standard file format used on OS X, Linux, BSD, Sun, and others. diff --git a/libgo/go/time/zoneinfo_windows.go b/libgo/go/time/zoneinfo_windows.go index a8d3dcb..1e18ad2 100644 --- a/libgo/go/time/zoneinfo_windows.go +++ b/libgo/go/time/zoneinfo_windows.go @@ -8,6 +8,7 @@ import ( "errors" "runtime" "syscall" + "unsafe" ) // TODO(rsc): Fall back to copy of zoneinfo files. @@ -16,21 +17,83 @@ import ( // time zone information. // The implementation assumes that this year's rules for daylight savings // time apply to all previous and future years as well. -// Also, time zone abbreviations are unavailable. The implementation constructs -// them using the capital letters from a longer time zone description. - -// abbrev returns the abbreviation to use for the given zone name. -func abbrev(name []uint16) string { - // name is 'Pacific Standard Time' but we want 'PST'. - // Extract just capital letters. It's not perfect but the - // information we need is not available from the kernel. - // Because time zone abbreviations are not unique, - // Windows refuses to expose them. - // - // http://social.msdn.microsoft.com/Forums/eu/vclanguage/thread/a87e1d25-fb71-4fe0-ae9c-a9578c9753eb - // http://stackoverflow.com/questions/4195948/windows-time-zone-abbreviations-in-asp-net + +// getKeyValue retrieves the string value kname associated with the open registry key kh. +func getKeyValue(kh syscall.Handle, kname string) (string, error) { + var buf [50]uint16 // buf needs to be large enough to fit zone descriptions + var typ uint32 + n := uint32(len(buf) * 2) // RegQueryValueEx's signature expects array of bytes, not uint16 + p, _ := syscall.UTF16PtrFromString(kname) + if err := syscall.RegQueryValueEx(kh, p, nil, &typ, (*byte)(unsafe.Pointer(&buf[0])), &n); err != nil { + return "", err + } + if typ != syscall.REG_SZ { // null terminated strings only + return "", errors.New("Key is not string") + } + return syscall.UTF16ToString(buf[:]), nil +} + +// matchZoneKey checks if stdname and dstname match the corresponding "Std" +// and "Dlt" key values in the kname key stored under the open registry key zones. +func matchZoneKey(zones syscall.Handle, kname string, stdname, dstname string) (matched bool, err2 error) { + var h syscall.Handle + p, _ := syscall.UTF16PtrFromString(kname) + if err := syscall.RegOpenKeyEx(zones, p, 0, syscall.KEY_READ, &h); err != nil { + return false, err + } + defer syscall.RegCloseKey(h) + + s, err := getKeyValue(h, "Std") + if err != nil { + return false, err + } + if s != stdname { + return false, nil + } + s, err = getKeyValue(h, "Dlt") + if err != nil { + return false, err + } + if s != dstname { + return false, nil + } + return true, nil +} + +// toEnglishName searches the registry for an English name of a time zone +// whose zone names are stdname and dstname and returns the English name. +func toEnglishName(stdname, dstname string) (string, error) { + var zones syscall.Handle + p, _ := syscall.UTF16PtrFromString(`SOFTWARE\Microsoft\Windows NT\CurrentVersion\Time Zones`) + if err := syscall.RegOpenKeyEx(syscall.HKEY_LOCAL_MACHINE, p, 0, syscall.KEY_READ, &zones); err != nil { + return "", err + } + defer syscall.RegCloseKey(zones) + + var count uint32 + if err := syscall.RegQueryInfoKey(zones, nil, nil, nil, &count, nil, nil, nil, nil, nil, nil, nil); err != nil { + return "", err + } + + var buf [50]uint16 // buf needs to be large enough to fit zone descriptions + for i := uint32(0); i < count; i++ { + n := uint32(len(buf)) + if syscall.RegEnumKeyEx(zones, i, &buf[0], &n, nil, nil, nil, nil) != nil { + continue + } + kname := syscall.UTF16ToString(buf[:]) + matched, err := matchZoneKey(zones, kname, stdname, dstname) + if err == nil && matched { + return kname, nil + } + } + return "", errors.New(`English name for time zone "` + stdname + `" not found in registry`) +} + +// extractCAPS exracts capital letters from description desc. +func extractCAPS(desc string) string { var short []rune - for _, c := range name { + for _, c := range desc { if 'A' <= c && c <= 'Z' { short = append(short, rune(c)) } @@ -38,6 +101,26 @@ func abbrev(name []uint16) string { return string(short) } +// abbrev returns the abbreviations to use for the given zone z. +func abbrev(z *syscall.Timezoneinformation) (std, dst string) { + stdName := syscall.UTF16ToString(z.StandardName[:]) + a, ok := abbrs[stdName] + if !ok { + dstName := syscall.UTF16ToString(z.DaylightName[:]) + // Perhaps stdName is not English. Try to convert it. + englishName, err := toEnglishName(stdName, dstName) + if err == nil { + a, ok = abbrs[englishName] + if ok { + return a.std, a.dst + } + } + // fallback to using capital letters + return extractCAPS(stdName), extractCAPS(dstName) + } + return a.std, a.dst +} + // pseudoUnix returns the pseudo-Unix time (seconds since Jan 1 1970 *LOCAL TIME*) // denoted by the system date+time d in the given year. // It is up to the caller to convert this local time into a UTC-based time. @@ -75,8 +158,10 @@ func initLocalFromTZI(i *syscall.Timezoneinformation) { } l.zone = make([]zone, nzone) + stdname, dstname := abbrev(i) + std := &l.zone[0] - std.name = abbrev(i.StandardName[0:]) + std.name = stdname if nzone == 1 { // No daylight savings. std.offset = -int(i.Bias) * 60 @@ -95,7 +180,7 @@ func initLocalFromTZI(i *syscall.Timezoneinformation) { std.offset = -int(i.Bias+i.StandardBias) * 60 dst := &l.zone[1] - dst.name = abbrev(i.DaylightName[0:]) + dst.name = dstname dst.offset = -int(i.Bias+i.DaylightBias) * 60 dst.isDST = true @@ -142,10 +227,27 @@ var usPacific = syscall.Timezoneinformation{ DaylightBias: -60, } +var aus = syscall.Timezoneinformation{ + Bias: -10 * 60, + StandardName: [32]uint16{ + 'A', 'U', 'S', ' ', 'E', 'a', 's', 't', 'e', 'r', 'n', ' ', 'S', 't', 'a', 'n', 'd', 'a', 'r', 'd', ' ', 'T', 'i', 'm', 'e', + }, + StandardDate: syscall.Systemtime{Month: 4, Day: 1, Hour: 3}, + DaylightName: [32]uint16{ + 'A', 'U', 'S', ' ', 'E', 'a', 's', 't', 'e', 'r', 'n', ' ', 'D', 'a', 'y', 'l', 'i', 'g', 'h', 't', ' ', 'T', 'i', 'm', 'e', + }, + DaylightDate: syscall.Systemtime{Month: 10, Day: 1, Hour: 2}, + DaylightBias: -60, +} + func initTestingZone() { initLocalFromTZI(&usPacific) } +func initAusTestingZone() { + initLocalFromTZI(&aus) +} + func initLocal() { var i syscall.Timezoneinformation if _, err := syscall.GetTimeZoneInformation(&i); err != nil { -- cgit v1.1