diff --git a/src/pkg/time/format.go b/src/pkg/time/format.go index 3c42f0c2d8d..5ddd54812f3 100644 --- a/src/pkg/time/format.go +++ b/src/pkg/time/format.go @@ -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 +} diff --git a/src/pkg/time/time_test.go b/src/pkg/time/time_test.go index 4999f4536ea..dceed491aa0 100644 --- a/src/pkg/time/time_test.go +++ b/src/pkg/time/time_test.go @@ -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},