Update
This commit is contained in:
199
vendor/github.com/go-json-experiment/json/arshal_time.go
generated
vendored
199
vendor/github.com/go-json-experiment/json/arshal_time.go
generated
vendored
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
//go:build !goexperiment.jsonv2 || !go1.25
|
||||
|
||||
package json
|
||||
|
||||
import (
|
||||
@@ -48,8 +50,11 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
|
||||
if !m.initFormat(mo.Format) {
|
||||
return newInvalidFormatError(enc, t, mo)
|
||||
}
|
||||
} else if mo.Flags.Get(jsonflags.FormatTimeWithLegacySemantics) {
|
||||
} else if mo.Flags.Get(jsonflags.FormatDurationAsNano) {
|
||||
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 (see https://go.dev/issue/71631); specify an explicit format"))
|
||||
}
|
||||
|
||||
// TODO(https://go.dev/issue/62121): Use reflect.Value.AssertTo.
|
||||
@@ -71,8 +76,11 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
|
||||
if !u.initFormat(uo.Format) {
|
||||
return newInvalidFormatError(dec, t, uo)
|
||||
}
|
||||
} else if uo.Flags.Get(jsonflags.FormatTimeWithLegacySemantics) {
|
||||
} else if uo.Flags.Get(jsonflags.FormatDurationAsNano) {
|
||||
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 (see https://go.dev/issue/71631); specify an explicit format"))
|
||||
}
|
||||
|
||||
stringify := !u.isNumeric() || xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers)
|
||||
@@ -142,7 +150,7 @@ func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
|
||||
if !u.initFormat(uo.Format) {
|
||||
return newInvalidFormatError(dec, t, uo)
|
||||
}
|
||||
} else if uo.Flags.Get(jsonflags.FormatTimeWithLegacySemantics) {
|
||||
} else if uo.Flags.Get(jsonflags.ParseTimeWithLooseRFC3339) {
|
||||
u.looseRFC3339 = true
|
||||
}
|
||||
|
||||
@@ -198,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
|
||||
}
|
||||
|
||||
@@ -213,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
|
||||
}
|
||||
@@ -220,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
|
||||
}
|
||||
@@ -236,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)
|
||||
}
|
||||
@@ -410,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
|
||||
@@ -426,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 {
|
||||
@@ -469,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
|
||||
@@ -568,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
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user