diff options
Diffstat (limited to 'src/pkg/time/time_test.go')
-rw-r--r-- | src/pkg/time/time_test.go | 424 |
1 files changed, 417 insertions, 7 deletions
diff --git a/src/pkg/time/time_test.go b/src/pkg/time/time_test.go index 28047804e..4b268f73d 100644 --- a/src/pkg/time/time_test.go +++ b/src/pkg/time/time_test.go @@ -9,6 +9,7 @@ import ( "encoding/gob" "encoding/json" "fmt" + "math/big" "math/rand" "strconv" "strings" @@ -192,6 +193,184 @@ func TestNanosecondsToUTCAndBack(t *testing.T) { } } +// The time routines provide no way to get absolute time +// (seconds since zero), but we need it to compute the right +// answer for bizarre roundings like "to the nearest 3 ns". +// Compute as t - year1 = (t - 1970) + (1970 - 2001) + (2001 - 1). +// t - 1970 is returned by Unix and Nanosecond. +// 1970 - 2001 is -(31*365+8)*86400 = -978307200 seconds. +// 2001 - 1 is 2000*365.2425*86400 = 63113904000 seconds. +const unixToZero = -978307200 + 63113904000 + +// abs returns the absolute time stored in t, as seconds and nanoseconds. +func abs(t Time) (sec, nsec int64) { + unix := t.Unix() + nano := t.Nanosecond() + return unix + unixToZero, int64(nano) +} + +// absString returns abs as a decimal string. +func absString(t Time) string { + sec, nsec := abs(t) + if sec < 0 { + sec = -sec + nsec = -nsec + if nsec < 0 { + nsec += 1e9 + sec-- + } + return fmt.Sprintf("-%d%09d", sec, nsec) + } + return fmt.Sprintf("%d%09d", sec, nsec) +} + +var truncateRoundTests = []struct { + t Time + d Duration +}{ + {Date(-1, January, 1, 12, 15, 30, 5e8, UTC), 3}, + {Date(-1, January, 1, 12, 15, 31, 5e8, UTC), 3}, + {Date(2012, January, 1, 12, 15, 30, 5e8, UTC), Second}, + {Date(2012, January, 1, 12, 15, 31, 5e8, UTC), Second}, +} + +func TestTruncateRound(t *testing.T) { + var ( + bsec = new(big.Int) + bnsec = new(big.Int) + bd = new(big.Int) + bt = new(big.Int) + br = new(big.Int) + bq = new(big.Int) + b1e9 = new(big.Int) + ) + + b1e9.SetInt64(1e9) + + testOne := func(ti, tns, di int64) bool { + t0 := Unix(ti, int64(tns)).UTC() + d := Duration(di) + if d < 0 { + d = -d + } + if d <= 0 { + d = 1 + } + + // Compute bt = absolute nanoseconds. + sec, nsec := abs(t0) + bsec.SetInt64(sec) + bnsec.SetInt64(nsec) + bt.Mul(bsec, b1e9) + bt.Add(bt, bnsec) + + // Compute quotient and remainder mod d. + bd.SetInt64(int64(d)) + bq.DivMod(bt, bd, br) + + // To truncate, subtract remainder. + // br is < d, so it fits in an int64. + r := br.Int64() + t1 := t0.Add(-Duration(r)) + + // Check that time.Truncate works. + if trunc := t0.Truncate(d); trunc != t1 { + t.Errorf("Time.Truncate(%s, %s) = %s, want %s\n"+ + "%v trunc %v =\n%v want\n%v", + t0.Format(RFC3339Nano), d, trunc, t1.Format(RFC3339Nano), + absString(t0), int64(d), absString(trunc), absString(t1)) + return false + } + + // To round, add d back if remainder r > d/2 or r == exactly d/2. + // The commented out code would round half to even instead of up, + // but that makes it time-zone dependent, which is a bit strange. + if r > int64(d)/2 || r+r == int64(d) /*&& bq.Bit(0) == 1*/ { + t1 = t1.Add(Duration(d)) + } + + // Check that time.Round works. + if rnd := t0.Round(d); rnd != t1 { + t.Errorf("Time.Round(%s, %s) = %s, want %s\n"+ + "%v round %v =\n%v want\n%v", + t0.Format(RFC3339Nano), d, rnd, t1.Format(RFC3339Nano), + absString(t0), int64(d), absString(rnd), absString(t1)) + return false + } + return true + } + + // manual test cases + for _, tt := range truncateRoundTests { + testOne(tt.t.Unix(), int64(tt.t.Nanosecond()), int64(tt.d)) + } + + // exhaustive near 0 + for i := 0; i < 100; i++ { + for j := 1; j < 100; j++ { + testOne(unixToZero, int64(i), int64(j)) + testOne(unixToZero, -int64(i), int64(j)) + if t.Failed() { + return + } + } + } + + if t.Failed() { + return + } + + // randomly generated test cases + cfg := &quick.Config{MaxCount: 100000} + if testing.Short() { + cfg.MaxCount = 1000 + } + + // divisors of Second + f1 := func(ti int64, tns int32, logdi int32) bool { + d := Duration(1) + a, b := uint(logdi%9), (logdi>>16)%9 + d <<= a + for i := 0; i < int(b); i++ { + d *= 5 + } + return testOne(ti, int64(tns), int64(d)) + } + quick.Check(f1, cfg) + + // multiples of Second + f2 := func(ti int64, tns int32, di int32) bool { + d := Duration(di) * Second + if d < 0 { + d = -d + } + return testOne(ti, int64(tns), int64(d)) + } + quick.Check(f2, cfg) + + // halfway cases + f3 := func(tns, di int64) bool { + di &= 0xfffffffe + if di == 0 { + di = 2 + } + tns -= tns % di + if tns < 0 { + tns += di / 2 + } else { + tns -= di / 2 + } + return testOne(0, tns, di) + } + quick.Check(f3, cfg) + + // full generality + f4 := func(ti int64, tns int32, di int64) bool { + return testOne(ti, int64(tns), di) + } + quick.Check(f4, cfg) +} + type TimeFormatTest struct { time Time formattedValue string @@ -289,7 +468,7 @@ type ParseTest struct { value string hasTZ bool // contains a time zone hasWD bool // contains a weekday - yearSign int // sign of year + yearSign int // sign of year, -1 indicates the year is not present in the format fracDigits int // number of digits of fractional second } @@ -299,6 +478,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}, @@ -324,6 +504,23 @@ 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}, + + // 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 + // cases would succeed, but the next four would not. Go 1.1 accepts all six. + {"", "2006-01-02 15:04:05.9999 -0700 MST", "2010-02-04 21:00:57 -0800 PST", true, false, 1, 0}, + {"", "2006-01-02 15:04:05.999999999 -0700 MST", "2010-02-04 21:00:57 -0800 PST", true, false, 1, 0}, + {"", "2006-01-02 15:04:05.9999 -0700 MST", "2010-02-04 21:00:57.0123 -0800 PST", true, false, 1, 4}, + {"", "2006-01-02 15:04:05.999999999 -0700 MST", "2010-02-04 21:00:57.0123 -0800 PST", true, false, 1, 4}, + {"", "2006-01-02 15:04:05.9999 -0700 MST", "2010-02-04 21:00:57.012345678 -0800 PST", true, false, 1, 9}, + {"", "2006-01-02 15:04:05.999999999 -0700 MST", "2010-02-04 21:00:57.012345678 -0800 PST", true, false, 1, 9}, + + // issue 4502. + {"", StampNano, "Feb 4 21:00:57.012345678", false, false, -1, 9}, + {"", "Jan _2 15:04:05.999", "Feb 4 21:00:57.012300000", false, false, -1, 4}, + {"", "Jan _2 15:04:05.999", "Feb 4 21:00:57.012345678", false, false, -1, 9}, + {"", "Jan _2 15:04:05.999999999", "Feb 4 21:00:57.0123", false, false, -1, 4}, + {"", "Jan _2 15:04:05.999999999", "Feb 4 21:00:57.012345678", false, false, -1, 9}, } func TestParse(t *testing.T) { @@ -337,6 +534,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. @@ -359,7 +592,7 @@ func TestRubyParse(t *testing.T) { func checkTime(time Time, test *ParseTest, t *testing.T) { // The time should be Thu Feb 4 21:00:57 PST 2010 - if test.yearSign*time.Year() != 2010 { + if test.yearSign >= 0 && test.yearSign*time.Year() != 2010 { t.Errorf("%s: bad year: %d not %d", test.name, time.Year(), 2010) } if time.Month() != February { @@ -440,6 +673,14 @@ var parseErrorTests = []ParseErrorTest{ {"Mon Jan _2 15:04:05.000 2006", "Thu Feb 4 23:00:59x01 2010", "cannot parse"}, {"Mon Jan _2 15:04:05.000 2006", "Thu Feb 4 23:00:59.xxx 2010", "cannot parse"}, {"Mon Jan _2 15:04:05.000 2006", "Thu Feb 4 23:00:59.-123 2010", "fractional second out of range"}, + // 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) { @@ -601,6 +842,103 @@ func TestISOWeek(t *testing.T) { } } +type YearDayTest struct { + year, month, day int + yday int +} + +// Test YearDay in several different scenarios +// and corner cases +var yearDayTests = []YearDayTest{ + // Non-leap-year tests + {2007, 1, 1, 1}, + {2007, 1, 15, 15}, + {2007, 2, 1, 32}, + {2007, 2, 15, 46}, + {2007, 3, 1, 60}, + {2007, 3, 15, 74}, + {2007, 4, 1, 91}, + {2007, 12, 31, 365}, + + // Leap-year tests + {2008, 1, 1, 1}, + {2008, 1, 15, 15}, + {2008, 2, 1, 32}, + {2008, 2, 15, 46}, + {2008, 3, 1, 61}, + {2008, 3, 15, 75}, + {2008, 4, 1, 92}, + {2008, 12, 31, 366}, + + // Looks like leap-year (but isn't) tests + {1900, 1, 1, 1}, + {1900, 1, 15, 15}, + {1900, 2, 1, 32}, + {1900, 2, 15, 46}, + {1900, 3, 1, 60}, + {1900, 3, 15, 74}, + {1900, 4, 1, 91}, + {1900, 12, 31, 365}, + + // Year one tests (non-leap) + {1, 1, 1, 1}, + {1, 1, 15, 15}, + {1, 2, 1, 32}, + {1, 2, 15, 46}, + {1, 3, 1, 60}, + {1, 3, 15, 74}, + {1, 4, 1, 91}, + {1, 12, 31, 365}, + + // Year minus one tests (non-leap) + {-1, 1, 1, 1}, + {-1, 1, 15, 15}, + {-1, 2, 1, 32}, + {-1, 2, 15, 46}, + {-1, 3, 1, 60}, + {-1, 3, 15, 74}, + {-1, 4, 1, 91}, + {-1, 12, 31, 365}, + + // 400 BC tests (leap-year) + {-400, 1, 1, 1}, + {-400, 1, 15, 15}, + {-400, 2, 1, 32}, + {-400, 2, 15, 46}, + {-400, 3, 1, 61}, + {-400, 3, 15, 75}, + {-400, 4, 1, 92}, + {-400, 12, 31, 366}, + + // Special Cases + + // Gregorian calendar change (no effect) + {1582, 10, 4, 277}, + {1582, 10, 15, 288}, +} + +// Check to see if YearDay is location sensitive +var yearDayLocations = []*Location{ + FixedZone("UTC-8", -8*60*60), + FixedZone("UTC-4", -4*60*60), + UTC, + FixedZone("UTC+4", 4*60*60), + FixedZone("UTC+8", 8*60*60), +} + +func TestYearDay(t *testing.T) { + for _, loc := range yearDayLocations { + for _, ydt := range yearDayTests { + dt := Date(ydt.year, Month(ydt.month), ydt.day, 0, 0, 0, 0, loc) + yday := dt.YearDay() + if yday != ydt.yday { + t.Errorf("got %d, expected %d for %d-%02d-%02d in %v", + yday, ydt.yday, ydt.year, ydt.month, ydt.day, loc) + } + } + } +} + var durationTests = []struct { str string d Duration @@ -741,7 +1079,7 @@ var gobTests = []Time{ Date(0, 1, 2, 3, 4, 5, 6, UTC), Date(7, 8, 9, 10, 11, 12, 13, FixedZone("", 0)), Unix(81985467080890095, 0x76543210), // Time.sec: 0x0123456789ABCDEF - Time{}, // nil location + {}, // nil location Date(1, 2, 3, 4, 5, 6, 7, FixedZone("", 32767*60)), Date(1, 2, 3, 4, 5, 6, 7, FixedZone("", -32768*60)), } @@ -805,7 +1143,7 @@ var jsonTests = []struct { time Time json string }{ - {Date(9999, 4, 12, 23, 20, 50, .52*1e9, UTC), `"9999-04-12T23:20:50.52Z"`}, + {Date(9999, 4, 12, 23, 20, 50, 520*1e6, UTC), `"9999-04-12T23:20:50.52Z"`}, {Date(1996, 12, 19, 16, 39, 57, 0, Local), `"1996-12-19T16:39:57-08:00"`}, {Date(0, 1, 1, 0, 0, 0, 1, FixedZone("", 1*60)), `"0000-01-01T00:00:00.000000001+00:01"`}, } @@ -892,6 +1230,8 @@ var parseDurationTests = []struct { {"-2m3.4s", true, -(2*Minute + 3*Second + 400*Millisecond)}, {"1h2m3s4ms5us6ns", true, 1*Hour + 2*Minute + 3*Second + 4*Millisecond + 5*Microsecond + 6*Nanosecond}, {"39h9m14.425s", true, 39*Hour + 9*Minute + 14*Second + 425*Millisecond}, + // large value + {"52763797000ns", true, 52763797000 * Nanosecond}, // errors {"", false, 0}, @@ -928,16 +1268,86 @@ func TestParseDurationRoundTrip(t *testing.T) { } } +// golang.org/issue/4622 +func TestLocationRace(t *testing.T) { + ResetLocalOnceForTest() // reset the Once to trigger the race + + c := make(chan string, 1) + go func() { + c <- Now().String() + }() + Now().String() + <-c + Sleep(100 * Millisecond) + + // Back to Los Angeles for subsequent tests: + ForceUSPacificForTesting() +} + +var ( + t Time + u int64 +) + +var mallocTest = []struct { + count int + desc string + fn func() +}{ + {0, `time.Now()`, func() { t = Now() }}, + {0, `time.Now().UnixNano()`, func() { u = Now().UnixNano() }}, +} + +func TestCountMallocs(t *testing.T) { + for _, mt := range mallocTest { + 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++ { - Now() + t = Now() + } +} + +func BenchmarkNowUnixNano(b *testing.B) { + for i := 0; i < b.N; i++ { + u = Now().UnixNano() } } func BenchmarkFormat(b *testing.B) { - time := Unix(1265346057, 0) + t := Unix(1265346057, 0) + for i := 0; i < b.N; i++ { + t.Format("Mon Jan 2 15:04:05 2006") + } +} + +func BenchmarkFormatNow(b *testing.B) { + // Like BenchmarkFormat, but easier, because the time zone + // lookup cache is optimized for the present. + t := Now() for i := 0; i < b.N; i++ { - time.Format("Mon Jan 2 15:04:05 2006") + t.Format("Mon Jan 2 15:04:05 2006") } } |