Source file src/encoding/json/v2/arshal_time.go

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build goexperiment.jsonv2
     6  
     7  package json
     8  
     9  import (
    10  	"bytes"
    11  	"cmp"
    12  	"errors"
    13  	"fmt"
    14  	"math"
    15  	"math/bits"
    16  	"reflect"
    17  	"strconv"
    18  	"strings"
    19  	"time"
    20  
    21  	"encoding/json/internal"
    22  	"encoding/json/internal/jsonflags"
    23  	"encoding/json/internal/jsonopts"
    24  	"encoding/json/internal/jsonwire"
    25  	"encoding/json/jsontext"
    26  )
    27  
    28  var (
    29  	timeDurationType = reflect.TypeFor[time.Duration]()
    30  	timeTimeType     = reflect.TypeFor[time.Time]()
    31  )
    32  
    33  func makeTimeArshaler(fncs *arshaler, t reflect.Type) *arshaler {
    34  	// Ideally, time types would implement MarshalerTo and UnmarshalerFrom,
    35  	// but that would incur a dependency on package json from package time.
    36  	// Given how widely used time is, it is more acceptable that we incur a
    37  	// dependency on time from json.
    38  	//
    39  	// Injecting the arshaling functionality like this will not be identical
    40  	// to actually declaring methods on the time types since embedding of the
    41  	// time types will not be able to forward this functionality.
    42  	switch t {
    43  	case timeDurationType:
    44  		fncs.nonDefault = true
    45  		marshalNano := fncs.marshal
    46  		fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) error {
    47  			xe := export.Encoder(enc)
    48  			var m durationArshaler
    49  			if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() {
    50  				if !m.initFormat(mo.Format) {
    51  					return newInvalidFormatError(enc, t, mo)
    52  				}
    53  			} else if mo.Flags.Get(jsonflags.FormatTimeWithLegacySemantics) {
    54  				return marshalNano(enc, va, mo)
    55  			}
    56  
    57  			// TODO(https://go.dev/issue/62121): Use reflect.Value.AssertTo.
    58  			m.td = *va.Addr().Interface().(*time.Duration)
    59  			k := stringOrNumberKind(!m.isNumeric() || xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers))
    60  			if err := xe.AppendRaw(k, true, m.appendMarshal); err != nil {
    61  				if !isSyntacticError(err) && !export.IsIOError(err) {
    62  					err = newMarshalErrorBefore(enc, t, err)
    63  				}
    64  				return err
    65  			}
    66  			return nil
    67  		}
    68  		unmarshalNano := fncs.unmarshal
    69  		fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) error {
    70  			xd := export.Decoder(dec)
    71  			var u durationArshaler
    72  			if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() {
    73  				if !u.initFormat(uo.Format) {
    74  					return newInvalidFormatError(dec, t, uo)
    75  				}
    76  			} else if uo.Flags.Get(jsonflags.FormatTimeWithLegacySemantics) {
    77  				return unmarshalNano(dec, va, uo)
    78  			}
    79  
    80  			stringify := !u.isNumeric() || xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers)
    81  			var flags jsonwire.ValueFlags
    82  			td := va.Addr().Interface().(*time.Duration)
    83  			val, err := xd.ReadValue(&flags)
    84  			if err != nil {
    85  				return err
    86  			}
    87  			switch k := val.Kind(); k {
    88  			case 'n':
    89  				if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
    90  					*td = time.Duration(0)
    91  				}
    92  				return nil
    93  			case '"':
    94  				if !stringify {
    95  					break
    96  				}
    97  				val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim())
    98  				if err := u.unmarshal(val); err != nil {
    99  					return newUnmarshalErrorAfter(dec, t, err)
   100  				}
   101  				*td = u.td
   102  				return nil
   103  			case '0':
   104  				if stringify {
   105  					break
   106  				}
   107  				if err := u.unmarshal(val); err != nil {
   108  					return newUnmarshalErrorAfter(dec, t, err)
   109  				}
   110  				*td = u.td
   111  				return nil
   112  			}
   113  			return newUnmarshalErrorAfter(dec, t, nil)
   114  		}
   115  	case timeTimeType:
   116  		fncs.nonDefault = true
   117  		fncs.marshal = func(enc *jsontext.Encoder, va addressableValue, mo *jsonopts.Struct) (err error) {
   118  			xe := export.Encoder(enc)
   119  			var m timeArshaler
   120  			if mo.Format != "" && mo.FormatDepth == xe.Tokens.Depth() {
   121  				if !m.initFormat(mo.Format) {
   122  					return newInvalidFormatError(enc, t, mo)
   123  				}
   124  			}
   125  
   126  			// TODO(https://go.dev/issue/62121): Use reflect.Value.AssertTo.
   127  			m.tt = *va.Addr().Interface().(*time.Time)
   128  			k := stringOrNumberKind(!m.isNumeric() || xe.Tokens.Last.NeedObjectName() || mo.Flags.Get(jsonflags.StringifyNumbers))
   129  			if err := xe.AppendRaw(k, !m.hasCustomFormat(), m.appendMarshal); err != nil {
   130  				if mo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
   131  					return internal.NewMarshalerError(va.Addr().Interface(), err, "MarshalJSON") // unlike unmarshal, always wrapped
   132  				}
   133  				if !isSyntacticError(err) && !export.IsIOError(err) {
   134  					err = newMarshalErrorBefore(enc, t, err)
   135  				}
   136  				return err
   137  			}
   138  			return nil
   139  		}
   140  		fncs.unmarshal = func(dec *jsontext.Decoder, va addressableValue, uo *jsonopts.Struct) (err error) {
   141  			xd := export.Decoder(dec)
   142  			var u timeArshaler
   143  			if uo.Format != "" && uo.FormatDepth == xd.Tokens.Depth() {
   144  				if !u.initFormat(uo.Format) {
   145  					return newInvalidFormatError(dec, t, uo)
   146  				}
   147  			} else if uo.Flags.Get(jsonflags.FormatTimeWithLegacySemantics) {
   148  				u.looseRFC3339 = true
   149  			}
   150  
   151  			stringify := !u.isNumeric() || xd.Tokens.Last.NeedObjectName() || uo.Flags.Get(jsonflags.StringifyNumbers)
   152  			var flags jsonwire.ValueFlags
   153  			tt := va.Addr().Interface().(*time.Time)
   154  			val, err := xd.ReadValue(&flags)
   155  			if err != nil {
   156  				return err
   157  			}
   158  			switch k := val.Kind(); k {
   159  			case 'n':
   160  				if !uo.Flags.Get(jsonflags.MergeWithLegacySemantics) {
   161  					*tt = time.Time{}
   162  				}
   163  				return nil
   164  			case '"':
   165  				if !stringify {
   166  					break
   167  				}
   168  				val = jsonwire.UnquoteMayCopy(val, flags.IsVerbatim())
   169  				if err := u.unmarshal(val); err != nil {
   170  					if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
   171  						return err // unlike marshal, never wrapped
   172  					}
   173  					return newUnmarshalErrorAfter(dec, t, err)
   174  				}
   175  				*tt = u.tt
   176  				return nil
   177  			case '0':
   178  				if stringify {
   179  					break
   180  				}
   181  				if err := u.unmarshal(val); err != nil {
   182  					if uo.Flags.Get(jsonflags.ReportErrorsWithLegacySemantics) {
   183  						return err // unlike marshal, never wrapped
   184  					}
   185  					return newUnmarshalErrorAfter(dec, t, err)
   186  				}
   187  				*tt = u.tt
   188  				return nil
   189  			}
   190  			return newUnmarshalErrorAfter(dec, t, nil)
   191  		}
   192  	}
   193  	return fncs
   194  }
   195  
   196  type durationArshaler struct {
   197  	td time.Duration
   198  
   199  	// base records the representation where:
   200  	//   - 0 uses time.Duration.String
   201  	//   - 1e0, 1e3, 1e6, or 1e9 use a decimal encoding of the duration as
   202  	//     nanoseconds, microseconds, milliseconds, or seconds.
   203  	base uint64
   204  }
   205  
   206  func (a *durationArshaler) initFormat(format string) (ok bool) {
   207  	switch format {
   208  	case "units":
   209  		a.base = 0
   210  	case "sec":
   211  		a.base = 1e9
   212  	case "milli":
   213  		a.base = 1e6
   214  	case "micro":
   215  		a.base = 1e3
   216  	case "nano":
   217  		a.base = 1e0
   218  	default:
   219  		return false
   220  	}
   221  	return true
   222  }
   223  
   224  func (a *durationArshaler) isNumeric() bool {
   225  	return a.base != 0 && a.base != 60
   226  }
   227  
   228  func (a *durationArshaler) appendMarshal(b []byte) ([]byte, error) {
   229  	switch a.base {
   230  	case 0:
   231  		return append(b, a.td.String()...), nil
   232  	default:
   233  		return appendDurationBase10(b, a.td, a.base), nil
   234  	}
   235  }
   236  
   237  func (a *durationArshaler) unmarshal(b []byte) (err error) {
   238  	switch a.base {
   239  	case 0:
   240  		a.td, err = time.ParseDuration(string(b))
   241  	default:
   242  		a.td, err = parseDurationBase10(b, a.base)
   243  	}
   244  	return err
   245  }
   246  
   247  type timeArshaler struct {
   248  	tt time.Time
   249  
   250  	// base records the representation where:
   251  	//   - 0 uses RFC 3339 encoding of the timestamp
   252  	//   - 1e0, 1e3, 1e6, or 1e9 use a decimal encoding of the timestamp as
   253  	//     seconds, milliseconds, microseconds, or nanoseconds since Unix epoch.
   254  	//   - math.MaxUint uses time.Time.Format to encode the timestamp
   255  	base   uint64
   256  	format string // time format passed to time.Parse
   257  
   258  	looseRFC3339 bool
   259  }
   260  
   261  func (a *timeArshaler) initFormat(format string) bool {
   262  	// We assume that an exported constant in the time package will
   263  	// always start with an uppercase ASCII letter.
   264  	if len(format) == 0 {
   265  		return false
   266  	}
   267  	a.base = math.MaxUint // implies custom format
   268  	if c := format[0]; !('a' <= c && c <= 'z') && !('A' <= c && c <= 'Z') {
   269  		a.format = format
   270  		return true
   271  	}
   272  	switch format {
   273  	case "ANSIC":
   274  		a.format = time.ANSIC
   275  	case "UnixDate":
   276  		a.format = time.UnixDate
   277  	case "RubyDate":
   278  		a.format = time.RubyDate
   279  	case "RFC822":
   280  		a.format = time.RFC822
   281  	case "RFC822Z":
   282  		a.format = time.RFC822Z
   283  	case "RFC850":
   284  		a.format = time.RFC850
   285  	case "RFC1123":
   286  		a.format = time.RFC1123
   287  	case "RFC1123Z":
   288  		a.format = time.RFC1123Z
   289  	case "RFC3339":
   290  		a.base = 0
   291  		a.format = time.RFC3339
   292  	case "RFC3339Nano":
   293  		a.base = 0
   294  		a.format = time.RFC3339Nano
   295  	case "Kitchen":
   296  		a.format = time.Kitchen
   297  	case "Stamp":
   298  		a.format = time.Stamp
   299  	case "StampMilli":
   300  		a.format = time.StampMilli
   301  	case "StampMicro":
   302  		a.format = time.StampMicro
   303  	case "StampNano":
   304  		a.format = time.StampNano
   305  	case "DateTime":
   306  		a.format = time.DateTime
   307  	case "DateOnly":
   308  		a.format = time.DateOnly
   309  	case "TimeOnly":
   310  		a.format = time.TimeOnly
   311  	case "unix":
   312  		a.base = 1e0
   313  	case "unixmilli":
   314  		a.base = 1e3
   315  	case "unixmicro":
   316  		a.base = 1e6
   317  	case "unixnano":
   318  		a.base = 1e9
   319  	default:
   320  		// Reject any Go identifier in case new constants are supported.
   321  		if strings.TrimFunc(format, isLetterOrDigit) == "" {
   322  			return false
   323  		}
   324  		a.format = format
   325  	}
   326  	return true
   327  }
   328  
   329  func (a *timeArshaler) isNumeric() bool {
   330  	return int(a.base) > 0
   331  }
   332  
   333  func (a *timeArshaler) hasCustomFormat() bool {
   334  	return a.base == math.MaxUint
   335  }
   336  
   337  func (a *timeArshaler) appendMarshal(b []byte) ([]byte, error) {
   338  	switch a.base {
   339  	case 0:
   340  		format := cmp.Or(a.format, time.RFC3339Nano)
   341  		n0 := len(b)
   342  		b = a.tt.AppendFormat(b, format)
   343  		// Not all Go timestamps can be represented as valid RFC 3339.
   344  		// Explicitly check for these edge cases.
   345  		// See https://go.dev/issue/4556 and https://go.dev/issue/54580.
   346  		switch b := b[n0:]; {
   347  		case b[len("9999")] != '-': // year must be exactly 4 digits wide
   348  			return b, errors.New("year outside of range [0,9999]")
   349  		case b[len(b)-1] != 'Z':
   350  			c := b[len(b)-len("Z07:00")]
   351  			if ('0' <= c && c <= '9') || parseDec2(b[len(b)-len("07:00"):]) >= 24 {
   352  				return b, errors.New("timezone hour outside of range [0,23]")
   353  			}
   354  		}
   355  		return b, nil
   356  	case math.MaxUint:
   357  		return a.tt.AppendFormat(b, a.format), nil
   358  	default:
   359  		return appendTimeUnix(b, a.tt, a.base), nil
   360  	}
   361  }
   362  
   363  func (a *timeArshaler) unmarshal(b []byte) (err error) {
   364  	switch a.base {
   365  	case 0:
   366  		// Use time.Time.UnmarshalText to avoid possible string allocation.
   367  		if err := a.tt.UnmarshalText(b); err != nil {
   368  			return err
   369  		}
   370  		// TODO(https://go.dev/issue/57912):
   371  		// RFC 3339 specifies the grammar for a valid timestamp.
   372  		// However, the parsing functionality in "time" is too loose and
   373  		// incorrectly accepts invalid timestamps as valid.
   374  		// Remove these manual checks when "time" checks it for us.
   375  		newParseError := func(layout, value, layoutElem, valueElem, message string) error {
   376  			return &time.ParseError{Layout: layout, Value: value, LayoutElem: layoutElem, ValueElem: valueElem, Message: message}
   377  		}
   378  		switch {
   379  		case a.looseRFC3339:
   380  			return nil
   381  		case b[len("2006-01-02T")+1] == ':': // hour must be two digits
   382  			return newParseError(time.RFC3339, string(b), "15", string(b[len("2006-01-02T"):][:1]), "")
   383  		case b[len("2006-01-02T15:04:05")] == ',': // sub-second separator must be a period
   384  			return newParseError(time.RFC3339, string(b), ".", ",", "")
   385  		case b[len(b)-1] != 'Z':
   386  			switch {
   387  			case parseDec2(b[len(b)-len("07:00"):]) >= 24: // timezone hour must be in range
   388  				return newParseError(time.RFC3339, string(b), "Z07:00", string(b[len(b)-len("Z07:00"):]), ": timezone hour out of range")
   389  			case parseDec2(b[len(b)-len("00"):]) >= 60: // timezone minute must be in range
   390  				return newParseError(time.RFC3339, string(b), "Z07:00", string(b[len(b)-len("Z07:00"):]), ": timezone minute out of range")
   391  			}
   392  		}
   393  		return nil
   394  	case math.MaxUint:
   395  		a.tt, err = time.Parse(a.format, string(b))
   396  		return err
   397  	default:
   398  		a.tt, err = parseTimeUnix(b, a.base)
   399  		return err
   400  	}
   401  }
   402  
   403  // appendDurationBase10 appends d formatted as a decimal fractional number,
   404  // where pow10 is a power-of-10 used to scale down the number.
   405  func appendDurationBase10(b []byte, d time.Duration, pow10 uint64) []byte {
   406  	b, n := mayAppendDurationSign(b, d)            // append sign
   407  	whole, frac := bits.Div64(0, n, uint64(pow10)) // compute whole and frac fields
   408  	b = strconv.AppendUint(b, whole, 10)           // append whole field
   409  	return appendFracBase10(b, frac, pow10)        // append frac field
   410  }
   411  
   412  // parseDurationBase10 parses d from a decimal fractional number,
   413  // where pow10 is a power-of-10 used to scale up the number.
   414  func parseDurationBase10(b []byte, pow10 uint64) (time.Duration, error) {
   415  	suffix, neg := consumeSign(b)                            // consume sign
   416  	wholeBytes, fracBytes := bytesCutByte(suffix, '.', true) // consume whole and frac fields
   417  	whole, okWhole := jsonwire.ParseUint(wholeBytes)         // parse whole field; may overflow
   418  	frac, okFrac := parseFracBase10(fracBytes, pow10)        // parse frac field
   419  	hi, lo := bits.Mul64(whole, uint64(pow10))               // overflow if hi > 0
   420  	sum, co := bits.Add64(lo, uint64(frac), 0)               // overflow if co > 0
   421  	switch d := mayApplyDurationSign(sum, neg); {            // overflow if neg != (d < 0)
   422  	case (!okWhole && whole != math.MaxUint64) || !okFrac:
   423  		return 0, fmt.Errorf("invalid duration %q: %w", b, strconv.ErrSyntax)
   424  	case !okWhole || hi > 0 || co > 0 || neg != (d < 0):
   425  		return 0, fmt.Errorf("invalid duration %q: %w", b, strconv.ErrRange)
   426  	default:
   427  		return d, nil
   428  	}
   429  }
   430  
   431  // mayAppendDurationSign appends a negative sign if n is negative.
   432  func mayAppendDurationSign(b []byte, d time.Duration) ([]byte, uint64) {
   433  	if d < 0 {
   434  		b = append(b, '-')
   435  		d *= -1
   436  	}
   437  	return b, uint64(d)
   438  }
   439  
   440  // mayApplyDurationSign inverts n if neg is specified.
   441  func mayApplyDurationSign(n uint64, neg bool) time.Duration {
   442  	if neg {
   443  		return -1 * time.Duration(n)
   444  	} else {
   445  		return +1 * time.Duration(n)
   446  	}
   447  }
   448  
   449  // appendTimeUnix appends t formatted as a decimal fractional number,
   450  // where pow10 is a power-of-10 used to scale up the number.
   451  func appendTimeUnix(b []byte, t time.Time, pow10 uint64) []byte {
   452  	sec, nsec := t.Unix(), int64(t.Nanosecond())
   453  	if sec < 0 {
   454  		b = append(b, '-')
   455  		sec, nsec = negateSecNano(sec, nsec)
   456  	}
   457  	switch {
   458  	case pow10 == 1e0: // fast case where units is in seconds
   459  		b = strconv.AppendUint(b, uint64(sec), 10)
   460  		return appendFracBase10(b, uint64(nsec), 1e9)
   461  	case uint64(sec) < 1e9: // intermediate case where units is not seconds, but no overflow
   462  		b = strconv.AppendUint(b, uint64(sec)*uint64(pow10)+uint64(uint64(nsec)/(1e9/pow10)), 10)
   463  		return appendFracBase10(b, (uint64(nsec)*pow10)%1e9, 1e9)
   464  	default: // slow case where units is not seconds and overflow would occur
   465  		b = strconv.AppendUint(b, uint64(sec), 10)
   466  		b = appendPaddedBase10(b, uint64(nsec)/(1e9/pow10), pow10)
   467  		return appendFracBase10(b, (uint64(nsec)*pow10)%1e9, 1e9)
   468  	}
   469  }
   470  
   471  // parseTimeUnix parses t formatted as a decimal fractional number,
   472  // where pow10 is a power-of-10 used to scale down the number.
   473  func parseTimeUnix(b []byte, pow10 uint64) (time.Time, error) {
   474  	suffix, neg := consumeSign(b)                            // consume sign
   475  	wholeBytes, fracBytes := bytesCutByte(suffix, '.', true) // consume whole and frac fields
   476  	whole, okWhole := jsonwire.ParseUint(wholeBytes)         // parse whole field; may overflow
   477  	frac, okFrac := parseFracBase10(fracBytes, 1e9/pow10)    // parse frac field
   478  	var sec, nsec int64
   479  	switch {
   480  	case pow10 == 1e0: // fast case where units is in seconds
   481  		sec = int64(whole) // check overflow later after negation
   482  		nsec = int64(frac) // cannot overflow
   483  	case okWhole: // intermediate case where units is not seconds, but no overflow
   484  		sec = int64(whole / pow10)                     // check overflow later after negation
   485  		nsec = int64((whole%pow10)*(1e9/pow10) + frac) // cannot overflow
   486  	case !okWhole && whole == math.MaxUint64: // slow case where units is not seconds and overflow occurred
   487  		width := int(math.Log10(float64(pow10)))                                // compute len(strconv.Itoa(pow10-1))
   488  		whole, okWhole = jsonwire.ParseUint(wholeBytes[:len(wholeBytes)-width]) // parse the upper whole field
   489  		mid, _ := parsePaddedBase10(wholeBytes[len(wholeBytes)-width:], pow10)  // parse the lower whole field
   490  		sec = int64(whole)                                                      // check overflow later after negation
   491  		nsec = int64(mid*(1e9/pow10) + frac)                                    // cannot overflow
   492  	}
   493  	if neg {
   494  		sec, nsec = negateSecNano(sec, nsec)
   495  	}
   496  	switch t := time.Unix(sec, nsec).UTC(); {
   497  	case (!okWhole && whole != math.MaxUint64) || !okFrac:
   498  		return time.Time{}, fmt.Errorf("invalid time %q: %w", b, strconv.ErrSyntax)
   499  	case !okWhole || neg != (t.Unix() < 0):
   500  		return time.Time{}, fmt.Errorf("invalid time %q: %w", b, strconv.ErrRange)
   501  	default:
   502  		return t, nil
   503  	}
   504  }
   505  
   506  // negateSecNano negates a Unix timestamp, where nsec must be within [0, 1e9).
   507  func negateSecNano(sec, nsec int64) (int64, int64) {
   508  	sec = ^sec               // twos-complement negation (i.e., -1*sec + 1)
   509  	nsec = -nsec + 1e9       // negate nsec and add 1e9 (which is the extra +1 from sec negation)
   510  	sec += int64(nsec / 1e9) // handle possible overflow of nsec if it started as zero
   511  	nsec %= 1e9              // ensure nsec stays within [0, 1e9)
   512  	return sec, nsec
   513  }
   514  
   515  // appendFracBase10 appends the fraction of n/max10,
   516  // where max10 is a power-of-10 that is larger than n.
   517  func appendFracBase10(b []byte, n, max10 uint64) []byte {
   518  	if n == 0 {
   519  		return b
   520  	}
   521  	return bytes.TrimRight(appendPaddedBase10(append(b, '.'), n, max10), "0")
   522  }
   523  
   524  // parseFracBase10 parses the fraction of n/max10,
   525  // where max10 is a power-of-10 that is larger than n.
   526  func parseFracBase10(b []byte, max10 uint64) (n uint64, ok bool) {
   527  	switch {
   528  	case len(b) == 0:
   529  		return 0, true
   530  	case len(b) < len(".0") || b[0] != '.':
   531  		return 0, false
   532  	}
   533  	return parsePaddedBase10(b[len("."):], max10)
   534  }
   535  
   536  // appendPaddedBase10 appends a zero-padded encoding of n,
   537  // where max10 is a power-of-10 that is larger than n.
   538  func appendPaddedBase10(b []byte, n, max10 uint64) []byte {
   539  	if n < max10/10 {
   540  		// Formatting of n is shorter than log10(max10),
   541  		// so add max10/10 to ensure the length is equal to log10(max10).
   542  		i := len(b)
   543  		b = strconv.AppendUint(b, n+max10/10, 10)
   544  		b[i]-- // subtract the addition of max10/10
   545  		return b
   546  	}
   547  	return strconv.AppendUint(b, n, 10)
   548  }
   549  
   550  // parsePaddedBase10 parses b as the zero-padded encoding of n,
   551  // where max10 is a power-of-10 that is larger than n.
   552  // Truncated suffix is treated as implicit zeros.
   553  // Extended suffix is ignored, but verified to contain only digits.
   554  func parsePaddedBase10(b []byte, max10 uint64) (n uint64, ok bool) {
   555  	pow10 := uint64(1)
   556  	for pow10 < max10 {
   557  		n *= 10
   558  		if len(b) > 0 {
   559  			if b[0] < '0' || '9' < b[0] {
   560  				return n, false
   561  			}
   562  			n += uint64(b[0] - '0')
   563  			b = b[1:]
   564  		}
   565  		pow10 *= 10
   566  	}
   567  	if len(b) > 0 && len(bytes.TrimRight(b, "0123456789")) > 0 {
   568  		return n, false // trailing characters are not digits
   569  	}
   570  	return n, true
   571  }
   572  
   573  // consumeSign consumes an optional leading negative sign.
   574  func consumeSign(b []byte) ([]byte, bool) {
   575  	if len(b) > 0 && b[0] == '-' {
   576  		return b[len("-"):], true
   577  	}
   578  	return b, false
   579  }
   580  
   581  // bytesCutByte is similar to bytes.Cut(b, []byte{c}),
   582  // except c may optionally be included as part of the suffix.
   583  func bytesCutByte(b []byte, c byte, include bool) ([]byte, []byte) {
   584  	if i := bytes.IndexByte(b, c); i >= 0 {
   585  		if include {
   586  			return b[:i], b[i:]
   587  		}
   588  		return b[:i], b[i+1:]
   589  	}
   590  	return b, nil
   591  }
   592  
   593  // parseDec2 parses b as an unsigned, base-10, 2-digit number.
   594  // The result is undefined if digits are not base-10.
   595  func parseDec2(b []byte) byte {
   596  	if len(b) < 2 {
   597  		return 0
   598  	}
   599  	return 10*(b[0]-'0') + (b[1] - '0')
   600  }
   601  

View as plain text