aboutsummaryrefslogtreecommitdiff
path: root/src/time
diff options
context:
space:
mode:
Diffstat (limited to 'src/time')
-rw-r--r--src/time/format.go52
-rw-r--r--src/time/format_test.go20
-rw-r--r--src/time/internal_test.go2
-rw-r--r--src/time/sleep_test.go67
-rw-r--r--src/time/time.go31
-rw-r--r--src/time/time_test.go32
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 {