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
This commit is contained in:
Thomas Pelletier
2021-12-26 17:05:10 +01:00
committed by GitHub
parent 177b4a5e53
commit 8ce5c3d78f
3 changed files with 31 additions and 14 deletions
+14 -5
View File
@@ -232,6 +232,7 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
if len(b) >= 1 && b[0] == '.' { if len(b) >= 1 && b[0] == '.' {
frac := 0 frac := 0
precision := 0
digits := 0 digits := 0
for i, c := range b[1:] { for i, c := range b[1:] {
@@ -241,23 +242,31 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
} }
break break
} }
digits++
const maxFracPrecision = 9 const maxFracPrecision = 9
if i >= maxFracPrecision { 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 *= 10
frac += int(c - '0') frac += int(c - '0')
digits++ precision++
} }
if digits == 0 { if precision == 0 {
return t, nil, newDecodeError(b[:1], "nanoseconds need at least one digit") return t, nil, newDecodeError(b[:1], "nanoseconds need at least one digit")
} }
t.Nanosecond = frac * nspow[digits] t.Nanosecond = frac * nspow[precision]
t.Precision = digits t.Precision = precision
return t, b[1+digits:], nil return t, b[1+digits:], nil
} }
+2 -1
View File
@@ -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 // 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* // 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 // Empty tables decoded in an interface{} create an empty initialized
// map[string]interface{}. // map[string]interface{}.
+15 -8
View File
@@ -2311,6 +2311,21 @@ func TestIssue708(t *testing.T) {
require.Equal(t, map[string]string{"0": ""}, v) 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) { func TestUnmarshalDecodeErrors(t *testing.T) {
examples := []struct { examples := []struct {
desc string desc string
@@ -2325,18 +2340,10 @@ func TestUnmarshalDecodeErrors(t *testing.T) {
desc: "local time with fractional", desc: "local time with fractional",
data: `a = 11:22:33.x`, 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", desc: "wrong time offset separator",
data: `a = 1979-05-27T00:32:00.-07:00`, 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", desc: "wrong time offset separator",
data: `a = 1979-05-27T00:32:00Z07:00`, data: `a = 1979-05-27T00:32:00Z07:00`,