time: allow the input to have fractional seconds even if

the format string does not specify its presence.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/4839059
This commit is contained in:
Rob Pike 2011-08-10 23:24:45 +10:00
parent 9c774c3f26
commit c9cf04a9f6
2 changed files with 65 additions and 25 deletions

View file

@ -26,8 +26,11 @@ const (
// replaced by a digit if the following number (a day) has two digits; for
// compatibility with fixed-width Unix time formats.
//
// A decimal point followed by one or more zeros represents a
// fractional second.
// A decimal point followed by one or more zeros represents a fractional
// second. When parsing (only), the input may contain a fractional second
// field immediately after the seconds field, even if the layout does not
// signify its presence. In that case a decimal point followed by a maximal
// series of digits is parsed as a fractional second.
//
// Numeric time zone offsets format as follows:
// -0700 ±hhmm
@ -169,7 +172,7 @@ func nextStdChunk(layout string) (prefix, std, suffix string) {
numZeros++
}
// String of digits must end here - only fractional second is all zeros.
if numZeros > 0 && (j >= len(layout) || layout[j] < '0' || '9' < layout[j]) {
if numZeros > 0 && !isDigit(layout, j) {
return layout[0:i], layout[i : i+1+numZeros], layout[i+1+numZeros:]
}
}
@ -416,14 +419,24 @@ func (e *ParseError) String() string {
strconv.Quote(e.Value) + e.Message
}
// isDigit returns true if s[i] is a decimal digit, false if not or
// if s[i] is out of range.
func isDigit(s string, i int) bool {
if len(s) <= i {
return false
}
c := s[i]
return '0' <= c && c <= '9'
}
// getnum parses s[0:1] or s[0:2] (fixed forces the latter)
// as a decimal integer and returns the integer and the
// remainder of the string.
func getnum(s string, fixed bool) (int, string, os.Error) {
if len(s) == 0 || s[0] < '0' || s[0] > '9' {
if !isDigit(s, 0) {
return 0, s, errBad
}
if len(s) == 1 || s[1] < '0' || s[1] > '9' {
if !isDigit(s, 1) {
if fixed {
return 0, s, errBad
}
@ -509,7 +522,7 @@ func Parse(alayout, avalue string) (*Time, os.Error) {
t.Year += 2000
}
case stdLongYear:
if len(value) < 4 || value[0] < '0' || value[0] > '9' {
if len(value) < 4 || !isDigit(value, 0) {
err = errBad
break
}
@ -557,6 +570,21 @@ func Parse(alayout, avalue string) (*Time, os.Error) {
if t.Second < 0 || 60 <= t.Second {
rangeErrString = "second"
}
// Special case: do we have a fractional second but no
// fractional second in the format?
if len(value) > 2 && value[0] == '.' && isDigit(value, 1) {
_, std, _ := nextStdChunk(layout)
if len(std) > 0 && std[0] == '.' && isDigit(std, 1) {
// Fractional second in the layout; proceed normally
break
}
// No fractional second in the layout but we have one in the input.
n := 2
for ; n < len(value) && isDigit(value, n); n++ {
}
rangeErrString, err = t.parseNanoseconds(value, n)
value = value[n:]
}
case stdISO8601TZ, stdISO8601ColonTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ:
if std[0] == 'Z' && len(value) >= 1 && value[0] == 'Z' {
value = value[1:]
@ -663,26 +691,8 @@ func Parse(alayout, avalue string) (*Time, os.Error) {
break
}
if len(std) >= 2 && std[0:2] == ".0" {
if value[0] != '.' {
err = errBad
break
}
t.Nanosecond, err = strconv.Atoi(value[1:len(std)])
if err != nil {
break
}
if t.Nanosecond < 0 || t.Nanosecond >= 1e9 {
rangeErrString = "fractional second"
break
}
rangeErrString, err = t.parseNanoseconds(value, len(std))
value = value[len(std):]
// We need nanoseconds, which means scaling by the number
// of missing digits in the format, maximum length 10. If it's
// longer than 10, we won't scale.
scaleDigits := 10 - len(std)
for i := 0; i < scaleDigits; i++ {
t.Nanosecond *= 10
}
}
}
if rangeErrString != "" {
@ -699,3 +709,26 @@ func Parse(alayout, avalue string) (*Time, os.Error) {
}
return &t, nil
}
func (t *Time) parseNanoseconds(value string, nbytes int) (rangErrString string, err os.Error) {
if value[0] != '.' {
return "", errBad
}
var ns int
ns, err = strconv.Atoi(value[1:nbytes])
if err != nil {
return "", err
}
if ns < 0 || 1e9 <= ns {
return "fractional second", nil
}
// We need nanoseconds, which means scaling by the number
// of missing digits in the format, maximum length 10. If it's
// longer than 10, we won't scale.
scaleDigits := 10 - nbytes
for i := 0; i < scaleDigits; i++ {
ns *= 10
}
t.Nanosecond = ns
return
}

View file

@ -250,6 +250,13 @@ var parseTests = []ParseTest{
{"RFC1123", RFC1123, "Thu, 04 Feb 2010 21:00:57 PST", true, true, 1, 0},
{"RFC3339", RFC3339, "2010-02-04T21:00:57-08:00", true, false, 1, 0},
{"custom: \"2006-01-02 15:04:05-07\"", "2006-01-02 15:04:05-07", "2010-02-04 21:00:57-08", true, false, 1, 0},
// Optional fractional seconds.
{"ANSIC", ANSIC, "Thu Feb 4 21:00:57.0 2010", false, true, 1, 1},
{"UnixDate", UnixDate, "Thu Feb 4 21:00:57.01 PST 2010", true, true, 1, 2},
{"RubyDate", RubyDate, "Thu Feb 04 21:00:57.012 -0800 2010", true, true, 1, 3},
{"RFC850", RFC850, "Thursday, 04-Feb-10 21:00:57.0123 PST", true, true, 1, 4},
{"RFC1123", RFC1123, "Thu, 04 Feb 2010 21:00:57.01234 PST", true, true, 1, 5},
{"RFC3339", RFC3339, "2010-02-04T21:00:57.012345678-08:00", true, false, 1, 9},
// Amount of white space should not matter.
{"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1, 0},
{"ANSIC", ANSIC, "Thu Feb 4 21:00:57 2010", false, true, 1, 0},