From 4cc12e35c8ab4b6576a752141d0edabe3c540297 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 8 Jun 2026 14:22:51 +0100 Subject: [PATCH 1/4] model: Add BenchmarkUnmarshalTime Signed-off-by: Bryan Boreham --- model/time_test.go | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/model/time_test.go b/model/time_test.go index a4e9069f1..3ab0994a3 100644 --- a/model/time_test.go +++ b/model/time_test.go @@ -460,3 +460,17 @@ func BenchmarkParseDuration(b *testing.B) { require.NoError(b, err) } } + +func BenchmarkUnmarshalTime(b *testing.B) { + cases := []string{"1780924784", "1780924784.01", "1780924784.001"} + + for _, c := range cases { + b.Run(c, func(b *testing.B) { + var t Time + data := []byte(c) + for b.Loop() { + _ = t.UnmarshalJSON(data) + } + }) + } +} From 98ca625caff21ab45c0aef3f532140d321a99368 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 8 Jun 2026 15:10:44 +0100 Subject: [PATCH 2/4] model: reduce allocations in Time.UnmarshalJSON Use strings.Cut instead of strings.Split. Signed-off-by: Bryan Boreham --- model/time.go | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/model/time.go b/model/time.go index 1730b0fdc..e1d2e2e0e 100644 --- a/model/time.go +++ b/model/time.go @@ -123,44 +123,39 @@ func (t Time) MarshalJSON() ([]byte, error) { // UnmarshalJSON implements the json.Unmarshaler interface. func (t *Time) UnmarshalJSON(b []byte) error { - p := strings.Split(string(b), ".") - switch len(p) { - case 1: - v, err := strconv.ParseInt(p[0], 10, 64) + base, frac, found := strings.Cut(string(b), ".") + if !found { + v, err := strconv.ParseInt(base, 10, 64) if err != nil { return err } *t = Time(v * second) - - case 2: - v, err := strconv.ParseInt(p[0], 10, 64) + } else { + v, err := strconv.ParseInt(base, 10, 64) if err != nil { return err } v *= second - prec := dotPrecision - len(p[1]) + prec := dotPrecision - len(frac) if prec < 0 { - p[1] = p[1][:dotPrecision] + frac = frac[:dotPrecision] } else if prec > 0 { - p[1] += strings.Repeat("0", prec) + frac += strings.Repeat("0", prec) } - va, err := strconv.ParseInt(p[1], 10, 32) + va, err := strconv.ParseInt(frac, 10, 32) if err != nil { return err } // If the value was something like -0.1 the negative is lost in the // parsing because of the leading zero, this ensures that we capture it. - if len(p[0]) > 0 && p[0][0] == '-' && v+va > 0 { + if len(base) > 0 && base[0] == '-' && v+va > 0 { *t = Time(v+va) * -1 } else { *t = Time(v + va) } - - default: - return fmt.Errorf("invalid time %q", string(b)) } return nil } From 8b99aea5d8085de523f583ef1b73a832c44fbae2 Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 8 Jun 2026 15:22:49 +0100 Subject: [PATCH 3/4] model: remove allocation in Time.UnmarshalJSON We don't need to modify the string version of the number via a memory allocation; it's much faster to modify the integer version. Signed-off-by: Bryan Boreham --- model/time.go | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/model/time.go b/model/time.go index e1d2e2e0e..77dd43121 100644 --- a/model/time.go +++ b/model/time.go @@ -140,14 +140,17 @@ func (t *Time) UnmarshalJSON(b []byte) error { prec := dotPrecision - len(frac) if prec < 0 { frac = frac[:dotPrecision] - } else if prec > 0 { - frac += strings.Repeat("0", prec) } - va, err := strconv.ParseInt(frac, 10, 32) if err != nil { return err } + switch prec { + case 1: + va *= 10 + case 2: + va *= 100 + } // If the value was something like -0.1 the negative is lost in the // parsing because of the leading zero, this ensures that we capture it. From d6a7988672c3c081cfb74cf757bd131e3a6e65eb Mon Sep 17 00:00:00 2001 From: Bryan Boreham Date: Mon, 8 Jun 2026 15:23:42 +0100 Subject: [PATCH 4/4] model: fix Time.UnmarshalJSON for larger negative numbers Signed-off-by: Bryan Boreham --- model/time.go | 10 +++------- model/time_test.go | 5 +++++ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/model/time.go b/model/time.go index 77dd43121..d053ada77 100644 --- a/model/time.go +++ b/model/time.go @@ -135,7 +135,6 @@ func (t *Time) UnmarshalJSON(b []byte) error { if err != nil { return err } - v *= second prec := dotPrecision - len(frac) if prec < 0 { @@ -152,13 +151,10 @@ func (t *Time) UnmarshalJSON(b []byte) error { va *= 100 } - // If the value was something like -0.1 the negative is lost in the - // parsing because of the leading zero, this ensures that we capture it. - if len(base) > 0 && base[0] == '-' && v+va > 0 { - *t = Time(v+va) * -1 - } else { - *t = Time(v + va) + if len(base) > 0 && base[0] == '-' { + va = -va } + *t = Time(v*second + va) } return nil } diff --git a/model/time_test.go b/model/time_test.go index 3ab0994a3..7633b46ef 100644 --- a/model/time_test.go +++ b/model/time_test.go @@ -432,6 +432,11 @@ func TestTimeJSON(t *testing.T) { }{ {Time(1), `0.001`}, {Time(-1), `-0.001`}, + {Time(1001), `1.001`}, + {Time(-1001), `-1.001`}, + {Time(123000), `123`}, + {Time(123100), `123.1`}, + {Time(123010), `123.01`}, } for i, test := range tests {