// 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
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:]
}
}
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
}
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
}
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:]
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 != "" {
}
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
+}
{"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},