return marshalNano(enc, va, mo)
} else {
// TODO(https://go.dev/issue/71631): Decide on default duration representation.
- return newMarshalErrorBefore(enc, t, errors.New("no default representation; specify an explicit format"))
+ return newMarshalErrorBefore(enc, t, errors.New("no default representation (see https://go.dev/issue/71631); specify an explicit format"))
}
// TODO(https://go.dev/issue/62121): Use reflect.Value.AssertTo.
return unmarshalNano(dec, va, uo)
} else {
// TODO(https://go.dev/issue/71631): Decide on default duration representation.
- return newUnmarshalErrorBeforeWithSkipping(dec, uo, t, errors.New("no default representation; specify an explicit format"))
+ return newUnmarshalErrorBeforeWithSkipping(dec, uo, t, errors.New("no default representation (see https://go.dev/issue/71631); specify an explicit format"))
}
stringify := !u.isNumeric() || xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers)
// - 0 uses time.Duration.String
// - 1e0, 1e3, 1e6, or 1e9 use a decimal encoding of the duration as
// nanoseconds, microseconds, milliseconds, or seconds.
+ // - 8601 uses ISO 8601
base uint64
}
a.base = 1e3
case "nano":
a.base = 1e0
+ case "iso8601":
+ a.base = 8601
default:
return false
}
}
func (a *durationArshaler) isNumeric() bool {
- return a.base != 0 && a.base != 60
+ return a.base != 0 && a.base != 8601
}
func (a *durationArshaler) appendMarshal(b []byte) ([]byte, error) {
switch a.base {
case 0:
return append(b, a.td.String()...), nil
+ case 8601:
+ return appendDurationISO8601(b, a.td), nil
default:
return appendDurationBase10(b, a.td, a.base), nil
}
switch a.base {
case 0:
a.td, err = time.ParseDuration(string(b))
+ case 8601:
+ a.td, err = parseDurationISO8601(b)
default:
a.td, err = parseDurationBase10(b, a.base)
}
// parseDurationBase10 parses d from a decimal fractional number,
// where pow10 is a power-of-10 used to scale up the number.
func parseDurationBase10(b []byte, pow10 uint64) (time.Duration, error) {
- suffix, neg := consumeSign(b) // consume sign
+ suffix, neg := consumeSign(b, false) // consume sign
wholeBytes, fracBytes := bytesCutByte(suffix, '.', true) // consume whole and frac fields
whole, okWhole := jsonwire.ParseUint(wholeBytes) // parse whole field; may overflow
frac, okFrac := parseFracBase10(fracBytes, pow10) // parse frac field
}
}
+// appendDurationISO8601 appends an ISO 8601 duration with a restricted grammar,
+// where leading and trailing zeroes and zero-value designators are omitted.
+// It only uses hour, minute, and second designators since ISO 8601 defines
+// those as being "accurate", while year, month, week, and day are "nominal".
+func appendDurationISO8601(b []byte, d time.Duration) []byte {
+ if d == 0 {
+ return append(b, "PT0S"...)
+ }
+ b, n := mayAppendDurationSign(b, d)
+ b = append(b, "PT"...)
+ n, nsec := bits.Div64(0, n, 1e9) // compute nsec field
+ n, sec := bits.Div64(0, n, 60) // compute sec field
+ hour, min := bits.Div64(0, n, 60) // compute hour and min fields
+ if hour > 0 {
+ b = append(strconv.AppendUint(b, hour, 10), 'H')
+ }
+ if min > 0 {
+ b = append(strconv.AppendUint(b, min, 10), 'M')
+ }
+ if sec > 0 || nsec > 0 {
+ b = append(appendFracBase10(strconv.AppendUint(b, sec, 10), nsec, 1e9), 'S')
+ }
+ return b
+}
+
+// daysPerYear is the exact average number of days in a year according to
+// the Gregorian calender, which has an extra day each year that is
+// a multiple of 4, unless it is evenly divisible by 100 but not by 400.
+// This does not take into account leap seconds, which are not deterministic.
+const daysPerYear = 365.2425
+
+var errInaccurateDateUnits = errors.New("inaccurate year, month, week, or day units")
+
+// parseDurationISO8601 parses a duration according to ISO 8601-1:2019,
+// section 5.5.2.2 and 5.5.2.3 with the following restrictions or extensions:
+//
+// - A leading minus sign is permitted for negative duration according
+// to ISO 8601-2:2019, section 4.4.1.9. We do not permit negative values
+// for each "time scale component", which is permitted by section 4.4.1.1,
+// but rarely supported by parsers.
+//
+// - A leading plus sign is permitted (and ignored).
+// This is not required by ISO 8601, but not forbidden either.
+// There is some precedent for this as it is supported by the principle of
+// duration arithmetic as specified in ISO 8601-2-2019, section 14.1.
+// Of note, the JavaScript grammar for ISO 8601 permits a leading plus sign.
+//
+// - A fractional value is only permitted for accurate units
+// (i.e., hour, minute, and seconds) in the last time component,
+// which is permissible by ISO 8601-1:2019, section 5.5.2.3.
+//
+// - Both periods ('.') and commas (',') are supported as the separator
+// between the integer part and fraction part of a number,
+// as specified in ISO 8601-1:2019, section 3.2.6.
+// While ISO 8601 recommends comma as the default separator,
+// most formatters uses a period.
+//
+// - Leading zeros are ignored. This is not required by ISO 8601,
+// but also not forbidden by the standard. Many parsers support this.
+//
+// - Lowercase designators are supported. This is not required by ISO 8601,
+// but also not forbidden by the standard. Many parsers support this.
+//
+// If the nominal units of year, month, week, or day are present,
+// this produces a best-effort value and also reports [errInaccurateDateUnits].
+//
+// The accepted grammar is identical to JavaScript's Duration:
+//
+// https://tc39.es/proposal-temporal/#prod-Duration
+//
+// We follow JavaScript's grammar as JSON itself is derived from JavaScript.
+// The Temporal.Duration.toJSON method is guaranteed to produce an output
+// that can be parsed by this function so long as arithmetic in JavaScript
+// do not use a largestUnit value higher than "hours" (which is the default).
+// Even if it does, this will do a best-effort parsing with inaccurate units,
+// but report [errInaccurateDateUnits].
+func parseDurationISO8601(b []byte) (time.Duration, error) {
+ var invalid, overflow, inaccurate, sawFrac bool
+ var sumNanos, n, co uint64
+
+ // cutBytes is like [bytes.Cut], but uses either c0 or c1 as the separator.
+ cutBytes := func(b []byte, c0, c1 byte) (prefix, suffix []byte, ok bool) {
+ for i, c := range b {
+ if c == c0 || c == c1 {
+ return b[:i], b[i+1:], true
+ }
+ }
+ return b, nil, false
+ }
+
+ // mayParseUnit attempts to parse another date or time number
+ // identified by the desHi and desLo unit characters.
+ // If the part is absent for current unit, it returns b as is.
+ mayParseUnit := func(b []byte, desHi, desLo byte, unit time.Duration) []byte {
+ number, suffix, ok := cutBytes(b, desHi, desLo)
+ if !ok || sawFrac {
+ return b // designator is not present or already saw fraction, which can only be in the last component
+ }
+
+ // Parse the number.
+ // A fraction allowed for the accurate units in the last part.
+ whole, frac, ok := cutBytes(number, '.', ',')
+ if ok {
+ sawFrac = true
+ invalid = invalid || len(frac) == len("") || unit > time.Hour
+ if unit == time.Second {
+ n, ok = parsePaddedBase10(frac, uint64(time.Second))
+ invalid = invalid || !ok
+ } else {
+ f, err := strconv.ParseFloat("0."+string(frac), 64)
+ invalid = invalid || err != nil || len(bytes.Trim(frac[len("."):], "0123456789")) > 0
+ n = uint64(math.Round(f * float64(unit))) // never overflows since f is within [0..1]
+ }
+ sumNanos, co = bits.Add64(sumNanos, n, 0) // overflow if co > 0
+ overflow = overflow || co > 0
+ }
+ for len(whole) > 1 && whole[0] == '0' {
+ whole = whole[len("0"):] // trim leading zeros
+ }
+ n, ok := jsonwire.ParseUint(whole) // overflow if !ok && MaxUint64
+ hi, lo := bits.Mul64(n, uint64(unit)) // overflow if hi > 0
+ sumNanos, co = bits.Add64(sumNanos, lo, 0) // overflow if co > 0
+ invalid = invalid || (!ok && n != math.MaxUint64)
+ overflow = overflow || (!ok && n == math.MaxUint64) || hi > 0 || co > 0
+ inaccurate = inaccurate || unit > time.Hour
+ return suffix
+ }
+
+ suffix, neg := consumeSign(b, true)
+ prefix, suffix, okP := cutBytes(suffix, 'P', 'p')
+ durDate, durTime, okT := cutBytes(suffix, 'T', 't')
+ invalid = invalid || len(prefix) > 0 || !okP || (okT && len(durTime) == 0) || len(durDate)+len(durTime) == 0
+ if len(durDate) > 0 { // nominal portion of the duration
+ durDate = mayParseUnit(durDate, 'Y', 'y', time.Duration(daysPerYear*24*60*60*1e9))
+ durDate = mayParseUnit(durDate, 'M', 'm', time.Duration(daysPerYear/12*24*60*60*1e9))
+ durDate = mayParseUnit(durDate, 'W', 'w', time.Duration(7*24*60*60*1e9))
+ durDate = mayParseUnit(durDate, 'D', 'd', time.Duration(24*60*60*1e9))
+ invalid = invalid || len(durDate) > 0 // unknown elements
+ }
+ if len(durTime) > 0 { // accurate portion of the duration
+ durTime = mayParseUnit(durTime, 'H', 'h', time.Duration(60*60*1e9))
+ durTime = mayParseUnit(durTime, 'M', 'm', time.Duration(60*1e9))
+ durTime = mayParseUnit(durTime, 'S', 's', time.Duration(1e9))
+ invalid = invalid || len(durTime) > 0 // unknown elements
+ }
+ d := mayApplyDurationSign(sumNanos, neg)
+ overflow = overflow || (neg != (d < 0) && d != 0) // overflows signed duration
+
+ switch {
+ case invalid:
+ return 0, fmt.Errorf("invalid ISO 8601 duration %q: %w", b, strconv.ErrSyntax)
+ case overflow:
+ return 0, fmt.Errorf("invalid ISO 8601 duration %q: %w", b, strconv.ErrRange)
+ case inaccurate:
+ return d, fmt.Errorf("invalid ISO 8601 duration %q: %w", b, errInaccurateDateUnits)
+ default:
+ return d, nil
+ }
+}
+
// mayAppendDurationSign appends a negative sign if n is negative.
func mayAppendDurationSign(b []byte, d time.Duration) ([]byte, uint64) {
if d < 0 {
// parseTimeUnix parses t formatted as a decimal fractional number,
// where pow10 is a power-of-10 used to scale down the number.
func parseTimeUnix(b []byte, pow10 uint64) (time.Time, error) {
- suffix, neg := consumeSign(b) // consume sign
+ suffix, neg := consumeSign(b, false) // consume sign
wholeBytes, fracBytes := bytesCutByte(suffix, '.', true) // consume whole and frac fields
whole, okWhole := jsonwire.ParseUint(wholeBytes) // parse whole field; may overflow
frac, okFrac := parseFracBase10(fracBytes, 1e9/pow10) // parse frac field
return n, true
}
-// consumeSign consumes an optional leading negative sign.
-func consumeSign(b []byte) ([]byte, bool) {
- if len(b) > 0 && b[0] == '-' {
- return b[len("-"):], true
+// consumeSign consumes an optional leading negative or positive sign.
+func consumeSign(b []byte, allowPlus bool) ([]byte, bool) {
+ if len(b) > 0 {
+ if b[0] == '-' {
+ return b[len("-"):], true
+ } else if b[0] == '+' && allowPlus {
+ return b[len("+"):], false
+ }
}
return b, false
}
package json
import (
+ "errors"
"fmt"
"math"
+ "strconv"
"testing"
"time"
base10Milli string
base10Micro string
base10Nano string
+ iso8601 string
}{
- {math.MaxInt64, "9223372036.854775807", "9223372036854.775807", "9223372036854775.807", "9223372036854775807"},
- {1e12 + 1e12, "2000", "2000000", "2000000000", "2000000000000"},
- {1e12 + 1e11, "1100", "1100000", "1100000000", "1100000000000"},
- {1e12 + 1e10, "1010", "1010000", "1010000000", "1010000000000"},
- {1e12 + 1e9, "1001", "1001000", "1001000000", "1001000000000"},
- {1e12 + 1e8, "1000.1", "1000100", "1000100000", "1000100000000"},
- {1e12 + 1e7, "1000.01", "1000010", "1000010000", "1000010000000"},
- {1e12 + 1e6, "1000.001", "1000001", "1000001000", "1000001000000"},
- {1e12 + 1e5, "1000.0001", "1000000.1", "1000000100", "1000000100000"},
- {1e12 + 1e4, "1000.00001", "1000000.01", "1000000010", "1000000010000"},
- {1e12 + 1e3, "1000.000001", "1000000.001", "1000000001", "1000000001000"},
- {1e12 + 1e2, "1000.0000001", "1000000.0001", "1000000000.1", "1000000000100"},
- {1e12 + 1e1, "1000.00000001", "1000000.00001", "1000000000.01", "1000000000010"},
- {1e12 + 1e0, "1000.000000001", "1000000.000001", "1000000000.001", "1000000000001"},
- {+(1e9 + 1), "1.000000001", "1000.000001", "1000000.001", "1000000001"},
- {+(1e9), "1", "1000", "1000000", "1000000000"},
- {+(1e9 - 1), "0.999999999", "999.999999", "999999.999", "999999999"},
- {+100000000, "0.1", "100", "100000", "100000000"},
- {+120000000, "0.12", "120", "120000", "120000000"},
- {+123000000, "0.123", "123", "123000", "123000000"},
- {+123400000, "0.1234", "123.4", "123400", "123400000"},
- {+123450000, "0.12345", "123.45", "123450", "123450000"},
- {+123456000, "0.123456", "123.456", "123456", "123456000"},
- {+123456700, "0.1234567", "123.4567", "123456.7", "123456700"},
- {+123456780, "0.12345678", "123.45678", "123456.78", "123456780"},
- {+123456789, "0.123456789", "123.456789", "123456.789", "123456789"},
- {+12345678, "0.012345678", "12.345678", "12345.678", "12345678"},
- {+1234567, "0.001234567", "1.234567", "1234.567", "1234567"},
- {+123456, "0.000123456", "0.123456", "123.456", "123456"},
- {+12345, "0.000012345", "0.012345", "12.345", "12345"},
- {+1234, "0.000001234", "0.001234", "1.234", "1234"},
- {+123, "0.000000123", "0.000123", "0.123", "123"},
- {+12, "0.000000012", "0.000012", "0.012", "12"},
- {+1, "0.000000001", "0.000001", "0.001", "1"},
- {0, "0", "0", "0", "0"},
- {-1, "-0.000000001", "-0.000001", "-0.001", "-1"},
- {-12, "-0.000000012", "-0.000012", "-0.012", "-12"},
- {-123, "-0.000000123", "-0.000123", "-0.123", "-123"},
- {-1234, "-0.000001234", "-0.001234", "-1.234", "-1234"},
- {-12345, "-0.000012345", "-0.012345", "-12.345", "-12345"},
- {-123456, "-0.000123456", "-0.123456", "-123.456", "-123456"},
- {-1234567, "-0.001234567", "-1.234567", "-1234.567", "-1234567"},
- {-12345678, "-0.012345678", "-12.345678", "-12345.678", "-12345678"},
- {-123456789, "-0.123456789", "-123.456789", "-123456.789", "-123456789"},
- {-123456780, "-0.12345678", "-123.45678", "-123456.78", "-123456780"},
- {-123456700, "-0.1234567", "-123.4567", "-123456.7", "-123456700"},
- {-123456000, "-0.123456", "-123.456", "-123456", "-123456000"},
- {-123450000, "-0.12345", "-123.45", "-123450", "-123450000"},
- {-123400000, "-0.1234", "-123.4", "-123400", "-123400000"},
- {-123000000, "-0.123", "-123", "-123000", "-123000000"},
- {-120000000, "-0.12", "-120", "-120000", "-120000000"},
- {-100000000, "-0.1", "-100", "-100000", "-100000000"},
- {-(1e9 - 1), "-0.999999999", "-999.999999", "-999999.999", "-999999999"},
- {-(1e9), "-1", "-1000", "-1000000", "-1000000000"},
- {-(1e9 + 1), "-1.000000001", "-1000.000001", "-1000000.001", "-1000000001"},
- {math.MinInt64, "-9223372036.854775808", "-9223372036854.775808", "-9223372036854775.808", "-9223372036854775808"},
+ {math.MaxInt64, "9223372036.854775807", "9223372036854.775807", "9223372036854775.807", "9223372036854775807", "PT2562047H47M16.854775807S"},
+ {123*time.Hour + 4*time.Minute + 56*time.Second, "443096", "443096000", "443096000000", "443096000000000", "PT123H4M56S"},
+ {time.Hour, "3600", "3600000", "3600000000", "3600000000000", "PT1H"},
+ {time.Minute, "60", "60000", "60000000", "60000000000", "PT1M"},
+ {1e12 + 1e12, "2000", "2000000", "2000000000", "2000000000000", "PT33M20S"},
+ {1e12 + 1e11, "1100", "1100000", "1100000000", "1100000000000", "PT18M20S"},
+ {1e12 + 1e10, "1010", "1010000", "1010000000", "1010000000000", "PT16M50S"},
+ {1e12 + 1e9, "1001", "1001000", "1001000000", "1001000000000", "PT16M41S"},
+ {1e12 + 1e8, "1000.1", "1000100", "1000100000", "1000100000000", "PT16M40.1S"},
+ {1e12 + 1e7, "1000.01", "1000010", "1000010000", "1000010000000", "PT16M40.01S"},
+ {1e12 + 1e6, "1000.001", "1000001", "1000001000", "1000001000000", "PT16M40.001S"},
+ {1e12 + 1e5, "1000.0001", "1000000.1", "1000000100", "1000000100000", "PT16M40.0001S"},
+ {1e12 + 1e4, "1000.00001", "1000000.01", "1000000010", "1000000010000", "PT16M40.00001S"},
+ {1e12 + 1e3, "1000.000001", "1000000.001", "1000000001", "1000000001000", "PT16M40.000001S"},
+ {1e12 + 1e2, "1000.0000001", "1000000.0001", "1000000000.1", "1000000000100", "PT16M40.0000001S"},
+ {1e12 + 1e1, "1000.00000001", "1000000.00001", "1000000000.01", "1000000000010", "PT16M40.00000001S"},
+ {1e12 + 1e0, "1000.000000001", "1000000.000001", "1000000000.001", "1000000000001", "PT16M40.000000001S"},
+ {+(1e9 + 1), "1.000000001", "1000.000001", "1000000.001", "1000000001", "PT1.000000001S"},
+ {+(1e9), "1", "1000", "1000000", "1000000000", "PT1S"},
+ {+(1e9 - 1), "0.999999999", "999.999999", "999999.999", "999999999", "PT0.999999999S"},
+ {+100000000, "0.1", "100", "100000", "100000000", "PT0.1S"},
+ {+120000000, "0.12", "120", "120000", "120000000", "PT0.12S"},
+ {+123000000, "0.123", "123", "123000", "123000000", "PT0.123S"},
+ {+123400000, "0.1234", "123.4", "123400", "123400000", "PT0.1234S"},
+ {+123450000, "0.12345", "123.45", "123450", "123450000", "PT0.12345S"},
+ {+123456000, "0.123456", "123.456", "123456", "123456000", "PT0.123456S"},
+ {+123456700, "0.1234567", "123.4567", "123456.7", "123456700", "PT0.1234567S"},
+ {+123456780, "0.12345678", "123.45678", "123456.78", "123456780", "PT0.12345678S"},
+ {+123456789, "0.123456789", "123.456789", "123456.789", "123456789", "PT0.123456789S"},
+ {+12345678, "0.012345678", "12.345678", "12345.678", "12345678", "PT0.012345678S"},
+ {+1234567, "0.001234567", "1.234567", "1234.567", "1234567", "PT0.001234567S"},
+ {+123456, "0.000123456", "0.123456", "123.456", "123456", "PT0.000123456S"},
+ {+12345, "0.000012345", "0.012345", "12.345", "12345", "PT0.000012345S"},
+ {+1234, "0.000001234", "0.001234", "1.234", "1234", "PT0.000001234S"},
+ {+123, "0.000000123", "0.000123", "0.123", "123", "PT0.000000123S"},
+ {+12, "0.000000012", "0.000012", "0.012", "12", "PT0.000000012S"},
+ {+1, "0.000000001", "0.000001", "0.001", "1", "PT0.000000001S"},
+ {0, "0", "0", "0", "0", "PT0S"},
+ {-1, "-0.000000001", "-0.000001", "-0.001", "-1", "-PT0.000000001S"},
+ {-12, "-0.000000012", "-0.000012", "-0.012", "-12", "-PT0.000000012S"},
+ {-123, "-0.000000123", "-0.000123", "-0.123", "-123", "-PT0.000000123S"},
+ {-1234, "-0.000001234", "-0.001234", "-1.234", "-1234", "-PT0.000001234S"},
+ {-12345, "-0.000012345", "-0.012345", "-12.345", "-12345", "-PT0.000012345S"},
+ {-123456, "-0.000123456", "-0.123456", "-123.456", "-123456", "-PT0.000123456S"},
+ {-1234567, "-0.001234567", "-1.234567", "-1234.567", "-1234567", "-PT0.001234567S"},
+ {-12345678, "-0.012345678", "-12.345678", "-12345.678", "-12345678", "-PT0.012345678S"},
+ {-123456789, "-0.123456789", "-123.456789", "-123456.789", "-123456789", "-PT0.123456789S"},
+ {-123456780, "-0.12345678", "-123.45678", "-123456.78", "-123456780", "-PT0.12345678S"},
+ {-123456700, "-0.1234567", "-123.4567", "-123456.7", "-123456700", "-PT0.1234567S"},
+ {-123456000, "-0.123456", "-123.456", "-123456", "-123456000", "-PT0.123456S"},
+ {-123450000, "-0.12345", "-123.45", "-123450", "-123450000", "-PT0.12345S"},
+ {-123400000, "-0.1234", "-123.4", "-123400", "-123400000", "-PT0.1234S"},
+ {-123000000, "-0.123", "-123", "-123000", "-123000000", "-PT0.123S"},
+ {-120000000, "-0.12", "-120", "-120000", "-120000000", "-PT0.12S"},
+ {-100000000, "-0.1", "-100", "-100000", "-100000000", "-PT0.1S"},
+ {-(1e9 - 1), "-0.999999999", "-999.999999", "-999999.999", "-999999999", "-PT0.999999999S"},
+ {-(1e9), "-1", "-1000", "-1000000", "-1000000000", "-PT1S"},
+ {-(1e9 + 1), "-1.000000001", "-1000.000001", "-1000000.001", "-1000000001", "-PT1.000000001S"},
+ {math.MinInt64, "-9223372036.854775808", "-9223372036854.775808", "-9223372036854775.808", "-9223372036854775808", "-PT2562047H47M16.854775808S"},
}
func TestFormatDuration(t *testing.T) {
check(tt.td, tt.base10Milli, 1e6)
check(tt.td, tt.base10Micro, 1e3)
check(tt.td, tt.base10Nano, 1e0)
+ check(tt.td, tt.iso8601, 8601)
}
}
in string
base uint64
want time.Duration
- wantErr bool
+ wantErr error
}{
- {"0", 1e0, 0, false},
- {"0.", 1e0, 0, true},
- {"0.0", 1e0, 0, false},
- {"0.00", 1e0, 0, false},
- {"00.0", 1e0, 0, true},
- {"+0", 1e0, 0, true},
- {"1e0", 1e0, 0, true},
- {"1.000000000x", 1e9, 0, true},
- {"1.000000x", 1e6, 0, true},
- {"1.000x", 1e3, 0, true},
- {"1.x", 1e0, 0, true},
- {"1.0000000009", 1e9, +time.Second, false},
- {"1.0000009", 1e6, +time.Millisecond, false},
- {"1.0009", 1e3, +time.Microsecond, false},
- {"1.9", 1e0, +time.Nanosecond, false},
- {"-9223372036854775809", 1e0, 0, true},
- {"9223372036854775.808", 1e3, 0, true},
- {"-9223372036854.775809", 1e6, 0, true},
- {"9223372036.854775808", 1e9, 0, true},
- {"-1.9", 1e0, -time.Nanosecond, false},
- {"-1.0009", 1e3, -time.Microsecond, false},
- {"-1.0000009", 1e6, -time.Millisecond, false},
- {"-1.0000000009", 1e9, -time.Second, false},
+ {"0", 1e0, 0, nil},
+ {"0.", 1e0, 0, strconv.ErrSyntax},
+ {"0.0", 1e0, 0, nil},
+ {"0.00", 1e0, 0, nil},
+ {"00.0", 1e0, 0, strconv.ErrSyntax},
+ {"+0", 1e0, 0, strconv.ErrSyntax},
+ {"1e0", 1e0, 0, strconv.ErrSyntax},
+ {"1.000000000x", 1e9, 0, strconv.ErrSyntax},
+ {"1.000000x", 1e6, 0, strconv.ErrSyntax},
+ {"1.000x", 1e3, 0, strconv.ErrSyntax},
+ {"1.x", 1e0, 0, strconv.ErrSyntax},
+ {"1.0000000009", 1e9, +time.Second, nil},
+ {"1.0000009", 1e6, +time.Millisecond, nil},
+ {"1.0009", 1e3, +time.Microsecond, nil},
+ {"1.9", 1e0, +time.Nanosecond, nil},
+ {"-9223372036854775809", 1e0, 0, strconv.ErrRange},
+ {"9223372036854775.808", 1e3, 0, strconv.ErrRange},
+ {"-9223372036854.775809", 1e6, 0, strconv.ErrRange},
+ {"9223372036.854775808", 1e9, 0, strconv.ErrRange},
+ {"-1.9", 1e0, -time.Nanosecond, nil},
+ {"-1.0009", 1e3, -time.Microsecond, nil},
+ {"-1.0000009", 1e6, -time.Millisecond, nil},
+ {"-1.0000000009", 1e9, -time.Second, nil},
+ {"", 8601, 0, strconv.ErrSyntax},
+ {"P", 8601, 0, strconv.ErrSyntax},
+ {"PT", 8601, 0, strconv.ErrSyntax},
+ {"PT0", 8601, 0, strconv.ErrSyntax},
+ {"DT0S", 8601, 0, strconv.ErrSyntax},
+ {"PT0S", 8601, 0, nil},
+ {" PT0S", 8601, 0, strconv.ErrSyntax},
+ {"PT0S ", 8601, 0, strconv.ErrSyntax},
+ {"+PT0S", 8601, 0, nil},
+ {"PT0.M", 8601, 0, strconv.ErrSyntax},
+ {"PT0.S", 8601, 0, strconv.ErrSyntax},
+ {"PT0.0S", 8601, 0, nil},
+ {"PT0.0_0H", 8601, 0, strconv.ErrSyntax},
+ {"PT0.0_0M", 8601, 0, strconv.ErrSyntax},
+ {"PT0.0_0S", 8601, 0, strconv.ErrSyntax},
+ {"PT.0S", 8601, 0, strconv.ErrSyntax},
+ {"PT00.0S", 8601, 0, nil},
+ {"PT0S", 8601, 0, nil},
+ {"PT1,5S", 8601, time.Second + 500*time.Millisecond, nil},
+ {"PT1H", 8601, time.Hour, nil},
+ {"PT1H0S", 8601, time.Hour, nil},
+ {"PT0S", 8601, 0, nil},
+ {"PT00S", 8601, 0, nil},
+ {"PT000S", 8601, 0, nil},
+ {"PTS", 8601, 0, strconv.ErrSyntax},
+ {"PT1M", 8601, time.Minute, nil},
+ {"PT01M", 8601, time.Minute, nil},
+ {"PT001M", 8601, time.Minute, nil},
+ {"PT1H59S", 8601, time.Hour + 59*time.Second, nil},
+ {"PT123H4M56.789S", 8601, 123*time.Hour + 4*time.Minute + 56*time.Second + 789*time.Millisecond, nil},
+ {"-PT123H4M56.789S", 8601, -123*time.Hour - 4*time.Minute - 56*time.Second - 789*time.Millisecond, nil},
+ {"PT0H0S", 8601, 0, nil},
+ {"PT0H", 8601, 0, nil},
+ {"PT0M", 8601, 0, nil},
+ {"-PT0S", 8601, 0, nil},
+ {"PT1M0S", 8601, time.Minute, nil},
+ {"PT0H1M0S", 8601, time.Minute, nil},
+ {"PT01H02M03S", 8601, 1*time.Hour + 2*time.Minute + 3*time.Second, nil},
+ {"PT0,123S", 8601, 123 * time.Millisecond, nil},
+ {"PT1.S", 8601, 0, strconv.ErrSyntax},
+ {"PT1.000S", 8601, time.Second, nil},
+ {"PT0.025H", 8601, time.Minute + 30*time.Second, nil},
+ {"PT0.025H0M", 8601, 0, strconv.ErrSyntax},
+ {"PT1.5M", 8601, time.Minute + 30*time.Second, nil},
+ {"PT1.5M0S", 8601, 0, strconv.ErrSyntax},
+ {"PT60M", 8601, time.Hour, nil},
+ {"PT3600S", 8601, time.Hour, nil},
+ {"PT1H2M3.0S", 8601, 1*time.Hour + 2*time.Minute + 3*time.Second, nil},
+ {"pt1h2m3,0s", 8601, 1*time.Hour + 2*time.Minute + 3*time.Second, nil},
+ {"PT-1H-2M-3S", 8601, 0, strconv.ErrSyntax},
+ {"P1Y", 8601, time.Duration(daysPerYear * 24 * 60 * 60 * 1e9), errInaccurateDateUnits},
+ {"P1.0Y", 8601, 0, strconv.ErrSyntax},
+ {"P1M", 8601, time.Duration(daysPerYear / 12 * 24 * 60 * 60 * 1e9), errInaccurateDateUnits},
+ {"P1.0M", 8601, 0, strconv.ErrSyntax},
+ {"P1W", 8601, 7 * 24 * time.Hour, errInaccurateDateUnits},
+ {"P1.0W", 8601, 0, strconv.ErrSyntax},
+ {"P1D", 8601, 24 * time.Hour, errInaccurateDateUnits},
+ {"P1.0D", 8601, 0, strconv.ErrSyntax},
+ {"P1W1S", 8601, 0, strconv.ErrSyntax},
+ {"-P1Y2M3W4DT5H6M7.8S", 8601, -(time.Duration(14*daysPerYear/12*24*60*60*1e9) + time.Duration((3*7+4)*24*60*60*1e9) + 5*time.Hour + 6*time.Minute + 7*time.Second + 800*time.Millisecond), errInaccurateDateUnits},
+ {"-p1y2m3w4dt5h6m7.8s", 8601, -(time.Duration(14*daysPerYear/12*24*60*60*1e9) + time.Duration((3*7+4)*24*60*60*1e9) + 5*time.Hour + 6*time.Minute + 7*time.Second + 800*time.Millisecond), errInaccurateDateUnits},
+ {"P0Y0M0DT1H2M3S", 8601, 1*time.Hour + 2*time.Minute + 3*time.Second, errInaccurateDateUnits},
+ {"PT0.0000000001S", 8601, 0, nil},
+ {"PT0.0000000005S", 8601, 0, nil},
+ {"PT0.000000000500000000S", 8601, 0, nil},
+ {"PT0.000000000499999999S", 8601, 0, nil},
+ {"PT2562047H47M16.854775808S", 8601, 0, strconv.ErrRange},
+ {"-PT2562047H47M16.854775809S", 8601, 0, strconv.ErrRange},
+ {"PT9223372036.854775807S", 8601, math.MaxInt64, nil},
+ {"PT9223372036.854775808S", 8601, 0, strconv.ErrRange},
+ {"-PT9223372036.854775808S", 8601, math.MinInt64, nil},
+ {"-PT9223372036.854775809S", 8601, 0, strconv.ErrRange},
+ {"PT18446744073709551616S", 8601, 0, strconv.ErrRange},
+ {"PT5124096H", 8601, 0, strconv.ErrRange},
+ {"PT2562047.7880152155019444H", 8601, math.MaxInt64, nil},
+ {"PT2562047.7880152155022222H", 8601, 0, strconv.ErrRange},
+ {"PT5124094H94M33.709551616S", 8601, 0, strconv.ErrRange},
}
func TestParseDuration(t *testing.T) {
switch err := a.unmarshal([]byte(tt.in)); {
case a.td != tt.want:
t.Errorf("parseDuration(%q, %s) = %v, want %v", tt.in, baseLabel(tt.base), a.td, tt.want)
- case (err == nil) && tt.wantErr:
- t.Errorf("parseDuration(%q, %s) error is nil, want non-nil", tt.in, baseLabel(tt.base))
- case (err != nil) && !tt.wantErr:
- t.Errorf("parseDuration(%q, %s) error is non-nil, want nil", tt.in, baseLabel(tt.base))
+ case !errors.Is(err, tt.wantErr):
+ t.Errorf("parseDuration(%q, %s) error = %v, want %v", tt.in, baseLabel(tt.base), err, tt.wantErr)
}
}
}
}
f.Fuzz(func(t *testing.T, want int64) {
var buf []byte
- for _, base := range [...]uint64{1e0, 1e3, 1e6, 1e9} {
+ for _, base := range [...]uint64{1e0, 1e3, 1e6, 1e9, 8601} {
a := durationArshaler{td: time.Duration(want), base: base}
buf, _ = a.appendMarshal(buf[:0])
switch err := a.unmarshal(buf); {
f.Add([]byte(tt.in))
}
f.Fuzz(func(t *testing.T, in []byte) {
- for _, base := range [...]uint64{1e0, 1e3, 1e6, 1e9, 60} {
+ for _, base := range [...]uint64{1e0, 1e3, 1e6, 1e9, 8601} {
a := durationArshaler{base: base}
- if err := a.unmarshal(in); err == nil && base != 60 {
+ switch err := a.unmarshal(in); {
+ case err != nil: // nothing else to check
+ case base != 8601:
if n, err := jsonwire.ConsumeNumber(in); err != nil || n != len(in) {
t.Fatalf("parseDuration(%q) error is nil for invalid JSON number", in)
}
in string
base uint64
want time.Time
- wantErr bool
+ wantErr error
}{
- {"0", 1e0, time.Unix(0, 0).UTC(), false},
- {"0.", 1e0, time.Time{}, true},
- {"0.0", 1e0, time.Unix(0, 0).UTC(), false},
- {"0.00", 1e0, time.Unix(0, 0).UTC(), false},
- {"00.0", 1e0, time.Time{}, true},
- {"+0", 1e0, time.Time{}, true},
- {"1e0", 1e0, time.Time{}, true},
- {"1234567890123456789012345678901234567890", 1e0, time.Time{}, true},
- {"9223372036854775808000.000000", 1e3, time.Time{}, true},
- {"9223372036854775807999999.9999", 1e6, time.Unix(math.MaxInt64, 1e9-1).UTC(), false},
- {"9223372036854775807999999999.9", 1e9, time.Unix(math.MaxInt64, 1e9-1).UTC(), false},
- {"9223372036854775807.999999999x", 1e0, time.Time{}, true},
- {"9223372036854775807000000000", 1e9, time.Unix(math.MaxInt64, 0).UTC(), false},
- {"-9223372036854775808", 1e0, time.Unix(math.MinInt64, 0).UTC(), false},
- {"-9223372036854775808000.000001", 1e3, time.Time{}, true},
- {"-9223372036854775808000000.0001", 1e6, time.Unix(math.MinInt64, 0).UTC(), false},
- {"-9223372036854775808000000000.x", 1e9, time.Time{}, true},
- {"-1234567890123456789012345678901234567890", 1e9, time.Time{}, true},
+ {"0", 1e0, time.Unix(0, 0).UTC(), nil},
+ {"0.", 1e0, time.Time{}, strconv.ErrSyntax},
+ {"0.0", 1e0, time.Unix(0, 0).UTC(), nil},
+ {"0.00", 1e0, time.Unix(0, 0).UTC(), nil},
+ {"00.0", 1e0, time.Time{}, strconv.ErrSyntax},
+ {"+0", 1e0, time.Time{}, strconv.ErrSyntax},
+ {"1e0", 1e0, time.Time{}, strconv.ErrSyntax},
+ {"1234567890123456789012345678901234567890", 1e0, time.Time{}, strconv.ErrRange},
+ {"9223372036854775808000.000000", 1e3, time.Time{}, strconv.ErrRange},
+ {"9223372036854775807999999.9999", 1e6, time.Unix(math.MaxInt64, 1e9-1).UTC(), nil},
+ {"9223372036854775807999999999.9", 1e9, time.Unix(math.MaxInt64, 1e9-1).UTC(), nil},
+ {"9223372036854775807.999999999x", 1e0, time.Time{}, strconv.ErrSyntax},
+ {"9223372036854775807000000000", 1e9, time.Unix(math.MaxInt64, 0).UTC(), nil},
+ {"-9223372036854775808", 1e0, time.Unix(math.MinInt64, 0).UTC(), nil},
+ {"-9223372036854775808000.000001", 1e3, time.Time{}, strconv.ErrRange},
+ {"-9223372036854775808000000.0001", 1e6, time.Unix(math.MinInt64, 0).UTC(), nil},
+ {"-9223372036854775808000000000.x", 1e9, time.Time{}, strconv.ErrSyntax},
+ {"-1234567890123456789012345678901234567890", 1e9, time.Time{}, strconv.ErrRange},
}
func TestParseTime(t *testing.T) {
switch err := a.unmarshal([]byte(tt.in)); {
case a.tt != tt.want:
t.Errorf("parseTime(%q, %s) = time.Unix(%d, %d), want time.Unix(%d, %d)", tt.in, baseLabel(tt.base), a.tt.Unix(), a.tt.Nanosecond(), tt.want.Unix(), tt.want.Nanosecond())
- case (err == nil) && tt.wantErr:
- t.Errorf("parseTime(%q, %s) = (time.Unix(%d, %d), nil), want non-nil error", tt.in, baseLabel(tt.base), a.tt.Unix(), a.tt.Nanosecond())
- case (err != nil) && !tt.wantErr:
- t.Errorf("parseTime(%q, %s) error is non-nil, want nil", tt.in, baseLabel(tt.base))
+ case !errors.Is(err, tt.wantErr):
+ t.Errorf("parseTime(%q, %s) error = %v, want %v", tt.in, baseLabel(tt.base), err, tt.wantErr)
}
}
}