package json
import (
"bytes"
"cmp"
"errors"
"fmt"
"math"
"math/bits"
"reflect"
"strconv"
"strings"
"time"
"encoding/json/internal"
"encoding/json/internal/jsonflags"
"encoding/json/internal/jsonopts"
"encoding/json/internal/jsonwire"
"encoding/json/jsontext"
)
var (
timeDurationType = reflect .TypeFor [time .Duration ]()
timeTimeType = reflect .TypeFor [time .Time ]()
)
func makeTimeArshaler(fncs *arshaler , t reflect .Type ) *arshaler {
switch t {
case timeDurationType :
fncs .nonDefault = true
marshalNano := fncs .marshal
fncs .marshal = func (enc *jsontext .Encoder , va addressableValue , mo *jsonopts .Struct ) error {
xe := export .Encoder (enc )
var m durationArshaler
if mo .Format != "" && mo .FormatDepth == xe .Tokens .Depth () {
if !m .initFormat (mo .Format ) {
return newInvalidFormatError (enc , t , mo )
}
} else if mo .Flags .Get (jsonflags .FormatDurationAsNano ) {
return marshalNano (enc , va , mo )
} else {
return newMarshalErrorBefore (enc , t , errors .New ("no default representation (see https://go.dev/issue/71631); specify an explicit format" ))
}
m .td = *va .Addr ().Interface ().(*time .Duration )
k := stringOrNumberKind (!m .isNumeric () || xe .Tokens .Last .NeedObjectName () || mo .Flags .Get (jsonflags .StringifyNumbers ))
if err := xe .AppendRaw (k , true , m .appendMarshal ); err != nil {
if !isSyntacticError (err ) && !export .IsIOError (err ) {
err = newMarshalErrorBefore (enc , t , err )
}
return err
}
return nil
}
unmarshalNano := fncs .unmarshal
fncs .unmarshal = func (dec *jsontext .Decoder , va addressableValue , uo *jsonopts .Struct ) error {
xd := export .Decoder (dec )
var u durationArshaler
if uo .Format != "" && uo .FormatDepth == xd .Tokens .Depth () {
if !u .initFormat (uo .Format ) {
return newInvalidFormatError (dec , t , uo )
}
} else if uo .Flags .Get (jsonflags .FormatDurationAsNano ) {
return unmarshalNano (dec , va , uo )
} else {
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 )
var flags jsonwire .ValueFlags
td := va .Addr ().Interface ().(*time .Duration )
val , err := xd .ReadValue (&flags )
if err != nil {
return err
}
switch k := val .Kind (); k {
case 'n' :
if !uo .Flags .Get (jsonflags .MergeWithLegacySemantics ) {
*td = time .Duration (0 )
}
return nil
case '"' :
if !stringify {
break
}
val = jsonwire .UnquoteMayCopy (val , flags .IsVerbatim ())
if err := u .unmarshal (val ); err != nil {
return newUnmarshalErrorAfter (dec , t , err )
}
*td = u .td
return nil
case '0' :
if stringify {
break
}
if err := u .unmarshal (val ); err != nil {
return newUnmarshalErrorAfter (dec , t , err )
}
*td = u .td
return nil
}
return newUnmarshalErrorAfter (dec , t , nil )
}
case timeTimeType :
fncs .nonDefault = true
fncs .marshal = func (enc *jsontext .Encoder , va addressableValue , mo *jsonopts .Struct ) (err error ) {
xe := export .Encoder (enc )
var m timeArshaler
if mo .Format != "" && mo .FormatDepth == xe .Tokens .Depth () {
if !m .initFormat (mo .Format ) {
return newInvalidFormatError (enc , t , mo )
}
}
m .tt = *va .Addr ().Interface ().(*time .Time )
k := stringOrNumberKind (!m .isNumeric () || xe .Tokens .Last .NeedObjectName () || mo .Flags .Get (jsonflags .StringifyNumbers ))
if err := xe .AppendRaw (k , !m .hasCustomFormat (), m .appendMarshal ); err != nil {
if mo .Flags .Get (jsonflags .ReportErrorsWithLegacySemantics ) {
return internal .NewMarshalerError (va .Addr ().Interface (), err , "MarshalJSON" )
}
if !isSyntacticError (err ) && !export .IsIOError (err ) {
err = newMarshalErrorBefore (enc , t , err )
}
return err
}
return nil
}
fncs .unmarshal = func (dec *jsontext .Decoder , va addressableValue , uo *jsonopts .Struct ) (err error ) {
xd := export .Decoder (dec )
var u timeArshaler
if uo .Format != "" && uo .FormatDepth == xd .Tokens .Depth () {
if !u .initFormat (uo .Format ) {
return newInvalidFormatError (dec , t , uo )
}
} else if uo .Flags .Get (jsonflags .ParseTimeWithLooseRFC3339 ) {
u .looseRFC3339 = true
}
stringify := !u .isNumeric () || xd .Tokens .Last .NeedObjectName () || uo .Flags .Get (jsonflags .StringifyNumbers )
var flags jsonwire .ValueFlags
tt := va .Addr ().Interface ().(*time .Time )
val , err := xd .ReadValue (&flags )
if err != nil {
return err
}
switch k := val .Kind (); k {
case 'n' :
if !uo .Flags .Get (jsonflags .MergeWithLegacySemantics ) {
*tt = time .Time {}
}
return nil
case '"' :
if !stringify {
break
}
val = jsonwire .UnquoteMayCopy (val , flags .IsVerbatim ())
if err := u .unmarshal (val ); err != nil {
if uo .Flags .Get (jsonflags .ReportErrorsWithLegacySemantics ) {
return err
}
return newUnmarshalErrorAfter (dec , t , err )
}
*tt = u .tt
return nil
case '0' :
if stringify {
break
}
if err := u .unmarshal (val ); err != nil {
if uo .Flags .Get (jsonflags .ReportErrorsWithLegacySemantics ) {
return err
}
return newUnmarshalErrorAfter (dec , t , err )
}
*tt = u .tt
return nil
}
return newUnmarshalErrorAfter (dec , t , nil )
}
}
return fncs
}
type durationArshaler struct {
td time .Duration
base uint64
}
func (a *durationArshaler ) initFormat (format string ) (ok bool ) {
switch format {
case "units" :
a .base = 0
case "sec" :
a .base = 1e9
case "milli" :
a .base = 1e6
case "micro" :
a .base = 1e3
case "nano" :
a .base = 1e0
case "iso8601" :
a .base = 8601
default :
return false
}
return true
}
func (a *durationArshaler ) isNumeric () bool {
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
}
}
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 )
}
return err
}
type timeArshaler struct {
tt time .Time
base uint64
format string
looseRFC3339 bool
}
func (a *timeArshaler ) initFormat (format string ) bool {
if len (format ) == 0 {
return false
}
a .base = math .MaxUint
if c := format [0 ]; !('a' <= c && c <= 'z' ) && !('A' <= c && c <= 'Z' ) {
a .format = format
return true
}
switch format {
case "ANSIC" :
a .format = time .ANSIC
case "UnixDate" :
a .format = time .UnixDate
case "RubyDate" :
a .format = time .RubyDate
case "RFC822" :
a .format = time .RFC822
case "RFC822Z" :
a .format = time .RFC822Z
case "RFC850" :
a .format = time .RFC850
case "RFC1123" :
a .format = time .RFC1123
case "RFC1123Z" :
a .format = time .RFC1123Z
case "RFC3339" :
a .base = 0
a .format = time .RFC3339
case "RFC3339Nano" :
a .base = 0
a .format = time .RFC3339Nano
case "Kitchen" :
a .format = time .Kitchen
case "Stamp" :
a .format = time .Stamp
case "StampMilli" :
a .format = time .StampMilli
case "StampMicro" :
a .format = time .StampMicro
case "StampNano" :
a .format = time .StampNano
case "DateTime" :
a .format = time .DateTime
case "DateOnly" :
a .format = time .DateOnly
case "TimeOnly" :
a .format = time .TimeOnly
case "unix" :
a .base = 1e0
case "unixmilli" :
a .base = 1e3
case "unixmicro" :
a .base = 1e6
case "unixnano" :
a .base = 1e9
default :
if strings .TrimFunc (format , isLetterOrDigit ) == "" {
return false
}
a .format = format
}
return true
}
func (a *timeArshaler ) isNumeric () bool {
return int (a .base ) > 0
}
func (a *timeArshaler ) hasCustomFormat () bool {
return a .base == math .MaxUint
}
func (a *timeArshaler ) appendMarshal (b []byte ) ([]byte , error ) {
switch a .base {
case 0 :
format := cmp .Or (a .format , time .RFC3339Nano )
n0 := len (b )
b = a .tt .AppendFormat (b , format )
switch b := b [n0 :]; {
case b [len ("9999" )] != '-' :
return b , errors .New ("year outside of range [0,9999]" )
case b [len (b )-1 ] != 'Z' :
c := b [len (b )-len ("Z07:00" )]
if ('0' <= c && c <= '9' ) || parseDec2 (b [len (b )-len ("07:00" ):]) >= 24 {
return b , errors .New ("timezone hour outside of range [0,23]" )
}
}
return b , nil
case math .MaxUint :
return a .tt .AppendFormat (b , a .format ), nil
default :
return appendTimeUnix (b , a .tt , a .base ), nil
}
}
func (a *timeArshaler ) unmarshal (b []byte ) (err error ) {
switch a .base {
case 0 :
if err := a .tt .UnmarshalText (b ); err != nil {
return err
}
newParseError := func (layout , value , layoutElem , valueElem , message string ) error {
return &time .ParseError {Layout : layout , Value : value , LayoutElem : layoutElem , ValueElem : valueElem , Message : message }
}
switch {
case a .looseRFC3339 :
return nil
case b [len ("2006-01-02T" )+1 ] == ':' :
return newParseError (time .RFC3339 , string (b ), "15" , string (b [len ("2006-01-02T" ):][:1 ]), "" )
case b [len ("2006-01-02T15:04:05" )] == ',' :
return newParseError (time .RFC3339 , string (b ), "." , "," , "" )
case b [len (b )-1 ] != 'Z' :
switch {
case parseDec2 (b [len (b )-len ("07:00" ):]) >= 24 :
return newParseError (time .RFC3339 , string (b ), "Z07:00" , string (b [len (b )-len ("Z07:00" ):]), ": timezone hour out of range" )
case parseDec2 (b [len (b )-len ("00" ):]) >= 60 :
return newParseError (time .RFC3339 , string (b ), "Z07:00" , string (b [len (b )-len ("Z07:00" ):]), ": timezone minute out of range" )
}
}
return nil
case math .MaxUint :
a .tt , err = time .Parse (a .format , string (b ))
return err
default :
a .tt , err = parseTimeUnix (b , a .base )
return err
}
}
func appendDurationBase10(b []byte , d time .Duration , pow10 uint64 ) []byte {
b , n := mayAppendDurationSign (b , d )
whole , frac := bits .Div64 (0 , n , uint64 (pow10 ))
b = strconv .AppendUint (b , whole , 10 )
return appendFracBase10 (b , frac , pow10 )
}
func parseDurationBase10(b []byte , pow10 uint64 ) (time .Duration , error ) {
suffix , neg := consumeSign (b , false )
wholeBytes , fracBytes := bytesCutByte (suffix , '.' , true )
whole , okWhole := jsonwire .ParseUint (wholeBytes )
frac , okFrac := parseFracBase10 (fracBytes , pow10 )
hi , lo := bits .Mul64 (whole , uint64 (pow10 ))
sum , co := bits .Add64 (lo , uint64 (frac ), 0 )
switch d := mayApplyDurationSign (sum , neg ); {
case (!okWhole && whole != math .MaxUint64 ) || !okFrac :
return 0 , fmt .Errorf ("invalid duration %q: %w" , b , strconv .ErrSyntax )
case !okWhole || hi > 0 || co > 0 || neg != (d < 0 ):
return 0 , fmt .Errorf ("invalid duration %q: %w" , b , strconv .ErrRange )
default :
return d , nil
}
}
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 )
n , sec := bits .Div64 (0 , n , 60 )
hour , min := bits .Div64 (0 , n , 60 )
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
}
const daysPerYear = 365.2425
var errInaccurateDateUnits = errors .New ("inaccurate year, month, week, or day units" )
func parseDurationISO8601(b []byte ) (time .Duration , error ) {
var invalid , overflow , inaccurate , sawFrac bool
var sumNanos , n , co uint64
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 := func (b []byte , desHi , desLo byte , unit time .Duration ) []byte {
number , suffix , ok := cutBytes (b , desHi , desLo )
if !ok || sawFrac {
return b
}
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 )))
}
sumNanos , co = bits .Add64 (sumNanos , n , 0 )
overflow = overflow || co > 0
}
for len (whole ) > 1 && whole [0 ] == '0' {
whole = whole [len ("0" ):]
}
n , ok := jsonwire .ParseUint (whole )
hi , lo := bits .Mul64 (n , uint64 (unit ))
sumNanos , co = bits .Add64 (sumNanos , lo , 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 {
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
}
if len (durTime ) > 0 {
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
}
d := mayApplyDurationSign (sumNanos , neg )
overflow = overflow || (neg != (d < 0 ) && d != 0 )
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
}
}
func mayAppendDurationSign(b []byte , d time .Duration ) ([]byte , uint64 ) {
if d < 0 {
b = append (b , '-' )
d *= -1
}
return b , uint64 (d )
}
func mayApplyDurationSign(n uint64 , neg bool ) time .Duration {
if neg {
return -1 * time .Duration (n )
} else {
return +1 * time .Duration (n )
}
}
func appendTimeUnix(b []byte , t time .Time , pow10 uint64 ) []byte {
sec , nsec := t .Unix (), int64 (t .Nanosecond ())
if sec < 0 {
b = append (b , '-' )
sec , nsec = negateSecNano (sec , nsec )
}
switch {
case pow10 == 1e0 :
b = strconv .AppendUint (b , uint64 (sec ), 10 )
return appendFracBase10 (b , uint64 (nsec ), 1e9 )
case uint64 (sec ) < 1e9 :
b = strconv .AppendUint (b , uint64 (sec )*uint64 (pow10 )+uint64 (uint64 (nsec )/(1e9 /pow10 )), 10 )
return appendFracBase10 (b , (uint64 (nsec )*pow10 )%1e9 , 1e9 )
default :
b = strconv .AppendUint (b , uint64 (sec ), 10 )
b = appendPaddedBase10 (b , uint64 (nsec )/(1e9 /pow10 ), pow10 )
return appendFracBase10 (b , (uint64 (nsec )*pow10 )%1e9 , 1e9 )
}
}
func parseTimeUnix(b []byte , pow10 uint64 ) (time .Time , error ) {
suffix , neg := consumeSign (b , false )
wholeBytes , fracBytes := bytesCutByte (suffix , '.' , true )
whole , okWhole := jsonwire .ParseUint (wholeBytes )
frac , okFrac := parseFracBase10 (fracBytes , 1e9 /pow10 )
var sec , nsec int64
switch {
case pow10 == 1e0 :
sec = int64 (whole )
nsec = int64 (frac )
case okWhole :
sec = int64 (whole / pow10 )
nsec = int64 ((whole %pow10 )*(1e9 /pow10 ) + frac )
case !okWhole && whole == math .MaxUint64 :
width := int (math .Log10 (float64 (pow10 )))
whole , okWhole = jsonwire .ParseUint (wholeBytes [:len (wholeBytes )-width ])
mid , _ := parsePaddedBase10 (wholeBytes [len (wholeBytes )-width :], pow10 )
sec = int64 (whole )
nsec = int64 (mid *(1e9 /pow10 ) + frac )
}
if neg {
sec , nsec = negateSecNano (sec , nsec )
}
switch t := time .Unix (sec , nsec ).UTC (); {
case (!okWhole && whole != math .MaxUint64 ) || !okFrac :
return time .Time {}, fmt .Errorf ("invalid time %q: %w" , b , strconv .ErrSyntax )
case !okWhole || neg != (t .Unix () < 0 ):
return time .Time {}, fmt .Errorf ("invalid time %q: %w" , b , strconv .ErrRange )
default :
return t , nil
}
}
func negateSecNano(sec , nsec int64 ) (int64 , int64 ) {
sec = ^sec
nsec = -nsec + 1e9
sec += int64 (nsec / 1e9 )
nsec %= 1e9
return sec , nsec
}
func appendFracBase10(b []byte , n , max10 uint64 ) []byte {
if n == 0 {
return b
}
return bytes .TrimRight (appendPaddedBase10 (append (b , '.' ), n , max10 ), "0" )
}
func parseFracBase10(b []byte , max10 uint64 ) (n uint64 , ok bool ) {
switch {
case len (b ) == 0 :
return 0 , true
case len (b ) < len (".0" ) || b [0 ] != '.' :
return 0 , false
}
return parsePaddedBase10 (b [len ("." ):], max10 )
}
func appendPaddedBase10(b []byte , n , max10 uint64 ) []byte {
if n < max10 /10 {
i := len (b )
b = strconv .AppendUint (b , n +max10 /10 , 10 )
b [i ]--
return b
}
return strconv .AppendUint (b , n , 10 )
}
func parsePaddedBase10(b []byte , max10 uint64 ) (n uint64 , ok bool ) {
pow10 := uint64 (1 )
for pow10 < max10 {
n *= 10
if len (b ) > 0 {
if b [0 ] < '0' || '9' < b [0 ] {
return n , false
}
n += uint64 (b [0 ] - '0' )
b = b [1 :]
}
pow10 *= 10
}
if len (b ) > 0 && len (bytes .TrimRight (b , "0123456789" )) > 0 {
return n , false
}
return n , true
}
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
}
func bytesCutByte(b []byte , c byte , include bool ) ([]byte , []byte ) {
if i := bytes .IndexByte (b , c ); i >= 0 {
if include {
return b [:i ], b [i :]
}
return b [:i ], b [i +1 :]
}
return b , nil
}
func parseDec2(b []byte ) byte {
if len (b ) < 2 {
return 0
}
return 10 *(b [0 ]-'0' ) + (b [1 ] - '0' )
}
The pages are generated with Golds v0.7.9-preview . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .