From 8ce5c3d78fd2b889894d1f953a7c55774ba524a4 Mon Sep 17 00:00:00 2001 From: Thomas Pelletier Date: Sun, 26 Dec 2021 17:05:10 +0100 Subject: [PATCH] Decoder: time allows extra precision (#710) As discussed[1], this change allows times to provide precision beyond the nanosecond (nine digits fractional part). Extra precision is truncated according to the TOML specificiation. [1]: https://github.com/pelletier/go-toml/discussions/707 --- decode.go | 19 ++++++++++++++----- unmarshaler.go | 3 ++- unmarshaler_test.go | 23 +++++++++++++++-------- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/decode.go b/decode.go index 3d741ff..2bc5eda 100644 --- a/decode.go +++ b/decode.go @@ -232,6 +232,7 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { if len(b) >= 1 && b[0] == '.' { frac := 0 + precision := 0 digits := 0 for i, c := range b[1:] { @@ -241,23 +242,31 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) { } break } + digits++ const maxFracPrecision = 9 if i >= maxFracPrecision { - return t, nil, newDecodeError(b[i-1:i], "maximum precision for date time is nanosecond") + // go-toml allows decoding fractional seconds + // beyond the supported precision of 9 + // digits. It truncates the fractional component + // to the supported precision and ignores the + // remaining digits. + // + // https://github.com/pelletier/go-toml/discussions/707 + continue } frac *= 10 frac += int(c - '0') - digits++ + precision++ } - if digits == 0 { + if precision == 0 { return t, nil, newDecodeError(b[:1], "nanoseconds need at least one digit") } - t.Nanosecond = frac * nspow[digits] - t.Precision = digits + t.Nanosecond = frac * nspow[precision] + t.Precision = precision return t, b[1+digits:], nil } diff --git a/unmarshaler.go b/unmarshaler.go index 28dce4e..d3ff84c 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -60,7 +60,8 @@ func (d *Decoder) SetStrict(strict bool) *Decoder { // // When a TOML local date, time, or date-time is decoded into a time.Time, its // value is represented in time.Local timezone. Otherwise the approriate Local* -// structure is used. +// structure is used. For time values, precision up to the nanosecond is +// supported by truncating extra digits. // // Empty tables decoded in an interface{} create an empty initialized // map[string]interface{}. diff --git a/unmarshaler_test.go b/unmarshaler_test.go index fd780d6..59b6d51 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -2311,6 +2311,21 @@ func TestIssue708(t *testing.T) { require.Equal(t, map[string]string{"0": ""}, v) } +func TestIssue710(t *testing.T) { + v := map[string]toml.LocalTime{} + err := toml.Unmarshal([]byte(`0=00:00:00.0000000000`), &v) + require.NoError(t, err) + require.Equal(t, map[string]toml.LocalTime{"0": {Precision: 9}}, v) + v1 := map[string]toml.LocalTime{} + err = toml.Unmarshal([]byte(`0=00:00:00.0000000001`), &v1) + require.NoError(t, err) + require.Equal(t, map[string]toml.LocalTime{"0": {Precision: 9}}, v1) + v2 := map[string]toml.LocalTime{} + err = toml.Unmarshal([]byte(`0=00:00:00.1111111119`), &v2) + require.NoError(t, err) + require.Equal(t, map[string]toml.LocalTime{"0": {Nanosecond: 111111111, Precision: 9}}, v2) +} + func TestUnmarshalDecodeErrors(t *testing.T) { examples := []struct { desc string @@ -2325,18 +2340,10 @@ func TestUnmarshalDecodeErrors(t *testing.T) { desc: "local time with fractional", data: `a = 11:22:33.x`, }, - { - desc: "local time frac precision too large", - data: `a = 2021-05-09T11:22:33.99999999999`, - }, { desc: "wrong time offset separator", data: `a = 1979-05-27T00:32:00.-07:00`, }, - { - desc: "missing fractional with tz", - data: `a = 2021-05-09T11:22:33.99999999999`, - }, { desc: "wrong time offset separator", data: `a = 1979-05-27T00:32:00Z07:00`,