time: move RFC 3339 optimizations to separate file

The optimizations were added in CL 425197 and CL 421877.
Move this functionality to a separate file to keep format.go smaller
and to document the justification for why this optimization exists.

Change-Id: I1e4e1ace19f9d596d8c0cf49ab6062f63a87b5bf
Reviewed-on: https://go-review.googlesource.com/c/go/+/430675
Run-TryBot: Joseph Tsai <joetsai@digital-static.net>
Auto-Submit: Jenny Rakoczy <jenny@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Rob Pike <r@golang.org>
Reviewed-by: Jenny Rakoczy <jenny@golang.org>
Reviewed-by: Ian Lance Taylor <iant@google.com>
Run-TryBot: Jenny Rakoczy <jenny@golang.org>
Auto-Submit: Joseph Tsai <joetsai@digital-static.net>
This commit is contained in:
Joe Tsai 2022-09-13 10:54:07 -07:00 committed by Gopher Robot
parent f15582c477
commit e7a2014fac
2 changed files with 131 additions and 117 deletions

View file

@ -797,50 +797,6 @@ func (t Time) appendFormat(b []byte, layout string) []byte {
return b
}
func (t Time) appendFormatRFC3339(b []byte, nanos bool) []byte {
_, offset, abs := t.locabs()
// Format date.
year, month, day, _ := absDate(abs, true)
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 := absClock(abs)
b = appendInt(b, hour, 2)
b = append(b, ':')
b = appendInt(b, min, 2)
b = append(b, ':')
b = appendInt(b, sec, 2)
if nanos {
std := stdFracSecond(stdFracSecond9, 9, '.')
b = formatNano(b, uint(t.Nanosecond()), std)
}
if offset == 0 {
return append(b, 'Z')
}
// Format zone.
zone := offset / 60 // convert to minutes
if zone < 0 {
b = append(b, '-')
zone = -zone
} else {
b = append(b, '+')
}
b = appendInt(b, zone/60, 2)
b = append(b, ':')
b = appendInt(b, zone%60, 2)
return b
}
var errBad = errors.New("bad value for field") // placeholder not passed to user
// ParseError describes a problem parsing a time string.
@ -1043,79 +999,6 @@ func ParseInLocation(layout, value string, loc *Location) (Time, error) {
return parse(layout, value, loc, loc)
}
func parseRFC3339(s string, local *Location) (Time, bool) {
// parseUint parses s as an unsigned decimal integer and
// verifies that it is within some range.
// If it is invalid or out-of-range,
// it sets ok to false and returns the min value.
ok := true
parseUint := func(s string, min, max int) (x int) {
for _, c := range []byte(s) {
if c < '0' || '9' < c {
ok = false
return min
}
x = x*10 + int(c) - '0'
}
if x < min || max < x {
ok = false
return min
}
return x
}
// Parse the date and time.
if len(s) < len("2006-01-02T15:04:05") {
return Time{}, false
}
year := parseUint(s[0:4], 0, 9999) // e.g., 2006
month := parseUint(s[5:7], 1, 12) // e.g., 01
day := parseUint(s[8:10], 1, daysIn(Month(month), year)) // e.g., 02
hour := parseUint(s[11:13], 0, 23) // e.g., 15
min := parseUint(s[14:16], 0, 59) // e.g., 04
sec := parseUint(s[17:19], 0, 59) // e.g., 05
if !ok || !(s[4] == '-' && s[7] == '-' && s[10] == 'T' && s[13] == ':' && s[16] == ':') {
return Time{}, false
}
s = s[19:]
// Parse the fractional second.
var nsec int
if len(s) >= 2 && s[0] == '.' && isDigit(s, 1) {
n := 2
for ; n < len(s) && isDigit(s, n); n++ {
}
nsec, _, _ = parseNanoseconds(s, n)
s = s[n:]
}
// Parse the time zone.
t := Date(year, Month(month), day, hour, min, sec, nsec, UTC)
if s != "Z" {
if len(s) != len("-07:00") {
return Time{}, false
}
hr := parseUint(s[1:3], 0, 23) // e.g., 07
mm := parseUint(s[4:6], 0, 59) // e.g., 00
if !ok || !((s[0] == '-' || s[0] == '+') && s[3] == ':') {
return Time{}, false
}
zoneOffset := (hr*60 + mm) * 60
if s[0] == '-' {
zoneOffset *= -1
}
t.addSec(-int64(zoneOffset))
// Use local zone with the given offset if possible.
if _, offset, _, _, _ := local.lookup(t.unixSec()); offset == zoneOffset {
t.setLoc(local)
} else {
t.setLoc(FixedZone("", zoneOffset))
}
}
return t, true
}
func parse(layout, value string, defaultLocation, local *Location) (Time, error) {
alayout, avalue := layout, value
rangeErrString := "" // set if a value is out of range

131
src/time/format_rfc3339.go Normal file
View file

@ -0,0 +1,131 @@
// Copyright 2022 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package time
// RFC 3339 is the most commonly used format.
//
// It is implicitly used by the Time.(Marshal|Unmarshal)(Text|JSON) methods.
// Also, according to analysis on https://go.dev/issue/52746,
// RFC 3339 accounts for 57% of all explicitly specified time formats,
// with the second most popular format only being used 8% of the time.
// The overwhelming use of RFC 3339 compared to all other formats justifies
// the addition of logic to optimize formatting and parsing.
func (t Time) appendFormatRFC3339(b []byte, nanos bool) []byte {
_, offset, abs := t.locabs()
// Format date.
year, month, day, _ := absDate(abs, true)
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 := absClock(abs)
b = appendInt(b, hour, 2)
b = append(b, ':')
b = appendInt(b, min, 2)
b = append(b, ':')
b = appendInt(b, sec, 2)
if nanos {
std := stdFracSecond(stdFracSecond9, 9, '.')
b = formatNano(b, uint(t.Nanosecond()), std)
}
if offset == 0 {
return append(b, 'Z')
}
// Format zone.
zone := offset / 60 // convert to minutes
if zone < 0 {
b = append(b, '-')
zone = -zone
} else {
b = append(b, '+')
}
b = appendInt(b, zone/60, 2)
b = append(b, ':')
b = appendInt(b, zone%60, 2)
return b
}
func parseRFC3339(s string, local *Location) (Time, bool) {
// parseUint parses s as an unsigned decimal integer and
// verifies that it is within some range.
// If it is invalid or out-of-range,
// it sets ok to false and returns the min value.
ok := true
parseUint := func(s string, min, max int) (x int) {
for _, c := range []byte(s) {
if c < '0' || '9' < c {
ok = false
return min
}
x = x*10 + int(c) - '0'
}
if x < min || max < x {
ok = false
return min
}
return x
}
// Parse the date and time.
if len(s) < len("2006-01-02T15:04:05") {
return Time{}, false
}
year := parseUint(s[0:4], 0, 9999) // e.g., 2006
month := parseUint(s[5:7], 1, 12) // e.g., 01
day := parseUint(s[8:10], 1, daysIn(Month(month), year)) // e.g., 02
hour := parseUint(s[11:13], 0, 23) // e.g., 15
min := parseUint(s[14:16], 0, 59) // e.g., 04
sec := parseUint(s[17:19], 0, 59) // e.g., 05
if !ok || !(s[4] == '-' && s[7] == '-' && s[10] == 'T' && s[13] == ':' && s[16] == ':') {
return Time{}, false
}
s = s[19:]
// Parse the fractional second.
var nsec int
if len(s) >= 2 && s[0] == '.' && isDigit(s, 1) {
n := 2
for ; n < len(s) && isDigit(s, n); n++ {
}
nsec, _, _ = parseNanoseconds(s, n)
s = s[n:]
}
// Parse the time zone.
t := Date(year, Month(month), day, hour, min, sec, nsec, UTC)
if s != "Z" {
if len(s) != len("-07:00") {
return Time{}, false
}
hr := parseUint(s[1:3], 0, 23) // e.g., 07
mm := parseUint(s[4:6], 0, 59) // e.g., 00
if !ok || !((s[0] == '-' || s[0] == '+') && s[3] == ':') {
return Time{}, false
}
zoneOffset := (hr*60 + mm) * 60
if s[0] == '-' {
zoneOffset *= -1
}
t.addSec(-int64(zoneOffset))
// Use local zone with the given offset if possible.
if _, offset, _, _, _ := local.lookup(t.unixSec()); offset == zoneOffset {
t.setLoc(local)
} else {
t.setLoc(FixedZone("", zoneOffset))
}
}
return t, true
}