]> Cypherpunks repositories - gostls13.git/commitdiff
time: allow the input to have fractional seconds even if
authorRob Pike <r@golang.org>
Wed, 10 Aug 2011 13:24:45 +0000 (23:24 +1000)
committerRob Pike <r@golang.org>
Wed, 10 Aug 2011 13:24:45 +0000 (23:24 +1000)
the format string does not specify its presence.

R=golang-dev, rsc
CC=golang-dev
https://golang.org/cl/4839059

src/pkg/time/format.go
src/pkg/time/time_test.go

index 3c42f0c2d8da956f2bc340cd346b00d144735bf3..5ddd54812f38cafaddd5e760a9e7c6d307f58245 100644 (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
+}
index 4999f4536ea2829fd8c0259de081329429730a58..dceed491aa0567ad692c90cf20163b1d6dc96cf2 100644 (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},