Skip to content

Commit e79c297

Browse files
korzhaoianlancetaylor
authored andcommitted
[release-branch.go1.17] time: propagate "," separator for fractional seconds into Format
In CL 300996 that fixed issue #6189, we made Parse recognize "," as a separator for fractional seconds. However, we didn't modify Format to propagate the separator verbatim from Parse. Without this change, we break prior functionality that relied on a comma being used in Format. For #48037 Fixes #48177 Change-Id: I6565a25e8657ca3747a58b25acba58f27cdcddc0 Reviewed-on: https://go-review.googlesource.com/c/go/+/345438 Run-TryBot: Ian Lance Taylor <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Ian Lance Taylor <[email protected]> Reviewed-by: Emmanuel Odeke <[email protected]> Trust: Cherry Mui <[email protected]> (cherry picked from commit e1c3f21) Reviewed-on: https://go-review.googlesource.com/c/go/+/350149 Trust: Ian Lance Taylor <[email protected]>
1 parent 21a4e67 commit e79c297

File tree

2 files changed

+59
-11
lines changed

2 files changed

+59
-11
lines changed

src/time/format.go

+39-11
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,11 @@ const (
146146
stdFracSecond0 // ".0", ".00", ... , trailing zeros included
147147
stdFracSecond9 // ".9", ".99", ..., trailing zeros omitted
148148

149-
stdNeedDate = 1 << 8 // need month, day, year
150-
stdNeedClock = 2 << 8 // need hour, minute, second
151-
stdArgShift = 16 // extra argument in high bits, above low stdArgShift
152-
stdMask = 1<<stdArgShift - 1 // mask out argument
149+
stdNeedDate = 1 << 8 // need month, day, year
150+
stdNeedClock = 2 << 8 // need hour, minute, second
151+
stdArgShift = 16 // extra argument in high bits, above low stdArgShift
152+
stdSeparatorShift = 28 // extra argument in high 4 bits for fractional second separators
153+
stdMask = 1<<stdArgShift - 1 // mask out argument
153154
)
154155

155156
// std0x records the std values for "01", "02", ..., "06".
@@ -289,11 +290,11 @@ func nextStdChunk(layout string) (prefix string, std int, suffix string) {
289290
}
290291
// String of digits must end here - only fractional second is all digits.
291292
if !isDigit(layout, j) {
292-
std := stdFracSecond0
293+
code := stdFracSecond0
293294
if layout[i+1] == '9' {
294-
std = stdFracSecond9
295+
code = stdFracSecond9
295296
}
296-
std |= (j - (i + 1)) << stdArgShift
297+
std := stdFracSecond(code, j-(i+1), c)
297298
return layout[0:i], std, layout[j:]
298299
}
299300
}
@@ -430,9 +431,36 @@ func atoi(s string) (x int, err error) {
430431
return x, nil
431432
}
432433

434+
// The "std" value passed to formatNano contains two packed fields: the number of
435+
// digits after the decimal and the separator character (period or comma).
436+
// These functions pack and unpack that variable.
437+
func stdFracSecond(code, n, c int) int {
438+
// Use 0xfff to make the failure case even more absurd.
439+
if c == '.' {
440+
return code | ((n & 0xfff) << stdArgShift)
441+
}
442+
return code | ((n & 0xfff) << stdArgShift) | 1<<stdSeparatorShift
443+
}
444+
445+
func digitsLen(std int) int {
446+
return (std >> stdArgShift) & 0xfff
447+
}
448+
449+
func separator(std int) byte {
450+
if (std >> stdSeparatorShift) == 0 {
451+
return '.'
452+
}
453+
return ','
454+
}
455+
433456
// formatNano appends a fractional second, as nanoseconds, to b
434457
// and returns the result.
435-
func formatNano(b []byte, nanosec uint, n int, trim bool) []byte {
458+
func formatNano(b []byte, nanosec uint, std int) []byte {
459+
var (
460+
n = digitsLen(std)
461+
separator = separator(std)
462+
trim = std&stdMask == stdFracSecond9
463+
)
436464
u := nanosec
437465
var buf [9]byte
438466
for start := len(buf); start > 0; {
@@ -452,7 +480,7 @@ func formatNano(b []byte, nanosec uint, n int, trim bool) []byte {
452480
return b
453481
}
454482
}
455-
b = append(b, '.')
483+
b = append(b, separator)
456484
return append(b, buf[:n]...)
457485
}
458486

@@ -732,7 +760,7 @@ func (t Time) AppendFormat(b []byte, layout string) []byte {
732760
b = appendInt(b, zone/60, 2)
733761
b = appendInt(b, zone%60, 2)
734762
case stdFracSecond0, stdFracSecond9:
735-
b = formatNano(b, uint(t.Nanosecond()), std>>stdArgShift, std&stdMask == stdFracSecond9)
763+
b = formatNano(b, uint(t.Nanosecond()), std)
736764
}
737765
}
738766
return b
@@ -1164,7 +1192,7 @@ func parse(layout, value string, defaultLocation, local *Location) (Time, error)
11641192
case stdFracSecond0:
11651193
// stdFracSecond0 requires the exact number of digits as specified in
11661194
// the layout.
1167-
ndigit := 1 + (std >> stdArgShift)
1195+
ndigit := 1 + digitsLen(std)
11681196
if len(value) < ndigit {
11691197
err = errBad
11701198
break

src/time/format_test.go

+20
Original file line numberDiff line numberDiff line change
@@ -832,3 +832,23 @@ func TestQuote(t *testing.T) {
832832
}
833833

834834
}
835+
836+
// Issue 48037
837+
func TestFormatFractionalSecondSeparators(t *testing.T) {
838+
tests := []struct {
839+
s, want string
840+
}{
841+
{`15:04:05.000`, `21:00:57.012`},
842+
{`15:04:05.999`, `21:00:57.012`},
843+
{`15:04:05,000`, `21:00:57,012`},
844+
{`15:04:05,999`, `21:00:57,012`},
845+
}
846+
847+
// The numeric time represents Thu Feb 4 21:00:57.012345600 PST 2009
848+
time := Unix(0, 1233810057012345600)
849+
for _, tt := range tests {
850+
if q := time.Format(tt.s); q != tt.want {
851+
t.Errorf("Format(%q) = got %q, want %q", tt.s, q, tt.want)
852+
}
853+
}
854+
}

0 commit comments

Comments
 (0)