diff options
Diffstat (limited to 'libgo/go/time')
-rw-r--r-- | libgo/go/time/example_test.go | 43 | ||||
-rw-r--r-- | libgo/go/time/format.go | 98 | ||||
-rw-r--r-- | libgo/go/time/sleep.go | 25 | ||||
-rw-r--r-- | libgo/go/time/sleep_test.go | 25 | ||||
-rw-r--r-- | libgo/go/time/time_test.go | 76 | ||||
-rw-r--r-- | libgo/go/time/zoneinfo.go | 25 | ||||
-rw-r--r-- | libgo/go/time/zoneinfo_read.go | 6 |
7 files changed, 242 insertions, 56 deletions
diff --git a/libgo/go/time/example_test.go b/libgo/go/time/example_test.go index cda565f..cfa5b38 100644 --- a/libgo/go/time/example_test.go +++ b/libgo/go/time/example_test.go @@ -58,15 +58,52 @@ func ExampleDate() { } func ExampleTime_Format() { - const format = "Jan 2, 2006 at 3:04pm (MST)" + // layout shows by example how the reference time should be represented. + const layout = "Jan 2, 2006 at 3:04pm (MST)" t := time.Date(2009, time.November, 10, 15, 0, 0, 0, time.Local) - fmt.Println(t.Format(format)) - fmt.Println(t.UTC().Format(format)) + fmt.Println(t.Format(layout)) + fmt.Println(t.UTC().Format(layout)) // Output: // Nov 10, 2009 at 3:00pm (PST) // Nov 10, 2009 at 11:00pm (UTC) } +func ExampleParse() { + // longForm shows by example how the reference time would be represented in + // the desired layout. + const longForm = "Jan 2, 2006 at 3:04pm (MST)" + t, _ := time.Parse(longForm, "Feb 3, 2013 at 7:54pm (PST)") + fmt.Println(t) + + // shortForm is another way the reference time would be represented + // in the desired layout; it has no time zone present. + // Note: without explicit zone, returns time in UTC. + const shortForm = "2006-Jan-02" + t, _ = time.Parse(shortForm, "2013-Feb-03") + fmt.Println(t) + + // Output: + // 2013-02-03 19:54:00 -0800 PST + // 2013-02-03 00:00:00 +0000 UTC +} + +func ExampleParseInLocation() { + loc, _ := time.LoadLocation("Europe/Berlin") + + const longForm = "Jan 2, 2006 at 3:04pm (MST)" + t, _ := time.ParseInLocation(longForm, "Jul 9, 2012 at 5:02am (CEST)", loc) + fmt.Println(t) + + // Note: without explicit zone, returns time in given location. + const shortForm = "2006-Jan-02" + t, _ = time.ParseInLocation(shortForm, "2012-Jul-09", loc) + fmt.Println(t) + + // Output: + // 2012-07-09 05:02:00 +0200 CEST + // 2012-07-09 00:00:00 +0200 CEST +} + func ExampleTime_Round() { t := time.Date(0, 0, 0, 12, 15, 30, 918273645, time.UTC) round := []time.Duration{ diff --git a/libgo/go/time/format.go b/libgo/go/time/format.go index 8d21040..7fe0402 100644 --- a/libgo/go/time/format.go +++ b/libgo/go/time/format.go @@ -6,15 +6,17 @@ package time import "errors" -// These are predefined layouts for use in Time.Format. -// The standard time used in the layouts is: +// These are predefined layouts for use in Time.Format and Time.Parse. +// The reference time used in the layouts is: // Mon Jan 2 15:04:05 MST 2006 // which is Unix time 1136239445. Since MST is GMT-0700, -// the standard time can be thought of as +// the reference time can be thought of as // 01/02 03:04:05PM '06 -0700 -// To define your own format, write down what the standard time would look +// To define your own format, write down what the reference time would look // like formatted your way; see the values of constants like ANSIC, -// StampMicro or Kitchen for examples. +// StampMicro or Kitchen for examples. The model is to demonstrate what the +// reference time looks like so that the Format and Parse methods can apply +// the same transformation to a general time value. // // 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 @@ -367,13 +369,16 @@ func (t Time) String() string { } // Format returns a textual representation of the time value formatted -// according to layout. The layout defines the format by showing the -// representation of the standard time, +// according to layout, which defines the format by showing how the reference +// time, // Mon Jan 2 15:04:05 -0700 MST 2006 -// which is then used to describe the time to be formatted. Predefined -// layouts ANSIC, UnixDate, RFC3339 and others describe standard -// representations. For more information about the formats and the -// definition of the standard time, see the documentation for ANSIC. +// would be displayed if it were the value; it serves as an example of the +// desired output. The same display rules will then be applied to the time +// value. +// Predefined layouts ANSIC, UnixDate, RFC3339 and others describe standard +// and convenient representations of the reference time. For more information +// about the formats and the definition of the reference time, see the +// documentation for ANSIC and the other constants defined by this package. func (t Time) Format(layout string) string { var ( name, offset, abs = t.locabs() @@ -611,14 +616,14 @@ func skip(value, prefix string) (string, error) { for len(prefix) > 0 { if prefix[0] == ' ' { if len(value) > 0 && value[0] != ' ' { - return "", errBad + return value, errBad } prefix = cutspace(prefix) value = cutspace(value) continue } if len(value) == 0 || value[0] != prefix[0] { - return "", errBad + return value, errBad } prefix = prefix[1:] value = value[1:] @@ -627,13 +632,15 @@ func skip(value, prefix string) (string, error) { } // Parse parses a formatted string and returns the time value it represents. -// The layout defines the format by showing the representation of the -// standard time, +// The layout defines the format by showing how the reference time, // Mon Jan 2 15:04:05 -0700 MST 2006 -// which is then used to describe the string to be parsed. Predefined layouts -// ANSIC, UnixDate, RFC3339 and others describe standard representations. For -// more information about the formats and the definition of the standard -// time, see the documentation for ANSIC. +// would be interpreted if it were the value; it serves as an example of +// the input format. The same interpretation will then be made to the +// input string. +// Predefined layouts ANSIC, UnixDate, RFC3339 and others describe standard +// and convenient representations of the reference time. For more information +// about the formats and the definition of the reference time, see the +// documentation for ANSIC and the other constants defined by this package. // // Elements omitted from the value are assumed to be zero or, when // zero is impossible, one, so parsing "3:04pm" returns the time @@ -641,7 +648,37 @@ func skip(value, prefix string) (string, error) { // 0, this time is before the zero Time). // Years must be in the range 0000..9999. The day of the week is checked // for syntax but it is otherwise ignored. +// +// In the absence of a time zone indicator, Parse returns a time in UTC. +// +// When parsing a time with a zone offset like -0700, if the offset corresponds +// to a time zone used by the current location (Local), then Parse uses that +// location and zone in the returned time. Otherwise it records the time as +// being in a fabricated location with time fixed at the given zone offset. +// +// When parsing a time with a zone abbreviation like MST, if the zone abbreviation +// has a defined offset in the current location, then that offset is used. +// The zone abbreviation "UTC" is recognized as UTC regardless of location. +// If the zone abbreviation is unknown, Parse records the time as being +// in a fabricated location with the given zone abbreviation and a zero offset. +// This choice means that such a time can be parse and reformatted with the +// same layout losslessly, but the exact instant used in the representation will +// differ by the actual zone offset. To avoid such problems, prefer time layouts +// that use a numeric zone offset, or use ParseInLocation. func Parse(layout, value string) (Time, error) { + return parse(layout, value, UTC, Local) +} + +// ParseInLocation is like Parse but differs in two important ways. +// First, in the absence of time zone information, Parse interprets a time as UTC; +// ParseInLocation interprets the time as in the given location. +// Second, when given a zone offset or abbreviation, Parse tries to match it +// against the Local location; ParseInLocation uses the given location. +func ParseInLocation(layout, value string, loc *Location) (Time, error) { + return parse(layout, value, loc, loc) +} + +func parse(layout, value string, defaultLocation, local *Location) (Time, error) { alayout, avalue := layout, value rangeErrString := "" // set if a value is out of range amSet := false // do we need to subtract 12 from the hour for midnight? @@ -892,20 +929,19 @@ func Parse(layout, value string) (Time, error) { hour = 0 } - // TODO: be more aggressive checking day? if z != nil { return Date(year, Month(month), day, hour, min, sec, nsec, z), nil } - t := Date(year, Month(month), day, hour, min, sec, nsec, UTC) if zoneOffset != -1 { + t := Date(year, Month(month), day, hour, min, sec, nsec, UTC) t.sec -= int64(zoneOffset) // Look for local zone with the given offset. // If that zone was in effect at the given time, use it. - name, offset, _, _, _ := Local.lookup(t.sec + internalToUnix) + name, offset, _, _, _ := local.lookup(t.sec + internalToUnix) if offset == zoneOffset && (zoneName == "" || name == zoneName) { - t.loc = Local + t.loc = local return t, nil } @@ -915,16 +951,14 @@ func Parse(layout, value string) (Time, error) { } if zoneName != "" { + t := Date(year, Month(month), day, hour, min, sec, nsec, UTC) // Look for local zone with the given offset. // If that zone was in effect at the given time, use it. - offset, _, ok := Local.lookupName(zoneName) + offset, _, ok := local.lookupName(zoneName, t.sec+internalToUnix) if ok { - name, off, _, _, _ := Local.lookup(t.sec + internalToUnix - int64(offset)) - if name == zoneName && off == offset { - t.sec -= int64(offset) - t.loc = Local - return t, nil - } + t.sec -= int64(offset) + t.loc = local + return t, nil } // Otherwise, create fake zone with unknown offset. @@ -932,8 +966,8 @@ func Parse(layout, value string) (Time, error) { return t, nil } - // Otherwise, fall back to UTC. - return t, nil + // Otherwise, fall back to default. + return Date(year, Month(month), day, hour, min, sec, nsec, defaultLocation), nil } func parseNanoseconds(value string, nbytes int) (ns int, rangeErrString string, err error) { diff --git a/libgo/go/time/sleep.go b/libgo/go/time/sleep.go index 1e6b4f2..591fa27 100644 --- a/libgo/go/time/sleep.go +++ b/libgo/go/time/sleep.go @@ -18,10 +18,25 @@ type runtimeTimer struct { i int32 when int64 period int64 - f func(int64, interface{}) + f func(int64, interface{}) // NOTE: must not be closure arg interface{} } +// when is a helper function for setting the 'when' field of a runtimeTimer. +// It returns what the time will be, in nanoseconds, Duration d in the future. +// If d is negative, it is ignored. If the returned value would be less than +// zero because of an overflow, MaxInt64 is returned. +func when(d Duration) int64 { + if d <= 0 { + return nano() + } + t := nano() + int64(d) + if t < 0 { + t = 1<<63 - 1 // math.MaxInt64 + } + return t +} + func startTimer(*runtimeTimer) func stopTimer(*runtimeTimer) bool @@ -49,7 +64,7 @@ func NewTimer(d Duration) *Timer { t := &Timer{ C: c, r: runtimeTimer{ - when: nano() + int64(d), + when: when(d), f: sendTime, arg: c, }, @@ -62,9 +77,9 @@ func NewTimer(d Duration) *Timer { // It returns true if the timer had been active, false if the timer had // expired or been stopped. func (t *Timer) Reset(d Duration) bool { - when := nano() + int64(d) + w := when(d) active := stopTimer(&t.r) - t.r.when = when + t.r.when = w startTimer(&t.r) return active } @@ -94,7 +109,7 @@ func After(d Duration) <-chan Time { func AfterFunc(d Duration, f func()) *Timer { t := &Timer{ r: runtimeTimer{ - when: nano() + int64(d), + when: when(d), f: goFunc, arg: f, }, diff --git a/libgo/go/time/sleep_test.go b/libgo/go/time/sleep_test.go index e5a9fdf..762549d 100644 --- a/libgo/go/time/sleep_test.go +++ b/libgo/go/time/sleep_test.go @@ -60,10 +60,11 @@ func TestAfterStress(t *testing.T) { Sleep(Nanosecond) } }() - c := Tick(1) + ticker := NewTicker(1) for i := 0; i < 100; i++ { - <-c + <-ticker.C } + ticker.Stop() atomic.StoreUint32(&stop, 1) } @@ -294,3 +295,23 @@ func TestReset(t *testing.T) { } t.Error(err) } + +// Test that sleeping for an interval so large it overflows does not +// result in a short sleep duration. +func TestOverflowSleep(t *testing.T) { + const timeout = 25 * Millisecond + const big = Duration(int64(1<<63 - 1)) + select { + case <-After(big): + t.Fatalf("big timeout fired") + case <-After(timeout): + // OK + } + const neg = Duration(-1 << 63) + select { + case <-After(neg): + // OK + case <-After(timeout): + t.Fatalf("negative timeout didn't fire") + } +} diff --git a/libgo/go/time/time_test.go b/libgo/go/time/time_test.go index a8953ae..a0ee37a 100644 --- a/libgo/go/time/time_test.go +++ b/libgo/go/time/time_test.go @@ -479,6 +479,7 @@ var parseTests = []ParseTest{ {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0800 2010", true, true, 1, 0}, {"RFC850", RFC850, "Thursday, 04-Feb-10 21:00:57 PST", true, true, 1, 0}, {"RFC1123", RFC1123, "Thu, 04 Feb 2010 21:00:57 PST", true, true, 1, 0}, + {"RFC1123", RFC1123, "Thu, 04 Feb 2010 22:00:57 PDT", true, true, 1, 0}, {"RFC1123Z", RFC1123Z, "Thu, 04 Feb 2010 21:00:57 -0800", true, true, 1, 0}, {"RFC3339", RFC3339, "2010-02-04T21:00:57-08:00", true, false, 1, 0}, {"custom: \"2006-01-02 15:04:05-07\"", "2006-01-02 15:04:05-07", "2010-02-04 21:00:57-08", true, false, 1, 0}, @@ -534,6 +535,42 @@ func TestParse(t *testing.T) { } } +func TestParseInSydney(t *testing.T) { + loc, err := LoadLocation("Australia/Sydney") + if err != nil { + t.Fatal(err) + } + + // Check that Parse (and ParseInLocation) understand + // that Feb EST and Aug EST are different time zones in Sydney + // even though both are called EST. + t1, err := ParseInLocation("Jan 02 2006 MST", "Feb 01 2013 EST", loc) + if err != nil { + t.Fatal(err) + } + t2 := Date(2013, February, 1, 00, 00, 00, 0, loc) + if t1 != t2 { + t.Fatalf("ParseInLocation(Feb 01 2013 EST, Sydney) = %v, want %v", t1, t2) + } + _, offset := t1.Zone() + if offset != 11*60*60 { + t.Fatalf("ParseInLocation(Feb 01 2013 EST, Sydney).Zone = _, %d, want _, %d", offset, 11*60*60) + } + + t1, err = ParseInLocation("Jan 02 2006 MST", "Aug 01 2013 EST", loc) + if err != nil { + t.Fatal(err) + } + t2 = Date(2013, August, 1, 00, 00, 00, 0, loc) + if t1 != t2 { + t.Fatalf("ParseInLocation(Aug 01 2013 EST, Sydney) = %v, want %v", t1, t2) + } + _, offset = t1.Zone() + if offset != 10*60*60 { + t.Fatalf("ParseInLocation(Aug 01 2013 EST, Sydney).Zone = _, %d, want _, %d", offset, 10*60*60) + } +} + var rubyTests = []ParseTest{ {"RubyDate", RubyDate, "Thu Feb 04 21:00:57 -0800 2010", true, true, 1, 0}, // Ignore the time zone in the test. If it parses, it'll be OK. @@ -640,6 +677,11 @@ var parseErrorTests = []ParseErrorTest{ // issue 4502. StampNano requires exactly 9 digits of precision. {StampNano, "Dec 7 11:22:01.000000", `cannot parse ".000000" as ".000000000"`}, {StampNano, "Dec 7 11:22:01.0000000000", "extra text: 0"}, + // issue 4493. Helpful errors. + {RFC3339, "2006-01-02T15:04:05Z07:00", `parsing time "2006-01-02T15:04:05Z07:00": extra text: 07:00`}, + {RFC3339, "2006-01-02T15:04_abc", `parsing time "2006-01-02T15:04_abc" as "2006-01-02T15:04:05Z07:00": cannot parse "_abc" as ":"`}, + {RFC3339, "2006-01-02T15:04:05_abc", `parsing time "2006-01-02T15:04:05_abc" as "2006-01-02T15:04:05Z07:00": cannot parse "_abc" as "Z07:00"`}, + {RFC3339, "2006-01-02T15:04:05Z_abc", `parsing time "2006-01-02T15:04:05Z_abc": extra text: _abc`}, } func TestParseErrors(t *testing.T) { @@ -1258,23 +1300,33 @@ var mallocTest = []struct { } func TestCountMallocs(t *testing.T) { - defer runtime.GOMAXPROCS(runtime.GOMAXPROCS(1)) + if runtime.GOMAXPROCS(0) > 1 { + t.Skip("skipping; GOMAXPROCS>1") + } for _, mt := range mallocTest { - const N = 100 - memstats := new(runtime.MemStats) - runtime.ReadMemStats(memstats) - mallocs := 0 - memstats.Mallocs - for i := 0; i < N; i++ { - mt.fn() - } - runtime.ReadMemStats(memstats) - mallocs += memstats.Mallocs - if mallocs/N > uint64(mt.count) { - t.Errorf("%s: expected %d mallocs, got %d", mt.desc, mt.count, mallocs/N) + allocs := int(testing.AllocsPerRun(100, mt.fn)) + if allocs > mt.count { + t.Errorf("%s: %d allocs, want %d", mt.desc, allocs, mt.count) } } } +func TestLoadFixed(t *testing.T) { + // Issue 4064: handle locations without any zone transitions. + loc, err := LoadLocation("Etc/GMT+1") + if err != nil { + t.Fatal(err) + } + + // The tzdata name Etc/GMT+1 uses "east is negative", + // but Go and most other systems use "east is positive". + // So GMT+1 corresponds to -3600 in the Go zone, not +3600. + name, offset := Now().In(loc).Zone() + if name != "GMT+1" || offset != -1*60*60 { + t.Errorf("Now().In(loc).Zone() = %q, %d, want %q, %d", name, offset, "GMT+1", -1*60*60) + } +} + 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 116d343..c44477f 100644 --- a/libgo/go/time/zoneinfo.go +++ b/libgo/go/time/zoneinfo.go @@ -145,15 +145,36 @@ func (l *Location) lookup(sec int64) (name string, offset int, isDST bool, start } // lookupName returns information about the time zone with -// the given name (such as "EST"). -func (l *Location) lookupName(name string) (offset int, isDST bool, ok bool) { +// the given name (such as "EST") at the given pseudo-Unix time +// (what the given time of day would be in UTC). +func (l *Location) lookupName(name string, unix int64) (offset int, isDST bool, ok bool) { l = l.get() + + // First try for a zone with the right name that was actually + // in effect at the given time. (In Sydney, Australia, both standard + // and daylight-savings time are abbreviated "EST". Using the + // offset helps us pick the right one for the given time. + // It's not perfect: during the backward transition we might pick + // either one.) + for i := range l.zone { + zone := &l.zone[i] + if zone.name == name { + nam, offset, isDST, _, _ := l.lookup(unix - int64(zone.offset)) + if nam == zone.name { + return offset, isDST, true + } + } + } + + // Otherwise fall back to an ordinary name match. for i := range l.zone { zone := &l.zone[i] if zone.name == name { return zone.offset, zone.isDST, true } } + + // Otherwise, give up. return } diff --git a/libgo/go/time/zoneinfo_read.go b/libgo/go/time/zoneinfo_read.go index a5a2de2..4519c99 100644 --- a/libgo/go/time/zoneinfo_read.go +++ b/libgo/go/time/zoneinfo_read.go @@ -174,6 +174,12 @@ func loadZoneData(bytes []byte) (l *Location, err error) { } } + if len(tx) == 0 { + // Build fake transition to cover all time. + // This happens in fixed locations like "Etc/GMT0". + tx = append(tx, zoneTrans{when: -1 << 63, index: 0}) + } + // Committed to succeed. l = &Location{zone: zone, tx: tx} |