Skip to content

Commit d3c622f

Browse files
authored
Remove base60 support for time.Duration (#166)
By popular demand (or rather lack thereof), remove support for the base60 representation of a time duration. There is no standard for this format and even the name is an attempt at naming something without clear industry basis for what this is called. Updates golang/go#71631
1 parent 925ba3f commit d3c622f

File tree

5 files changed

+60
-130
lines changed

5 files changed

+60
-130
lines changed

arshal.go

-4
Original file line numberDiff line numberDiff line change
@@ -155,8 +155,6 @@ var export = jsontext.Internal.Export(&internal.AllowInternalUse)
155155
// If the format is "sec", "milli", "micro", or "nano",
156156
// then the duration is encoded as a JSON number of the number of seconds
157157
// (or milliseconds, microseconds, or nanoseconds) in the duration.
158-
// If the format is "base60", it is encoded as a JSON string
159-
// using the "H:MM:SS.SSSSSSSSS" representation.
160158
// If the format is "units", it uses [time.Duration.String].
161159
//
162160
// - All other Go types (e.g., complex numbers, channels, and functions)
@@ -385,8 +383,6 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro
385383
// If the format is "sec", "milli", "micro", or "nano",
386384
// then the duration is decoded from a JSON number of the number of seconds
387385
// (or milliseconds, microseconds, or nanoseconds) in the duration.
388-
// If the format is "base60", it is decoded from a JSON string
389-
// using the "H:MM:SS.SSSSSSSSS" representation.
390386
// If the format is "units", it uses [time.ParseDuration].
391387
//
392388
// - All other Go types (e.g., complex numbers, channels, and functions)

arshal_test.go

+2-5
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,6 @@ type (
373373
D8 time.Duration `json:",string,format:micro"`
374374
D9 time.Duration `json:",format:nano"`
375375
D10 time.Duration `json:",string,format:nano"`
376-
D11 time.Duration `json:",format:base60"`
377376
}
378377
structTimeFormat struct {
379378
T1 time.Time
@@ -4373,7 +4372,6 @@ func TestMarshal(t *testing.T) {
43734372
12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond,
43744373
12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond,
43754374
12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond,
4376-
12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond,
43774375
},
43784376
want: `{
43794377
"D1": "12h34m56.078090012s",
@@ -4385,8 +4383,7 @@ func TestMarshal(t *testing.T) {
43854383
"D7": 45296078090.012,
43864384
"D8": "45296078090.012",
43874385
"D9": 45296078090012,
4388-
"D10": "45296078090012",
4389-
"D11": "12:34:56.078090012"
4386+
"D10": "45296078090012"
43904387
}`,
43914388
}, {
43924389
name: jsontest.Name("Duration/Format/Legacy"),
@@ -4395,7 +4392,7 @@ func TestMarshal(t *testing.T) {
43954392
D1: 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond,
43964393
D2: 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond,
43974394
},
4398-
want: `{"D1":45296078090012,"D2":"12h34m56.078090012s","D3":0,"D4":"0","D5":0,"D6":"0","D7":0,"D8":"0","D9":0,"D10":"0","D11":"0:00:00"}`,
4395+
want: `{"D1":45296078090012,"D2":"12h34m56.078090012s","D3":0,"D4":"0","D5":0,"D6":"0","D7":0,"D8":"0","D9":0,"D10":"0"}`,
43994396
}, {
44004397
name: jsontest.Name("Duration/MapKey"),
44014398
in: map[time.Duration]string{time.Second: ""},

arshal_time.go

-46
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,6 @@ type durationArshaler struct {
198198
// - 0 uses time.Duration.String
199199
// - 1e0, 1e3, 1e6, or 1e9 use a decimal encoding of the duration as
200200
// nanoseconds, microseconds, milliseconds, or seconds.
201-
// - 60 uses a "H:MM:SS.SSSSSSSSS" encoding
202201
base uint64
203202
}
204203

@@ -214,8 +213,6 @@ func (a *durationArshaler) initFormat(format string) (ok bool) {
214213
a.base = 1e3
215214
case "nano":
216215
a.base = 1e0
217-
case "base60": // see https://en.wikipedia.org/wiki/Sexagesimal#Modern_usage
218-
a.base = 60
219216
default:
220217
return false
221218
}
@@ -230,8 +227,6 @@ func (a *durationArshaler) appendMarshal(b []byte) ([]byte, error) {
230227
switch a.base {
231228
case 0:
232229
return append(b, a.td.String()...), nil
233-
case 60:
234-
return appendDurationBase60(b, a.td), nil
235230
default:
236231
return appendDurationBase10(b, a.td, a.base), nil
237232
}
@@ -241,8 +236,6 @@ func (a *durationArshaler) unmarshal(b []byte) (err error) {
241236
switch a.base {
242237
case 0:
243238
a.td, err = time.ParseDuration(string(b))
244-
case 60:
245-
a.td, err = parseDurationBase60(b)
246239
default:
247240
a.td, err = parseDurationBase10(b, a.base)
248241
}
@@ -433,45 +426,6 @@ func parseDurationBase10(b []byte, pow10 uint64) (time.Duration, error) {
433426
}
434427
}
435428

436-
// appendDurationBase60 appends d formatted with H:MM:SS.SSS notation.
437-
func appendDurationBase60(b []byte, d time.Duration) []byte {
438-
b, n := mayAppendDurationSign(b, d) // append sign
439-
n, nsec := bits.Div64(0, n, 1e9) // compute nsec field
440-
n, sec := bits.Div64(0, n, 60) // compute sec field
441-
hour, min := bits.Div64(0, n, 60) // compute hour and min fields
442-
b = strconv.AppendUint(b, hour, 10) // append hour field
443-
b = append(b, ':', '0'+byte(min/10), '0'+byte(min%10)) // append min field
444-
b = append(b, ':', '0'+byte(sec/10), '0'+byte(sec%10)) // append sec field
445-
return appendFracBase10(b, nsec, 1e9) // append nsec field
446-
}
447-
448-
// parseDurationBase60 parses d formatted with H:MM:SS.SSS notation.
449-
// The exact grammar is `-?(0|[1-9][0-9]*):[0-5][0-9]:[0-5][0-9]([.][0-9]+)?`.
450-
func parseDurationBase60(b []byte) (time.Duration, error) {
451-
checkBase60 := func(b []byte) bool {
452-
return len(b) == 2 && ('0' <= b[0] && b[0] <= '5') && '0' <= b[1] && b[1] <= '9'
453-
}
454-
suffix, neg := consumeSign(b) // consume sign
455-
hourBytes, suffix := bytesCutByte(suffix, ':', false) // consume hour field
456-
minBytes, suffix := bytesCutByte(suffix, ':', false) // consume min field
457-
secBytes, nsecBytes := bytesCutByte(suffix, '.', true) // consume sec and nsec fields
458-
hour, okHour := jsonwire.ParseUint(hourBytes) // parse hour field; may overflow
459-
min := parseDec2(minBytes) // parse min field
460-
sec := parseDec2(secBytes) // parse sec field
461-
nsec, okNsec := parseFracBase10(nsecBytes, 1e9) // parse nsec field
462-
n := uint64(min)*60*1e9 + uint64(sec)*1e9 + uint64(nsec) // cannot overflow
463-
hi, lo := bits.Mul64(hour, 60*60*1e9) // overflow if hi > 0
464-
sum, co := bits.Add64(lo, n, 0) // overflow if co > 0
465-
switch d := mayApplyDurationSign(sum, neg); { // overflow if neg != (d < 0)
466-
case (!okHour && hour != math.MaxUint64) || !checkBase60(minBytes) || !checkBase60(secBytes) || !okNsec:
467-
return 0, fmt.Errorf("invalid duration %q: %w", b, strconv.ErrSyntax)
468-
case !okHour || hi > 0 || co > 0 || neg != (d < 0):
469-
return 0, fmt.Errorf("invalid duration %q: %w", b, strconv.ErrRange)
470-
default:
471-
return d, nil
472-
}
473-
}
474-
475429
// mayAppendDurationSign appends a negative sign if n is negative.
476430
func mayAppendDurationSign(b []byte, d time.Duration) ([]byte, uint64) {
477431
if d < 0 {

arshal_time_test.go

+57-71
Original file line numberDiff line numberDiff line change
@@ -26,64 +26,63 @@ var formatDurationTestdata = []struct {
2626
base10Milli string
2727
base10Micro string
2828
base10Nano string
29-
base60 string
3029
}{
31-
{math.MaxInt64, "9223372036.854775807", "9223372036854.775807", "9223372036854775.807", "9223372036854775807", "2562047:47:16.854775807"},
32-
{1e12 + 1e12, "2000", "2000000", "2000000000", "2000000000000", "0:33:20"},
33-
{1e12 + 1e11, "1100", "1100000", "1100000000", "1100000000000", "0:18:20"},
34-
{1e12 + 1e10, "1010", "1010000", "1010000000", "1010000000000", "0:16:50"},
35-
{1e12 + 1e9, "1001", "1001000", "1001000000", "1001000000000", "0:16:41"},
36-
{1e12 + 1e8, "1000.1", "1000100", "1000100000", "1000100000000", "0:16:40.1"},
37-
{1e12 + 1e7, "1000.01", "1000010", "1000010000", "1000010000000", "0:16:40.01"},
38-
{1e12 + 1e6, "1000.001", "1000001", "1000001000", "1000001000000", "0:16:40.001"},
39-
{1e12 + 1e5, "1000.0001", "1000000.1", "1000000100", "1000000100000", "0:16:40.0001"},
40-
{1e12 + 1e4, "1000.00001", "1000000.01", "1000000010", "1000000010000", "0:16:40.00001"},
41-
{1e12 + 1e3, "1000.000001", "1000000.001", "1000000001", "1000000001000", "0:16:40.000001"},
42-
{1e12 + 1e2, "1000.0000001", "1000000.0001", "1000000000.1", "1000000000100", "0:16:40.0000001"},
43-
{1e12 + 1e1, "1000.00000001", "1000000.00001", "1000000000.01", "1000000000010", "0:16:40.00000001"},
44-
{1e12 + 1e0, "1000.000000001", "1000000.000001", "1000000000.001", "1000000000001", "0:16:40.000000001"},
45-
{+(1e9 + 1), "1.000000001", "1000.000001", "1000000.001", "1000000001", "0:00:01.000000001"},
46-
{+(1e9), "1", "1000", "1000000", "1000000000", "0:00:01"},
47-
{+(1e9 - 1), "0.999999999", "999.999999", "999999.999", "999999999", "0:00:00.999999999"},
48-
{+100000000, "0.1", "100", "100000", "100000000", "0:00:00.1"},
49-
{+120000000, "0.12", "120", "120000", "120000000", "0:00:00.12"},
50-
{+123000000, "0.123", "123", "123000", "123000000", "0:00:00.123"},
51-
{+123400000, "0.1234", "123.4", "123400", "123400000", "0:00:00.1234"},
52-
{+123450000, "0.12345", "123.45", "123450", "123450000", "0:00:00.12345"},
53-
{+123456000, "0.123456", "123.456", "123456", "123456000", "0:00:00.123456"},
54-
{+123456700, "0.1234567", "123.4567", "123456.7", "123456700", "0:00:00.1234567"},
55-
{+123456780, "0.12345678", "123.45678", "123456.78", "123456780", "0:00:00.12345678"},
56-
{+123456789, "0.123456789", "123.456789", "123456.789", "123456789", "0:00:00.123456789"},
57-
{+12345678, "0.012345678", "12.345678", "12345.678", "12345678", "0:00:00.012345678"},
58-
{+1234567, "0.001234567", "1.234567", "1234.567", "1234567", "0:00:00.001234567"},
59-
{+123456, "0.000123456", "0.123456", "123.456", "123456", "0:00:00.000123456"},
60-
{+12345, "0.000012345", "0.012345", "12.345", "12345", "0:00:00.000012345"},
61-
{+1234, "0.000001234", "0.001234", "1.234", "1234", "0:00:00.000001234"},
62-
{+123, "0.000000123", "0.000123", "0.123", "123", "0:00:00.000000123"},
63-
{+12, "0.000000012", "0.000012", "0.012", "12", "0:00:00.000000012"},
64-
{+1, "0.000000001", "0.000001", "0.001", "1", "0:00:00.000000001"},
65-
{0, "0", "0", "0", "0", "0:00:00"},
66-
{-1, "-0.000000001", "-0.000001", "-0.001", "-1", "-0:00:00.000000001"},
67-
{-12, "-0.000000012", "-0.000012", "-0.012", "-12", "-0:00:00.000000012"},
68-
{-123, "-0.000000123", "-0.000123", "-0.123", "-123", "-0:00:00.000000123"},
69-
{-1234, "-0.000001234", "-0.001234", "-1.234", "-1234", "-0:00:00.000001234"},
70-
{-12345, "-0.000012345", "-0.012345", "-12.345", "-12345", "-0:00:00.000012345"},
71-
{-123456, "-0.000123456", "-0.123456", "-123.456", "-123456", "-0:00:00.000123456"},
72-
{-1234567, "-0.001234567", "-1.234567", "-1234.567", "-1234567", "-0:00:00.001234567"},
73-
{-12345678, "-0.012345678", "-12.345678", "-12345.678", "-12345678", "-0:00:00.012345678"},
74-
{-123456789, "-0.123456789", "-123.456789", "-123456.789", "-123456789", "-0:00:00.123456789"},
75-
{-123456780, "-0.12345678", "-123.45678", "-123456.78", "-123456780", "-0:00:00.12345678"},
76-
{-123456700, "-0.1234567", "-123.4567", "-123456.7", "-123456700", "-0:00:00.1234567"},
77-
{-123456000, "-0.123456", "-123.456", "-123456", "-123456000", "-0:00:00.123456"},
78-
{-123450000, "-0.12345", "-123.45", "-123450", "-123450000", "-0:00:00.12345"},
79-
{-123400000, "-0.1234", "-123.4", "-123400", "-123400000", "-0:00:00.1234"},
80-
{-123000000, "-0.123", "-123", "-123000", "-123000000", "-0:00:00.123"},
81-
{-120000000, "-0.12", "-120", "-120000", "-120000000", "-0:00:00.12"},
82-
{-100000000, "-0.1", "-100", "-100000", "-100000000", "-0:00:00.1"},
83-
{-(1e9 - 1), "-0.999999999", "-999.999999", "-999999.999", "-999999999", "-0:00:00.999999999"},
84-
{-(1e9), "-1", "-1000", "-1000000", "-1000000000", "-0:00:01"},
85-
{-(1e9 + 1), "-1.000000001", "-1000.000001", "-1000000.001", "-1000000001", "-0:00:01.000000001"},
86-
{math.MinInt64, "-9223372036.854775808", "-9223372036854.775808", "-9223372036854775.808", "-9223372036854775808", "-2562047:47:16.854775808"},
30+
{math.MaxInt64, "9223372036.854775807", "9223372036854.775807", "9223372036854775.807", "9223372036854775807"},
31+
{1e12 + 1e12, "2000", "2000000", "2000000000", "2000000000000"},
32+
{1e12 + 1e11, "1100", "1100000", "1100000000", "1100000000000"},
33+
{1e12 + 1e10, "1010", "1010000", "1010000000", "1010000000000"},
34+
{1e12 + 1e9, "1001", "1001000", "1001000000", "1001000000000"},
35+
{1e12 + 1e8, "1000.1", "1000100", "1000100000", "1000100000000"},
36+
{1e12 + 1e7, "1000.01", "1000010", "1000010000", "1000010000000"},
37+
{1e12 + 1e6, "1000.001", "1000001", "1000001000", "1000001000000"},
38+
{1e12 + 1e5, "1000.0001", "1000000.1", "1000000100", "1000000100000"},
39+
{1e12 + 1e4, "1000.00001", "1000000.01", "1000000010", "1000000010000"},
40+
{1e12 + 1e3, "1000.000001", "1000000.001", "1000000001", "1000000001000"},
41+
{1e12 + 1e2, "1000.0000001", "1000000.0001", "1000000000.1", "1000000000100"},
42+
{1e12 + 1e1, "1000.00000001", "1000000.00001", "1000000000.01", "1000000000010"},
43+
{1e12 + 1e0, "1000.000000001", "1000000.000001", "1000000000.001", "1000000000001"},
44+
{+(1e9 + 1), "1.000000001", "1000.000001", "1000000.001", "1000000001"},
45+
{+(1e9), "1", "1000", "1000000", "1000000000"},
46+
{+(1e9 - 1), "0.999999999", "999.999999", "999999.999", "999999999"},
47+
{+100000000, "0.1", "100", "100000", "100000000"},
48+
{+120000000, "0.12", "120", "120000", "120000000"},
49+
{+123000000, "0.123", "123", "123000", "123000000"},
50+
{+123400000, "0.1234", "123.4", "123400", "123400000"},
51+
{+123450000, "0.12345", "123.45", "123450", "123450000"},
52+
{+123456000, "0.123456", "123.456", "123456", "123456000"},
53+
{+123456700, "0.1234567", "123.4567", "123456.7", "123456700"},
54+
{+123456780, "0.12345678", "123.45678", "123456.78", "123456780"},
55+
{+123456789, "0.123456789", "123.456789", "123456.789", "123456789"},
56+
{+12345678, "0.012345678", "12.345678", "12345.678", "12345678"},
57+
{+1234567, "0.001234567", "1.234567", "1234.567", "1234567"},
58+
{+123456, "0.000123456", "0.123456", "123.456", "123456"},
59+
{+12345, "0.000012345", "0.012345", "12.345", "12345"},
60+
{+1234, "0.000001234", "0.001234", "1.234", "1234"},
61+
{+123, "0.000000123", "0.000123", "0.123", "123"},
62+
{+12, "0.000000012", "0.000012", "0.012", "12"},
63+
{+1, "0.000000001", "0.000001", "0.001", "1"},
64+
{0, "0", "0", "0", "0"},
65+
{-1, "-0.000000001", "-0.000001", "-0.001", "-1"},
66+
{-12, "-0.000000012", "-0.000012", "-0.012", "-12"},
67+
{-123, "-0.000000123", "-0.000123", "-0.123", "-123"},
68+
{-1234, "-0.000001234", "-0.001234", "-1.234", "-1234"},
69+
{-12345, "-0.000012345", "-0.012345", "-12.345", "-12345"},
70+
{-123456, "-0.000123456", "-0.123456", "-123.456", "-123456"},
71+
{-1234567, "-0.001234567", "-1.234567", "-1234.567", "-1234567"},
72+
{-12345678, "-0.012345678", "-12.345678", "-12345.678", "-12345678"},
73+
{-123456789, "-0.123456789", "-123.456789", "-123456.789", "-123456789"},
74+
{-123456780, "-0.12345678", "-123.45678", "-123456.78", "-123456780"},
75+
{-123456700, "-0.1234567", "-123.4567", "-123456.7", "-123456700"},
76+
{-123456000, "-0.123456", "-123.456", "-123456", "-123456000"},
77+
{-123450000, "-0.12345", "-123.45", "-123450", "-123450000"},
78+
{-123400000, "-0.1234", "-123.4", "-123400", "-123400000"},
79+
{-123000000, "-0.123", "-123", "-123000", "-123000000"},
80+
{-120000000, "-0.12", "-120", "-120000", "-120000000"},
81+
{-100000000, "-0.1", "-100", "-100000", "-100000000"},
82+
{-(1e9 - 1), "-0.999999999", "-999.999999", "-999999.999", "-999999999"},
83+
{-(1e9), "-1", "-1000", "-1000000", "-1000000000"},
84+
{-(1e9 + 1), "-1.000000001", "-1000.000001", "-1000000.001", "-1000000001"},
85+
{math.MinInt64, "-9223372036.854775808", "-9223372036854.775808", "-9223372036854775.808", "-9223372036854775808"},
8786
}
8887

8988
func TestFormatDuration(t *testing.T) {
@@ -106,7 +105,6 @@ func TestFormatDuration(t *testing.T) {
106105
check(tt.td, tt.base10Milli, 1e6)
107106
check(tt.td, tt.base10Micro, 1e3)
108107
check(tt.td, tt.base10Nano, 1e0)
109-
check(tt.td, tt.base60, 60)
110108
}
111109
}
112110

@@ -139,18 +137,6 @@ var parseDurationTestdata = []struct {
139137
{"-1.0009", 1e3, -time.Microsecond, false},
140138
{"-1.0000009", 1e6, -time.Millisecond, false},
141139
{"-1.0000000009", 1e9, -time.Second, false},
142-
{"1:23:45", 60, time.Hour + 23*time.Minute + 45*time.Second, false},
143-
{"1:60:45", 60, 0, true},
144-
{"1:23:60", 60, 0, true},
145-
{"1:23:45.", 60, 0, true},
146-
{"1:23:45.0", 60, time.Hour + 23*time.Minute + 45*time.Second, false},
147-
{"1:23:45.1234567899", 60, time.Hour + 23*time.Minute + 45*time.Second + 123456789*time.Nanosecond, false},
148-
{"1:23:45.123456789x", 60, 0, true},
149-
{"23:45", 60, 0, true},
150-
{"45", 60, 0, true},
151-
{"00:00:00", 60, 0, true},
152-
{"2562047:47:16.854775808", 60, 0, true},
153-
{"2562048:00:00", 60, 0, true},
154140
}
155141

156142
func TestParseDuration(t *testing.T) {
@@ -173,7 +159,7 @@ func FuzzFormatDuration(f *testing.F) {
173159
}
174160
f.Fuzz(func(t *testing.T, want int64) {
175161
var buf []byte
176-
for _, base := range [...]uint64{1e0, 1e3, 1e6, 1e9, 60} {
162+
for _, base := range [...]uint64{1e0, 1e3, 1e6, 1e9} {
177163
a := durationArshaler{td: time.Duration(want), base: base}
178164
buf, _ = a.appendMarshal(buf[:0])
179165
switch err := a.unmarshal(buf); {

example_test.go

+1-4
Original file line numberDiff line numberDiff line change
@@ -410,7 +410,6 @@ func Example_formatFlags() {
410410
TimeUnixSec time.Time `json:",format:unix"`
411411
DurationSecs time.Duration `json:",format:sec"`
412412
DurationNanos time.Duration `json:",format:nano"`
413-
DurationBase60 time.Duration `json:",format:base60"`
414413
}{
415414
BytesBase64: []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
416415
BytesHex: [8]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
@@ -422,7 +421,6 @@ func Example_formatFlags() {
422421
TimeUnixSec: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
423422
DurationSecs: 12*time.Hour + 34*time.Minute + 56*time.Second + 7*time.Millisecond + 8*time.Microsecond + 9*time.Nanosecond,
424423
DurationNanos: 12*time.Hour + 34*time.Minute + 56*time.Second + 7*time.Millisecond + 8*time.Microsecond + 9*time.Nanosecond,
425-
DurationBase60: 12*time.Hour + 34*time.Minute + 56*time.Second + 7*time.Millisecond + 8*time.Microsecond + 9*time.Nanosecond,
426424
}
427425

428426
b, err := json.Marshal(&value)
@@ -452,8 +450,7 @@ func Example_formatFlags() {
452450
// "TimeDateOnly": "2000-01-01",
453451
// "TimeUnixSec": 946684800,
454452
// "DurationSecs": 45296.007008009,
455-
// "DurationNanos": 45296007008009,
456-
// "DurationBase60": "12:34:56.007008009"
453+
// "DurationNanos": 45296007008009
457454
// }
458455
}
459456

0 commit comments

Comments
 (0)