Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove base60 support for time.Duration #166

Merged
merged 1 commit into from
Feb 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions arshal.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,6 @@ var export = jsontext.Internal.Export(&internal.AllowInternalUse)
// If the format is "sec", "milli", "micro", or "nano",
// then the duration is encoded as a JSON number of the number of seconds
// (or milliseconds, microseconds, or nanoseconds) in the duration.
// If the format is "base60", it is encoded as a JSON string
// using the "H:MM:SS.SSSSSSSSS" representation.
// If the format is "units", it uses [time.Duration.String].
//
// - All other Go types (e.g., complex numbers, channels, and functions)
Expand Down Expand Up @@ -385,8 +383,6 @@ func marshalEncode(out *jsontext.Encoder, in any, mo *jsonopts.Struct) (err erro
// If the format is "sec", "milli", "micro", or "nano",
// then the duration is decoded from a JSON number of the number of seconds
// (or milliseconds, microseconds, or nanoseconds) in the duration.
// If the format is "base60", it is decoded from a JSON string
// using the "H:MM:SS.SSSSSSSSS" representation.
// If the format is "units", it uses [time.ParseDuration].
//
// - All other Go types (e.g., complex numbers, channels, and functions)
Expand Down
7 changes: 2 additions & 5 deletions arshal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,6 @@ type (
D8 time.Duration `json:",string,format:micro"`
D9 time.Duration `json:",format:nano"`
D10 time.Duration `json:",string,format:nano"`
D11 time.Duration `json:",format:base60"`
}
structTimeFormat struct {
T1 time.Time
Expand Down Expand Up @@ -4373,7 +4372,6 @@ func TestMarshal(t *testing.T) {
12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond,
12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond,
12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond,
12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond,
},
want: `{
"D1": "12h34m56.078090012s",
Expand All @@ -4385,8 +4383,7 @@ func TestMarshal(t *testing.T) {
"D7": 45296078090.012,
"D8": "45296078090.012",
"D9": 45296078090012,
"D10": "45296078090012",
"D11": "12:34:56.078090012"
"D10": "45296078090012"
}`,
}, {
name: jsontest.Name("Duration/Format/Legacy"),
Expand All @@ -4395,7 +4392,7 @@ func TestMarshal(t *testing.T) {
D1: 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond,
D2: 12*time.Hour + 34*time.Minute + 56*time.Second + 78*time.Millisecond + 90*time.Microsecond + 12*time.Nanosecond,
},
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"}`,
want: `{"D1":45296078090012,"D2":"12h34m56.078090012s","D3":0,"D4":"0","D5":0,"D6":"0","D7":0,"D8":"0","D9":0,"D10":"0"}`,
}, {
name: jsontest.Name("Duration/MapKey"),
in: map[time.Duration]string{time.Second: ""},
Expand Down
46 changes: 0 additions & 46 deletions arshal_time.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,6 @@ 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.
// - 60 uses a "H:MM:SS.SSSSSSSSS" encoding
base uint64
}

Expand All @@ -214,8 +213,6 @@ func (a *durationArshaler) initFormat(format string) (ok bool) {
a.base = 1e3
case "nano":
a.base = 1e0
case "base60": // see https://en.wikipedia.org/wiki/Sexagesimal#Modern_usage
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What a lost opportunity to call this format:sexagesimal 😂

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yea... imagine all the bad jokes we would have to deal with if it were called that...

a.base = 60
default:
return false
}
Expand All @@ -230,8 +227,6 @@ 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
}
Expand All @@ -241,8 +236,6 @@ 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)
}
Expand Down Expand Up @@ -433,45 +426,6 @@ func parseDurationBase10(b []byte, pow10 uint64) (time.Duration, error) {
}
}

// 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, 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 {
Expand Down
128 changes: 57 additions & 71 deletions arshal_time_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,64 +26,63 @@ var formatDurationTestdata = []struct {
base10Milli string
base10Micro string
base10Nano string
base60 string
}{
{math.MaxInt64, "9223372036.854775807", "9223372036854.775807", "9223372036854775.807", "9223372036854775807", "2562047:47:16.854775807"},
{1e12 + 1e12, "2000", "2000000", "2000000000", "2000000000000", "0:33:20"},
{1e12 + 1e11, "1100", "1100000", "1100000000", "1100000000000", "0:18:20"},
{1e12 + 1e10, "1010", "1010000", "1010000000", "1010000000000", "0:16:50"},
{1e12 + 1e9, "1001", "1001000", "1001000000", "1001000000000", "0:16:41"},
{1e12 + 1e8, "1000.1", "1000100", "1000100000", "1000100000000", "0:16:40.1"},
{1e12 + 1e7, "1000.01", "1000010", "1000010000", "1000010000000", "0:16:40.01"},
{1e12 + 1e6, "1000.001", "1000001", "1000001000", "1000001000000", "0:16:40.001"},
{1e12 + 1e5, "1000.0001", "1000000.1", "1000000100", "1000000100000", "0:16:40.0001"},
{1e12 + 1e4, "1000.00001", "1000000.01", "1000000010", "1000000010000", "0:16:40.00001"},
{1e12 + 1e3, "1000.000001", "1000000.001", "1000000001", "1000000001000", "0:16:40.000001"},
{1e12 + 1e2, "1000.0000001", "1000000.0001", "1000000000.1", "1000000000100", "0:16:40.0000001"},
{1e12 + 1e1, "1000.00000001", "1000000.00001", "1000000000.01", "1000000000010", "0:16:40.00000001"},
{1e12 + 1e0, "1000.000000001", "1000000.000001", "1000000000.001", "1000000000001", "0:16:40.000000001"},
{+(1e9 + 1), "1.000000001", "1000.000001", "1000000.001", "1000000001", "0:00:01.000000001"},
{+(1e9), "1", "1000", "1000000", "1000000000", "0:00:01"},
{+(1e9 - 1), "0.999999999", "999.999999", "999999.999", "999999999", "0:00:00.999999999"},
{+100000000, "0.1", "100", "100000", "100000000", "0:00:00.1"},
{+120000000, "0.12", "120", "120000", "120000000", "0:00:00.12"},
{+123000000, "0.123", "123", "123000", "123000000", "0:00:00.123"},
{+123400000, "0.1234", "123.4", "123400", "123400000", "0:00:00.1234"},
{+123450000, "0.12345", "123.45", "123450", "123450000", "0:00:00.12345"},
{+123456000, "0.123456", "123.456", "123456", "123456000", "0:00:00.123456"},
{+123456700, "0.1234567", "123.4567", "123456.7", "123456700", "0:00:00.1234567"},
{+123456780, "0.12345678", "123.45678", "123456.78", "123456780", "0:00:00.12345678"},
{+123456789, "0.123456789", "123.456789", "123456.789", "123456789", "0:00:00.123456789"},
{+12345678, "0.012345678", "12.345678", "12345.678", "12345678", "0:00:00.012345678"},
{+1234567, "0.001234567", "1.234567", "1234.567", "1234567", "0:00:00.001234567"},
{+123456, "0.000123456", "0.123456", "123.456", "123456", "0:00:00.000123456"},
{+12345, "0.000012345", "0.012345", "12.345", "12345", "0:00:00.000012345"},
{+1234, "0.000001234", "0.001234", "1.234", "1234", "0:00:00.000001234"},
{+123, "0.000000123", "0.000123", "0.123", "123", "0:00:00.000000123"},
{+12, "0.000000012", "0.000012", "0.012", "12", "0:00:00.000000012"},
{+1, "0.000000001", "0.000001", "0.001", "1", "0:00:00.000000001"},
{0, "0", "0", "0", "0", "0:00:00"},
{-1, "-0.000000001", "-0.000001", "-0.001", "-1", "-0:00:00.000000001"},
{-12, "-0.000000012", "-0.000012", "-0.012", "-12", "-0:00:00.000000012"},
{-123, "-0.000000123", "-0.000123", "-0.123", "-123", "-0:00:00.000000123"},
{-1234, "-0.000001234", "-0.001234", "-1.234", "-1234", "-0:00:00.000001234"},
{-12345, "-0.000012345", "-0.012345", "-12.345", "-12345", "-0:00:00.000012345"},
{-123456, "-0.000123456", "-0.123456", "-123.456", "-123456", "-0:00:00.000123456"},
{-1234567, "-0.001234567", "-1.234567", "-1234.567", "-1234567", "-0:00:00.001234567"},
{-12345678, "-0.012345678", "-12.345678", "-12345.678", "-12345678", "-0:00:00.012345678"},
{-123456789, "-0.123456789", "-123.456789", "-123456.789", "-123456789", "-0:00:00.123456789"},
{-123456780, "-0.12345678", "-123.45678", "-123456.78", "-123456780", "-0:00:00.12345678"},
{-123456700, "-0.1234567", "-123.4567", "-123456.7", "-123456700", "-0:00:00.1234567"},
{-123456000, "-0.123456", "-123.456", "-123456", "-123456000", "-0:00:00.123456"},
{-123450000, "-0.12345", "-123.45", "-123450", "-123450000", "-0:00:00.12345"},
{-123400000, "-0.1234", "-123.4", "-123400", "-123400000", "-0:00:00.1234"},
{-123000000, "-0.123", "-123", "-123000", "-123000000", "-0:00:00.123"},
{-120000000, "-0.12", "-120", "-120000", "-120000000", "-0:00:00.12"},
{-100000000, "-0.1", "-100", "-100000", "-100000000", "-0:00:00.1"},
{-(1e9 - 1), "-0.999999999", "-999.999999", "-999999.999", "-999999999", "-0:00:00.999999999"},
{-(1e9), "-1", "-1000", "-1000000", "-1000000000", "-0:00:01"},
{-(1e9 + 1), "-1.000000001", "-1000.000001", "-1000000.001", "-1000000001", "-0:00:01.000000001"},
{math.MinInt64, "-9223372036.854775808", "-9223372036854.775808", "-9223372036854775.808", "-9223372036854775808", "-2562047:47:16.854775808"},
{math.MaxInt64, "9223372036.854775807", "9223372036854.775807", "9223372036854775.807", "9223372036854775807"},
{1e12 + 1e12, "2000", "2000000", "2000000000", "2000000000000"},
{1e12 + 1e11, "1100", "1100000", "1100000000", "1100000000000"},
{1e12 + 1e10, "1010", "1010000", "1010000000", "1010000000000"},
{1e12 + 1e9, "1001", "1001000", "1001000000", "1001000000000"},
{1e12 + 1e8, "1000.1", "1000100", "1000100000", "1000100000000"},
{1e12 + 1e7, "1000.01", "1000010", "1000010000", "1000010000000"},
{1e12 + 1e6, "1000.001", "1000001", "1000001000", "1000001000000"},
{1e12 + 1e5, "1000.0001", "1000000.1", "1000000100", "1000000100000"},
{1e12 + 1e4, "1000.00001", "1000000.01", "1000000010", "1000000010000"},
{1e12 + 1e3, "1000.000001", "1000000.001", "1000000001", "1000000001000"},
{1e12 + 1e2, "1000.0000001", "1000000.0001", "1000000000.1", "1000000000100"},
{1e12 + 1e1, "1000.00000001", "1000000.00001", "1000000000.01", "1000000000010"},
{1e12 + 1e0, "1000.000000001", "1000000.000001", "1000000000.001", "1000000000001"},
{+(1e9 + 1), "1.000000001", "1000.000001", "1000000.001", "1000000001"},
{+(1e9), "1", "1000", "1000000", "1000000000"},
{+(1e9 - 1), "0.999999999", "999.999999", "999999.999", "999999999"},
{+100000000, "0.1", "100", "100000", "100000000"},
{+120000000, "0.12", "120", "120000", "120000000"},
{+123000000, "0.123", "123", "123000", "123000000"},
{+123400000, "0.1234", "123.4", "123400", "123400000"},
{+123450000, "0.12345", "123.45", "123450", "123450000"},
{+123456000, "0.123456", "123.456", "123456", "123456000"},
{+123456700, "0.1234567", "123.4567", "123456.7", "123456700"},
{+123456780, "0.12345678", "123.45678", "123456.78", "123456780"},
{+123456789, "0.123456789", "123.456789", "123456.789", "123456789"},
{+12345678, "0.012345678", "12.345678", "12345.678", "12345678"},
{+1234567, "0.001234567", "1.234567", "1234.567", "1234567"},
{+123456, "0.000123456", "0.123456", "123.456", "123456"},
{+12345, "0.000012345", "0.012345", "12.345", "12345"},
{+1234, "0.000001234", "0.001234", "1.234", "1234"},
{+123, "0.000000123", "0.000123", "0.123", "123"},
{+12, "0.000000012", "0.000012", "0.012", "12"},
{+1, "0.000000001", "0.000001", "0.001", "1"},
{0, "0", "0", "0", "0"},
{-1, "-0.000000001", "-0.000001", "-0.001", "-1"},
{-12, "-0.000000012", "-0.000012", "-0.012", "-12"},
{-123, "-0.000000123", "-0.000123", "-0.123", "-123"},
{-1234, "-0.000001234", "-0.001234", "-1.234", "-1234"},
{-12345, "-0.000012345", "-0.012345", "-12.345", "-12345"},
{-123456, "-0.000123456", "-0.123456", "-123.456", "-123456"},
{-1234567, "-0.001234567", "-1.234567", "-1234.567", "-1234567"},
{-12345678, "-0.012345678", "-12.345678", "-12345.678", "-12345678"},
{-123456789, "-0.123456789", "-123.456789", "-123456.789", "-123456789"},
{-123456780, "-0.12345678", "-123.45678", "-123456.78", "-123456780"},
{-123456700, "-0.1234567", "-123.4567", "-123456.7", "-123456700"},
{-123456000, "-0.123456", "-123.456", "-123456", "-123456000"},
{-123450000, "-0.12345", "-123.45", "-123450", "-123450000"},
{-123400000, "-0.1234", "-123.4", "-123400", "-123400000"},
{-123000000, "-0.123", "-123", "-123000", "-123000000"},
{-120000000, "-0.12", "-120", "-120000", "-120000000"},
{-100000000, "-0.1", "-100", "-100000", "-100000000"},
{-(1e9 - 1), "-0.999999999", "-999.999999", "-999999.999", "-999999999"},
{-(1e9), "-1", "-1000", "-1000000", "-1000000000"},
{-(1e9 + 1), "-1.000000001", "-1000.000001", "-1000000.001", "-1000000001"},
{math.MinInt64, "-9223372036.854775808", "-9223372036854.775808", "-9223372036854775.808", "-9223372036854775808"},
}

func TestFormatDuration(t *testing.T) {
Expand All @@ -106,7 +105,6 @@ func TestFormatDuration(t *testing.T) {
check(tt.td, tt.base10Milli, 1e6)
check(tt.td, tt.base10Micro, 1e3)
check(tt.td, tt.base10Nano, 1e0)
check(tt.td, tt.base60, 60)
}
}

Expand Down Expand Up @@ -139,18 +137,6 @@ var parseDurationTestdata = []struct {
{"-1.0009", 1e3, -time.Microsecond, false},
{"-1.0000009", 1e6, -time.Millisecond, false},
{"-1.0000000009", 1e9, -time.Second, false},
{"1:23:45", 60, time.Hour + 23*time.Minute + 45*time.Second, false},
{"1:60:45", 60, 0, true},
{"1:23:60", 60, 0, true},
{"1:23:45.", 60, 0, true},
{"1:23:45.0", 60, time.Hour + 23*time.Minute + 45*time.Second, false},
{"1:23:45.1234567899", 60, time.Hour + 23*time.Minute + 45*time.Second + 123456789*time.Nanosecond, false},
{"1:23:45.123456789x", 60, 0, true},
{"23:45", 60, 0, true},
{"45", 60, 0, true},
{"00:00:00", 60, 0, true},
{"2562047:47:16.854775808", 60, 0, true},
{"2562048:00:00", 60, 0, true},
}

func TestParseDuration(t *testing.T) {
Expand All @@ -173,7 +159,7 @@ func FuzzFormatDuration(f *testing.F) {
}
f.Fuzz(func(t *testing.T, want int64) {
var buf []byte
for _, base := range [...]uint64{1e0, 1e3, 1e6, 1e9, 60} {
for _, base := range [...]uint64{1e0, 1e3, 1e6, 1e9} {
a := durationArshaler{td: time.Duration(want), base: base}
buf, _ = a.appendMarshal(buf[:0])
switch err := a.unmarshal(buf); {
Expand Down
5 changes: 1 addition & 4 deletions example_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,6 @@ func Example_formatFlags() {
TimeUnixSec time.Time `json:",format:unix"`
DurationSecs time.Duration `json:",format:sec"`
DurationNanos time.Duration `json:",format:nano"`
DurationBase60 time.Duration `json:",format:base60"`
}{
BytesBase64: []byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
BytesHex: [8]byte{0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef},
Expand All @@ -422,7 +421,6 @@ func Example_formatFlags() {
TimeUnixSec: time.Date(2000, 1, 1, 0, 0, 0, 0, time.UTC),
DurationSecs: 12*time.Hour + 34*time.Minute + 56*time.Second + 7*time.Millisecond + 8*time.Microsecond + 9*time.Nanosecond,
DurationNanos: 12*time.Hour + 34*time.Minute + 56*time.Second + 7*time.Millisecond + 8*time.Microsecond + 9*time.Nanosecond,
DurationBase60: 12*time.Hour + 34*time.Minute + 56*time.Second + 7*time.Millisecond + 8*time.Microsecond + 9*time.Nanosecond,
}

b, err := json.Marshal(&value)
Expand Down Expand Up @@ -452,8 +450,7 @@ func Example_formatFlags() {
// "TimeDateOnly": "2000-01-01",
// "TimeUnixSec": 946684800,
// "DurationSecs": 45296.007008009,
// "DurationNanos": 45296007008009,
// "DurationBase60": "12:34:56.007008009"
// "DurationNanos": 45296007008009
// }
}

Expand Down
Loading