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