diff --git a/src/encoding/json/v2/arshal.go b/src/encoding/json/v2/arshal.go index 99fcc5bd46..10b16efe4a 100644 --- a/src/encoding/json/v2/arshal.go +++ b/src/encoding/json/v2/arshal.go @@ -147,17 +147,23 @@ var export = jsontext.Internal.Export(&internal.AllowInternalUse) // If the format matches one of the format constants declared // in the time package (e.g., RFC1123), then that format is used. // If the format is "unix", "unixmilli", "unixmicro", or "unixnano", -// then the timestamp is encoded as a JSON number of the number of seconds -// (or milliseconds, microseconds, or nanoseconds) since the Unix epoch, -// which is January 1st, 1970 at 00:00:00 UTC. +// then the timestamp is encoded as a possibly fractional JSON number +// of the number of seconds (or milliseconds, microseconds, or nanoseconds) +// since the Unix epoch, which is January 1st, 1970 at 00:00:00 UTC. +// To avoid a fractional component, round the timestamp to the relevant unit. // Otherwise, the format is used as-is with [time.Time.Format] if non-empty. // -// - A Go [time.Duration] is encoded as a JSON string containing the duration -// formatted according to [time.Duration.String]. +// - A Go [time.Duration] currently has no default representation and +// requires an explicit format to be specified. // If the format is "sec", "milli", "micro", or "nano", -// then the duration is encoded as a JSON number of the number of seconds -// (or milliseconds, microseconds, or nanoseconds) in the duration. -// If the format is "units", it uses [time.Duration.String]. +// then the duration is encoded as a possibly fractional JSON number +// of the number of seconds (or milliseconds, microseconds, or nanoseconds). +// To avoid a fractional component, round the duration to the relevant unit. +// If the format is "units", it is encoded as a JSON string formatted using +// [time.Duration.String] (e.g., "1h30m" for 1 hour 30 minutes). +// If the format is "iso8601", it is encoded as a JSON string using the +// ISO 8601 standard for durations (e.g., "PT1H30M" for 1 hour 30 minutes) +// using only accurate units of hours, minutes, and seconds. // // - All other Go types (e.g., complex numbers, channels, and functions) // have no default representation and result in a [SemanticError]. @@ -375,17 +381,21 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro // If the format matches one of the format constants declared in // the time package (e.g., RFC1123), then that format is used for parsing. // If the format is "unix", "unixmilli", "unixmicro", or "unixnano", -// then the timestamp is decoded from a JSON number of the number of seconds -// (or milliseconds, microseconds, or nanoseconds) since the Unix epoch, -// which is January 1st, 1970 at 00:00:00 UTC. +// then the timestamp is decoded from an optionally fractional JSON number +// of the number of seconds (or milliseconds, microseconds, or nanoseconds) +// since the Unix epoch, which is January 1st, 1970 at 00:00:00 UTC. // Otherwise, the format is used as-is with [time.Time.Parse] if non-empty. // -// - A Go [time.Duration] is decoded from a JSON string by -// passing the decoded string to [time.ParseDuration]. +// - A Go [time.Duration] currently has no default representation and +// requires an explicit format to be specified. // If the format is "sec", "milli", "micro", or "nano", -// then the duration is decoded from a JSON number of the number of seconds -// (or milliseconds, microseconds, or nanoseconds) in the duration. -// If the format is "units", it uses [time.ParseDuration]. +// then the duration is decoded from an optionally fractional JSON number +// of the number of seconds (or milliseconds, microseconds, or nanoseconds). +// If the format is "units", it is decoded from a JSON string parsed using +// [time.ParseDuration] (e.g., "1h30m" for 1 hour 30 minutes). +// If the format is "iso8601", it is decoded from a JSON string using the +// ISO 8601 standard for durations (e.g., "PT1H30M" for 1 hour 30 minutes) +// accepting only accurate units of hours, minutes, or seconds. // // - All other Go types (e.g., complex numbers, channels, and functions) // have no default representation and result in a [SemanticError]. diff --git a/src/encoding/json/v2/arshal_test.go b/src/encoding/json/v2/arshal_test.go index 6a1c97db1b..8494deed03 100644 --- a/src/encoding/json/v2/arshal_test.go +++ b/src/encoding/json/v2/arshal_test.go @@ -375,6 +375,7 @@ type ( D8 time.Duration `json:",string,format:micro"` D9 time.Duration `json:",format:nano"` D10 time.Duration `json:",string,format:nano"` + D11 time.Duration `json:",format:iso8601"` } structTimeFormat struct { T1 time.Time @@ -4375,6 +4376,7 @@ func TestMarshal(t *testing.T) { 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, }, want: `{ "D1": "12h34m56.078090012s", @@ -4386,7 +4388,8 @@ func TestMarshal(t *testing.T) { "D7": 45296078090.012, "D8": "45296078090.012", "D9": 45296078090012, - "D10": "45296078090012" + "D10": "45296078090012", + "D11": "PT12H34M56.078090012S" }`, }, { /* TODO(https://go.dev/issue/71631): Re-enable this test case. @@ -4396,7 +4399,7 @@ func TestMarshal(t *testing.T) { D1: 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, D2: 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, }, - want: `{"D1":45296078090012,"D2":"12h34m56.078090012s","D3":0,"D4":"0","D5":0,"D6":"0","D7":0,"D8":"0","D9":0,"D10":"0"}`, + want: `{"D1":45296078090012,"D2":"12h34m56.078090012s","D3":0,"D4":"0","D5":0,"D6":"0","D7":0,"D8":"0","D9":0,"D10":"0","D11":"PT0S"}`, }, { */ /* TODO(https://go.dev/issue/71631): Re-enable this test case. name: jsontest.Name("Duration/MapKey"), @@ -8833,6 +8836,35 @@ func TestUnmarshal(t *testing.T) { D time.Duration `json:",format:units"` // TODO(https://go.dev/issue/71631): Remove the format flag. }{1}), wantErr: newInvalidCharacterError("x", "at start of value", len64(`{"D":`), "/D"), + }, { + name: jsontest.Name("Duration/Format"), + inBuf: `{ + "D1": "12h34m56.078090012s", + "D2": "12h34m56.078090012s", + "D3": 45296.078090012, + "D4": "45296.078090012", + "D5": 45296078.090012, + "D6": "45296078.090012", + "D7": 45296078090.012, + "D8": "45296078090.012", + "D9": 45296078090012, + "D10": "45296078090012", + "D11": "PT12H34M56.078090012S" + }`, + inVal: new(structDurationFormat), + want: addr(structDurationFormat{ + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond, + }), }, { name: jsontest.Name("Duration/Format/Invalid"), inBuf: `{"D":"0s"}`, diff --git a/src/encoding/json/v2/arshal_time.go b/src/encoding/json/v2/arshal_time.go index 53f061e621..fefa50ff5f 100644 --- a/src/encoding/json/v2/arshal_time.go +++ b/src/encoding/json/v2/arshal_time.go @@ -54,7 +54,7 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler { 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. @@ -80,7 +80,7 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler { 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) @@ -206,6 +206,7 @@ type durationArshaler struct { // - 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 } @@ -221,6 +222,8 @@ func (a *durationArshaler) initFormat(format string) (ok bool) { a.base = 1e3 case "nano": a.base = 1e0 + case "iso8601": + a.base = 8601 default: return false } @@ -228,13 +231,15 @@ func (a *durationArshaler) initFormat(format string) (ok bool) { } 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 } @@ -244,6 +249,8 @@ func (a *durationArshaler) unmarshal(b []byte) (err error) { 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) } @@ -418,7 +425,7 @@ func appendDurationBase10(b []byte, d time.Duration, pow10 uint64) []byte { // 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 @@ -434,6 +441,166 @@ func parseDurationBase10(b []byte, pow10 uint64) (time.Duration, error) { } } +// 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 { @@ -477,7 +644,7 @@ func appendTimeUnix(b []byte, t time.Time, pow10 uint64) []byte { // 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 @@ -576,10 +743,14 @@ func parsePaddedBase10(b []byte, max10 uint64) (n uint64, ok bool) { 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 } diff --git a/src/encoding/json/v2/arshal_time_test.go b/src/encoding/json/v2/arshal_time_test.go index faa09de509..6c08e12494 100644 --- a/src/encoding/json/v2/arshal_time_test.go +++ b/src/encoding/json/v2/arshal_time_test.go @@ -7,8 +7,10 @@ package json import ( + "errors" "fmt" "math" + "strconv" "testing" "time" @@ -28,63 +30,67 @@ var formatDurationTestdata = []struct { 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) { @@ -107,6 +113,7 @@ 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) } } @@ -114,31 +121,108 @@ var parseDurationTestdata = []struct { 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) { @@ -147,10 +231,8 @@ 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) } } } @@ -161,7 +243,7 @@ func FuzzFormatDuration(f *testing.F) { } 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); { @@ -179,9 +261,11 @@ func FuzzParseDuration(f *testing.F) { 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) } @@ -239,26 +323,26 @@ var parseTimeTestdata = []struct { 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) { @@ -267,10 +351,8 @@ 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) } } } diff --git a/src/encoding/json/v2/example_test.go b/src/encoding/json/v2/example_test.go index fe40bff964..c6bf0a864d 100644 --- a/src/encoding/json/v2/example_test.go +++ b/src/encoding/json/v2/example_test.go @@ -402,27 +402,29 @@ func Example_unknownMembers() { // The "format" tag option can be used to alter the formatting of certain types. func Example_formatFlags() { value := struct { - BytesBase64 []byte `json:",format:base64"` - BytesHex [8]byte `json:",format:hex"` - BytesArray []byte `json:",format:array"` - FloatNonFinite float64 `json:",format:nonfinite"` - MapEmitNull map[string]any `json:",format:emitnull"` - SliceEmitNull []any `json:",format:emitnull"` - TimeDateOnly time.Time `json:",format:'2006-01-02'"` - TimeUnixSec time.Time `json:",format:unix"` - DurationSecs time.Duration `json:",format:sec"` - DurationNanos time.Duration `json:",format:nano"` + BytesBase64 []byte `json:",format:base64"` + BytesHex [8]byte `json:",format:hex"` + BytesArray []byte `json:",format:array"` + FloatNonFinite float64 `json:",format:nonfinite"` + MapEmitNull map[string]any `json:",format:emitnull"` + SliceEmitNull []any `json:",format:emitnull"` + TimeDateOnly time.Time `json:",format:'2006-01-02'"` + TimeUnixSec time.Time `json:",format:unix"` + DurationSecs time.Duration `json:",format:sec"` + DurationNanos time.Duration `json:",format:nano"` + DurationISO8601 time.Duration `json:",format:iso8601"` }{ - BytesBase64: []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, - BytesHex: [8]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, - BytesArray: []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, - FloatNonFinite: math.NaN(), - MapEmitNull: nil, - SliceEmitNull: nil, - TimeDateOnly: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), - TimeUnixSec: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), - DurationSecs: 12*time.Hour + 34*time.Minute + 56*time.Second + 7*time.Millisecond + 8*time.Microsecond + 9*time.Nanosecond, - DurationNanos: 12*time.Hour + 34*time.Minute + 56*time.Second + 7*time.Millisecond + 8*time.Microsecond + 9*time.Nanosecond, + BytesBase64: []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, + BytesHex: [8]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, + BytesArray: []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef}, + FloatNonFinite: math.NaN(), + MapEmitNull: nil, + SliceEmitNull: nil, + TimeDateOnly: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + TimeUnixSec: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC), + DurationSecs: 12*time.Hour + 34*time.Minute + 56*time.Second + 7*time.Millisecond + 8*time.Microsecond + 9*time.Nanosecond, + DurationNanos: 12*time.Hour + 34*time.Minute + 56*time.Second + 7*time.Millisecond + 8*time.Microsecond + 9*time.Nanosecond, + DurationISO8601: 12*time.Hour + 34*time.Minute + 56*time.Second + 7*time.Millisecond + 8*time.Microsecond + 9*time.Nanosecond, } b, err := json.Marshal(&value) @@ -452,7 +454,8 @@ func Example_formatFlags() { // "TimeDateOnly": "2000-01-01", // "TimeUnixSec": 946684800, // "DurationSecs": 45296.007008009, - // "DurationNanos": 45296007008009 + // "DurationNanos": 45296007008009, + // "DurationISO8601": "PT12H34M56.007008009S" // } }