diff --git a/src/time/export_test.go b/src/time/export_test.go index a4940d12f91d80..a3fb4717b12aa8 100644 --- a/src/time/export_test.go +++ b/src/time/export_test.go @@ -134,6 +134,7 @@ var StdChunkNames = map[int]string{ var Quote = quote var AppendInt = appendInt +var AppendIntWidth4 = appendIntWidth4 var AppendFormatAny = Time.appendFormat var AppendFormatRFC3339 = Time.appendFormatRFC3339 var ParseAny = parse diff --git a/src/time/format.go b/src/time/format.go index ad5486f4d28f89..c40a2c6f72af04 100644 --- a/src/time/format.go +++ b/src/time/format.go @@ -464,6 +464,18 @@ func appendInt(b []byte, x int, width int) []byte { return b } +const onesDigit = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789" +const tensDigit = "0000000000111111111122222222223333333333444444444455555555556666666666777777777788888888889999999999" + +// appendIntWidth4 is semantically identical to appendInt(b, x, 4) +// but optimized for 0 ≤ x < 10000. +func appendIntWidth4(b []byte, x int) []byte { + if x < 0 || x >= 1e4 { + return appendInt(b, x, 4) + } + return append(b, tensDigit[x/1e2], onesDigit[x/1e2], tensDigit[x%1e2], onesDigit[x%1e2]) +} + // Never printed, just needs to be non-nil for return by atoi. var errAtoi = errors.New("time: invalid number") diff --git a/src/time/format_rfc3339.go b/src/time/format_rfc3339.go index 05fddfca89f64c..ab12c877aaf1e4 100644 --- a/src/time/format_rfc3339.go +++ b/src/time/format_rfc3339.go @@ -18,23 +18,22 @@ import "errors" func (t Time) appendFormatRFC3339(b []byte, nanos bool) []byte { _, offset, abs := t.locabs() - // Format date. + // Format date and time. year, month, day := abs.days().date() - b = appendInt(b, year, 4) - b = append(b, '-') - b = appendInt(b, int(month), 2) - b = append(b, '-') - b = appendInt(b, day, 2) - - b = append(b, 'T') - - // Format time. hour, min, sec := abs.clock() - b = appendInt(b, hour, 2) - b = append(b, ':') - b = appendInt(b, min, 2) - b = append(b, ':') - b = appendInt(b, sec, 2) + + b = appendIntWidth4(b, year) + b = append(b, '-', + tensDigit[month], onesDigit[month], + '-', + tensDigit[day], onesDigit[day], + 'T', + tensDigit[hour], onesDigit[hour], + ':', + tensDigit[min], onesDigit[min], + ':', + tensDigit[sec], onesDigit[sec], + ) if nanos { std := stdFracSecond(stdFracSecond9, 9, '.') @@ -53,9 +52,19 @@ func (t Time) appendFormatRFC3339(b []byte, nanos bool) []byte { } else { b = append(b, '+') } - b = appendInt(b, zone/60, 2) - b = append(b, ':') - b = appendInt(b, zone%60, 2) + + if zone > 3600 { + b = appendInt(b, zone/60, 2) + b = append(b, ':', + tensDigit[zone%60], onesDigit[zone%60]) + return b + } + + b = append(b, + tensDigit[zone/60], onesDigit[zone/60], + ':', + tensDigit[zone%60], onesDigit[zone%60], + ) return b } diff --git a/src/time/format_test.go b/src/time/format_test.go index 2537c765968ee2..8422f0f356b795 100644 --- a/src/time/format_test.go +++ b/src/time/format_test.go @@ -1091,3 +1091,40 @@ func FuzzParseRFC3339(f *testing.F) { } }) } + +func TestAppendIntWidth(t *testing.T) { + values := []int{0, -1, 1, 10, -10, 99, -99, 9999, -9999, 10001} + for _, v := range values { + want := AppendInt(nil, v, 4) + got := AppendIntWidth4(nil, v) + if !bytes.Equal(got, want) { + t.Errorf("AppendIntWidth4(%d) = %s, want %s", v, got, want) + } + } +} + +func BenchmarkAppendIntWidth4(b *testing.B) { + b.Run("name=AppendInt", func(b *testing.B) { + var buf = make([]byte, 0, 8) + b.ResetTimer() + for b.Loop() { + buf = AppendInt(buf[:0], 360, 4) + } + }) + b.Run("name=AppendIntWidth4", func(b *testing.B) { + var buf = make([]byte, 0, 8) + b.ResetTimer() + for b.Loop() { + buf = AppendIntWidth4(buf[:0], 360) + } + }) +} + +func BenchmarkTimeFormatRFC3339(b *testing.B) { + tm := Unix(1661201140, 676836973) + buf := make([]byte, 0, 64) + b.ReportAllocs() + for b.Loop() { + buf = tm.AppendFormat(buf[:0], RFC3339) + } +}