diff options
Diffstat (limited to 'src/time')
-rw-r--r-- | src/time/format.go | 52 | ||||
-rw-r--r-- | src/time/format_test.go | 20 | ||||
-rw-r--r-- | src/time/internal_test.go | 2 | ||||
-rw-r--r-- | src/time/sleep_test.go | 67 | ||||
-rw-r--r-- | src/time/time.go | 31 | ||||
-rw-r--r-- | src/time/time_test.go | 32 |
6 files changed, 183 insertions, 21 deletions
diff --git a/src/time/format.go b/src/time/format.go index 7ae89c557d..7373892b97 100644 --- a/src/time/format.go +++ b/src/time/format.go @@ -74,7 +74,7 @@ import "errors" // for compatibility with fixed-width Unix time formats. A leading zero represents // a zero-padded value. // -// The formats and 002 are space-padded and zero-padded +// The formats __2 and 002 are space-padded and zero-padded // three-character day of year; there is no unpadded day of year format. // // A comma or decimal point followed by one or more zeros represents @@ -146,10 +146,11 @@ const ( 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 - stdArgShift = 16 // extra argument in high bits, above low stdArgShift - stdMask = 1<<stdArgShift - 1 // mask out argument + stdNeedDate = 1 << 8 // need month, day, year + stdNeedClock = 2 << 8 // need hour, minute, second + stdArgShift = 16 // extra argument in high bits, above low stdArgShift + stdSeparatorShift = 28 // extra argument in high 4 bits for fractional second separators + stdMask = 1<<stdArgShift - 1 // mask out argument ) // std0x records the std values for "01", "02", ..., "06". @@ -289,11 +290,11 @@ func nextStdChunk(layout string) (prefix string, std int, suffix string) { } // String of digits must end here - only fractional second is all digits. if !isDigit(layout, j) { - std := stdFracSecond0 + code := stdFracSecond0 if layout[i+1] == '9' { - std = stdFracSecond9 + code = stdFracSecond9 } - std |= (j - (i + 1)) << stdArgShift + std := stdFracSecond(code, j-(i+1), c) return layout[0:i], std, layout[j:] } } @@ -430,9 +431,36 @@ func atoi(s string) (x int, err error) { return x, nil } +// The "std" value passed to formatNano contains two packed fields: the number of +// digits after the decimal and the separator character (period or comma). +// These functions pack and unpack that variable. +func stdFracSecond(code, n, c int) int { + // Use 0xfff to make the failure case even more absurd. + if c == '.' { + return code | ((n & 0xfff) << stdArgShift) + } + return code | ((n & 0xfff) << stdArgShift) | 1<<stdSeparatorShift +} + +func digitsLen(std int) int { + return (std >> stdArgShift) & 0xfff +} + +func separator(std int) byte { + if (std >> stdSeparatorShift) == 0 { + return '.' + } + return ',' +} + // formatNano appends a fractional second, as nanoseconds, to b // and returns the result. -func formatNano(b []byte, nanosec uint, n int, trim bool) []byte { +func formatNano(b []byte, nanosec uint, std int) []byte { + var ( + n = digitsLen(std) + separator = separator(std) + trim = std&stdMask == stdFracSecond9 + ) u := nanosec var buf [9]byte for start := len(buf); start > 0; { @@ -452,7 +480,7 @@ func formatNano(b []byte, nanosec uint, n int, trim bool) []byte { return b } } - b = append(b, '.') + b = append(b, separator) return append(b, buf[:n]...) } @@ -733,7 +761,7 @@ func (t Time) AppendFormat(b []byte, layout string) []byte { b = appendInt(b, zone/60, 2) b = appendInt(b, zone%60, 2) case stdFracSecond0, stdFracSecond9: - b = formatNano(b, uint(t.Nanosecond()), std>>stdArgShift, std&stdMask == stdFracSecond9) + b = formatNano(b, uint(t.Nanosecond()), std) } } return b @@ -1165,7 +1193,7 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error) case stdFracSecond0: // stdFracSecond0 requires the exact number of digits as specified in // the layout. - ndigit := 1 + (std >> stdArgShift) + ndigit := 1 + digitsLen(std) if len(value) < ndigit { err = errBad break diff --git a/src/time/format_test.go b/src/time/format_test.go index 1af41e2dfb..93cbcf9401 100644 --- a/src/time/format_test.go +++ b/src/time/format_test.go @@ -832,3 +832,23 @@ func TestQuote(t *testing.T) { } } + +// Issue 48037 +func TestFormatFractionalSecondSeparators(t *testing.T) { + tests := []struct { + s, want string + }{ + {`15:04:05.000`, `21:00:57.012`}, + {`15:04:05.999`, `21:00:57.012`}, + {`15:04:05,000`, `21:00:57,012`}, + {`15:04:05,999`, `21:00:57,012`}, + } + + // The numeric time represents Thu Feb 4 21:00:57.012345600 PST 2009 + time := Unix(0, 1233810057012345600) + for _, tt := range tests { + if q := time.Format(tt.s); q != tt.want { + t.Errorf("Format(%q) = got %q, want %q", tt.s, q, tt.want) + } + } +} diff --git a/src/time/internal_test.go b/src/time/internal_test.go index 87a4208b05..2c75e449d3 100644 --- a/src/time/internal_test.go +++ b/src/time/internal_test.go @@ -12,7 +12,7 @@ func init() { func initTestingZone() { z, err := loadLocation("America/Los_Angeles", zoneSources[len(zoneSources)-1:]) if err != nil { - panic("cannot load America/Los_Angeles for testing: " + err.Error()) + panic("cannot load America/Los_Angeles for testing: " + err.Error() + "; you may want to use -tags=timetzdata") } z.name = "Local" localLoc = *z diff --git a/src/time/sleep_test.go b/src/time/sleep_test.go index e0172bf5e0..c48e704eb7 100644 --- a/src/time/sleep_test.go +++ b/src/time/sleep_test.go @@ -7,6 +7,7 @@ package time_test import ( "errors" "fmt" + "math/rand" "runtime" "strings" "sync" @@ -561,6 +562,72 @@ func TestTimerModifiedEarlier(t *testing.T) { } } +// Test that rapidly moving timers earlier and later doesn't cause +// some of the sleep times to be lost. +// Issue 47762 +func TestAdjustTimers(t *testing.T) { + var rnd = rand.New(rand.NewSource(Now().UnixNano())) + + timers := make([]*Timer, 100) + states := make([]int, len(timers)) + indices := rnd.Perm(len(timers)) + + for len(indices) != 0 { + var ii = rnd.Intn(len(indices)) + var i = indices[ii] + + var timer = timers[i] + var state = states[i] + states[i]++ + + switch state { + case 0: + timers[i] = NewTimer(0) + case 1: + <-timer.C // Timer is now idle. + + // Reset to various long durations, which we'll cancel. + case 2: + if timer.Reset(1 * Minute) { + panic("shouldn't be active (1)") + } + case 4: + if timer.Reset(3 * Minute) { + panic("shouldn't be active (3)") + } + case 6: + if timer.Reset(2 * Minute) { + panic("shouldn't be active (2)") + } + + // Stop and drain a long-duration timer. + case 3, 5, 7: + if !timer.Stop() { + t.Logf("timer %d state %d Stop returned false", i, state) + <-timer.C + } + + // Start a short-duration timer we expect to select without blocking. + case 8: + if timer.Reset(0) { + t.Fatal("timer.Reset returned true") + } + case 9: + now := Now() + <-timer.C + dur := Since(now) + if dur > 750*Millisecond { + t.Errorf("timer %d took %v to complete", i, dur) + } + + // Timer is done. Swap with tail and remove. + case 10: + indices[ii] = indices[len(indices)-1] + indices = indices[:len(indices)-1] + } + } +} + // Benchmark timer latency when the thread that creates the timer is busy with // other work and the timers must be serviced by other threads. // https://golang.org/issue/38860 diff --git a/src/time/time.go b/src/time/time.go index 4ecc3d82dc..edf0c62610 100644 --- a/src/time/time.go +++ b/src/time/time.go @@ -425,7 +425,6 @@ const ( internalToUnix int64 = -unixToInternal wallToInternal int64 = (1884*365 + 1884/4 - 1884/100 + 1884/400) * secondsPerDay - internalToWall int64 = -wallToInternal ) // IsZero reports whether t represents the zero time instant, @@ -1163,19 +1162,26 @@ func (t Time) UnixNano() int64 { return (t.unixSec())*1e9 + int64(t.nsec()) } -const timeBinaryVersion byte = 1 +const ( + timeBinaryVersionV1 byte = iota + 1 // For general situation + timeBinaryVersionV2 // For LMT only +) // MarshalBinary implements the encoding.BinaryMarshaler interface. func (t Time) MarshalBinary() ([]byte, error) { var offsetMin int16 // minutes east of UTC. -1 is UTC. + var offsetSec int8 + version := timeBinaryVersionV1 if t.Location() == UTC { offsetMin = -1 } else { _, offset := t.Zone() if offset%60 != 0 { - return nil, errors.New("Time.MarshalBinary: zone offset has fractional minute") + version = timeBinaryVersionV2 + offsetSec = int8(offset % 60) } + offset /= 60 if offset < -32768 || offset == -1 || offset > 32767 { return nil, errors.New("Time.MarshalBinary: unexpected zone offset") @@ -1186,8 +1192,8 @@ func (t Time) MarshalBinary() ([]byte, error) { sec := t.sec() nsec := t.nsec() enc := []byte{ - timeBinaryVersion, // byte 0 : version - byte(sec >> 56), // bytes 1-8: seconds + version, // byte 0 : version + byte(sec >> 56), // bytes 1-8: seconds byte(sec >> 48), byte(sec >> 40), byte(sec >> 32), @@ -1202,6 +1208,9 @@ func (t Time) MarshalBinary() ([]byte, error) { byte(offsetMin >> 8), // bytes 13-14: zone offset in minutes byte(offsetMin), } + if version == timeBinaryVersionV2 { + enc = append(enc, byte(offsetSec)) + } return enc, nil } @@ -1213,11 +1222,16 @@ func (t *Time) UnmarshalBinary(data []byte) error { return errors.New("Time.UnmarshalBinary: no data") } - if buf[0] != timeBinaryVersion { + version := buf[0] + if version != timeBinaryVersionV1 && version != timeBinaryVersionV2 { return errors.New("Time.UnmarshalBinary: unsupported version") } - if len(buf) != /*version*/ 1+ /*sec*/ 8+ /*nsec*/ 4+ /*zone offset*/ 2 { + wantLen := /*version*/ 1 + /*sec*/ 8 + /*nsec*/ 4 + /*zone offset*/ 2 + if version == timeBinaryVersionV2 { + wantLen++ + } + if len(buf) != wantLen { return errors.New("Time.UnmarshalBinary: invalid length") } @@ -1230,6 +1244,9 @@ func (t *Time) UnmarshalBinary(data []byte) error { buf = buf[4:] offset := int(int16(buf[1])|int16(buf[0])<<8) * 60 + if version == timeBinaryVersionV2 { + offset += int(buf[2]) + } *t = Time{} t.wall = uint64(nsec) diff --git a/src/time/time_test.go b/src/time/time_test.go index cea5f2d3f5..e2fb897b6d 100644 --- a/src/time/time_test.go +++ b/src/time/time_test.go @@ -767,7 +767,6 @@ var notEncodableTimes = []struct { time Time want string }{ - {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"}, @@ -1437,6 +1436,37 @@ func TestMarshalBinaryZeroTime(t *testing.T) { } } +func TestMarshalBinaryVersion2(t *testing.T) { + t0, err := Parse(RFC3339, "1880-01-01T00:00:00Z") + if err != nil { + t.Errorf("Failed to parse time, error = %v", err) + } + loc, err := LoadLocation("US/Eastern") + if err != nil { + t.Errorf("Failed to load location, error = %v", err) + } + t1 := t0.In(loc) + b, err := t1.MarshalBinary() + if err != nil { + t.Errorf("Failed to Marshal, error = %v", err) + } + + t2 := Time{} + err = t2.UnmarshalBinary(b) + if err != nil { + t.Errorf("Failed to Unmarshal, error = %v", err) + } + + if !(t0.Equal(t1) && t1.Equal(t2)) { + if !t0.Equal(t1) { + t.Errorf("The result t1: %+v after Marshal is not matched original t0: %+v", t1, t0) + } + if !t1.Equal(t2) { + t.Errorf("The result t2: %+v after Unmarshal is not matched original t1: %+v", t2, t1) + } + } +} + // Issue 17720: Zero value of time.Month fails to print func TestZeroMonthString(t *testing.T) { if got, want := Month(0).String(), "%!Month(0)"; got != want { |