decoder: fix time fractional parsing

This commit is contained in:
Thomas Pelletier
2021-04-20 23:16:08 -04:00
parent 9b67e40640
commit ee102a3528
4 changed files with 307 additions and 214 deletions
+31 -8
View File
@@ -136,7 +136,7 @@ func parseDateTime(b []byte) (time.Time, error) {
var ( var (
errParseLocalDateTimeWrongLength = errors.New( errParseLocalDateTimeWrongLength = errors.New(
"local datetimes are expected to have the format YYYY-MM-DDTHH:MM:SS[.NNNNNN]", "local datetimes are expected to have the format YYYY-MM-DDTHH:MM:SS[.NNNNNNNNN]",
) )
errParseLocalDateTimeWrongSeparator = errors.New("datetime separator is expected to be T or a space") errParseLocalDateTimeWrongSeparator = errors.New("datetime separator is expected to be T or a space")
) )
@@ -144,8 +144,8 @@ var (
func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) { func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) {
var dt LocalDateTime var dt LocalDateTime
const localDateTimeByteLen = 11 const localDateTimeByteMinLen = 11
if len(b) < localDateTimeByteLen { if len(b) < localDateTimeByteMinLen {
return dt, nil, errParseLocalDateTimeWrongLength return dt, nil, errParseLocalDateTimeWrongLength
} }
@@ -207,18 +207,41 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
return t, nil, err return t, nil, err
} }
if len(b) >= 15 && b[8] == '.' { if len(b) >= 9 && b[8] == '.' {
t.Nanosecond, err = parseDecimalDigits(b[9:15]) frac := 0
if err != nil { digits := 0
return t, nil, err
for i, c := range b[9:] {
if !isDigit(c) {
if i == 0 {
return t, nil, newDecodeError(b[i:i+1], "need at least one digit after fraction point")
}
break
}
if i >= 9 {
return t, nil, newDecodeError(b[i:i+1], "maximum precision for date time is nanosecond")
}
frac *= 10
frac += int(c - '0')
digits++
} }
return t, b[15:], nil t.Nanosecond = frac * nanosecPower(digits)
return t, b[9+digits:], nil
} }
return t, b[8:], nil return t, b[8:], nil
} }
var nspow = []int{0, 1e8, 1e7, 1e6, 1e5, 1e4, 1e3, 1e2, 1e1, 1e0}
func nanosecPower(n int) int {
return nspow[n]
}
var ( var (
errParseFloatStartDot = errors.New("float cannot start with a dot") errParseFloatStartDot = errors.New("float cannot start with a dot")
errParseFloatEndDot = errors.New("float cannot end with a dot") errParseFloatEndDot = errors.New("float cannot end with a dot")
+204 -205
View File
@@ -1430,211 +1430,210 @@ func TestUnmarshalPreservesUnexportedFields(t *testing.T) {
}) })
} }
// func TestUnmarshalLocalDate(t *testing.T) {
//func TestUnmarshalLocalDate(t *testing.T) { t.Run("ToLocalDate", func(t *testing.T) {
// t.Run("ToLocalDate", func(t *testing.T) { type dateStruct struct {
// type dateStruct struct { Date toml.LocalDate
// Date toml.LocalDate }
// }
// doc := `date = 1979-05-27`
// doc := `date = 1979-05-27`
// var obj dateStruct
// var obj dateStruct
// err := toml.Unmarshal([]byte(doc), &obj)
// err := toml.Unmarshal([]byte(doc), &obj)
// if err != nil {
// if err != nil { t.Fatal(err)
// t.Fatal(err) }
// }
// if obj.Date.Year != 1979 {
// if obj.Date.Year != 1979 { t.Errorf("expected year 1979, got %d", obj.Date.Year)
// t.Errorf("expected year 1979, got %d", obj.Date.Year) }
// } if obj.Date.Month != 5 {
// if obj.Date.Month != 5 { t.Errorf("expected month 5, got %d", obj.Date.Month)
// t.Errorf("expected month 5, got %d", obj.Date.Month) }
// } if obj.Date.Day != 27 {
// if obj.Date.Day != 27 { t.Errorf("expected day 27, got %d", obj.Date.Day)
// t.Errorf("expected day 27, got %d", obj.Date.Day) }
// } })
// })
// t.Run("ToLocalDate", func(t *testing.T) {
// t.Run("ToLocalDate", func(t *testing.T) { type dateStruct struct {
// type dateStruct struct { Date time.Time
// Date time.Time }
// }
// doc := `date = 1979-05-27`
// doc := `date = 1979-05-27`
// var obj dateStruct
// var obj dateStruct
// err := toml.Unmarshal([]byte(doc), &obj)
// err := toml.Unmarshal([]byte(doc), &obj)
// if err != nil {
// if err != nil { t.Fatal(err)
// t.Fatal(err) }
// }
// if obj.Date.Year() != 1979 {
// if obj.Date.Year() != 1979 { t.Errorf("expected year 1979, got %d", obj.Date.Year())
// t.Errorf("expected year 1979, got %d", obj.Date.Year()) }
// } if obj.Date.Month() != 5 {
// if obj.Date.Month() != 5 { t.Errorf("expected month 5, got %d", obj.Date.Month())
// t.Errorf("expected month 5, got %d", obj.Date.Month()) }
// } if obj.Date.Day() != 27 {
// if obj.Date.Day() != 27 { t.Errorf("expected day 27, got %d", obj.Date.Day())
// t.Errorf("expected day 27, got %d", obj.Date.Day()) }
// } })
// }) }
//}
// func TestUnmarshalLocalDateTime(t *testing.T) {
//func TestUnmarshalLocalDateTime(t *testing.T) { examples := []struct {
// examples := []struct { name string
// name string in string
// in string out toml.LocalDateTime
// out toml.LocalDateTime }{
// }{ {
// { name: "normal",
// name: "normal", in: "1979-05-27T07:32:00",
// in: "1979-05-27T07:32:00", out: toml.LocalDateTime{
// out: toml.LocalDateTime{ Date: toml.LocalDate{
// Date: toml.LocalDate{ Year: 1979,
// Year: 1979, Month: 5,
// Month: 5, Day: 27,
// Day: 27, },
// }, Time: toml.LocalTime{
// Time: toml.LocalTime{ Hour: 7,
// Hour: 7, Minute: 32,
// Minute: 32, Second: 0,
// Second: 0, Nanosecond: 0,
// Nanosecond: 0, },
// }, }},
// }}, {
// { name: "with nanoseconds",
// name: "with nanoseconds", in: "1979-05-27T00:32:00.999999",
// in: "1979-05-27T00:32:00.999999", out: toml.LocalDateTime{
// out: toml.LocalDateTime{ Date: toml.LocalDate{
// Date: toml.LocalDate{ Year: 1979,
// Year: 1979, Month: 5,
// Month: 5, Day: 27,
// Day: 27, },
// }, Time: toml.LocalTime{
// Time: toml.LocalTime{ Hour: 0,
// Hour: 0, Minute: 32,
// Minute: 32, Second: 0,
// Second: 0, Nanosecond: 999999000,
// Nanosecond: 999999000, },
// }, },
// }, },
// }, }
// }
// for i, example := range examples {
// for i, example := range examples { doc := fmt.Sprintf(`date = %s`, example.in)
// doc := fmt.Sprintf(`date = %s`, example.in)
// t.Run(fmt.Sprintf("ToLocalDateTime_%d_%s", i, example.name), func(t *testing.T) {
// t.Run(fmt.Sprintf("ToLocalDateTime_%d_%s", i, example.name), func(t *testing.T) { type dateStruct struct {
// type dateStruct struct { Date toml.LocalDateTime
// Date toml.LocalDateTime }
// }
// var obj dateStruct
// var obj dateStruct
// err := toml.Unmarshal([]byte(doc), &obj)
// err := toml.Unmarshal([]byte(doc), &obj)
// if err != nil {
// if err != nil { t.Fatal(err)
// t.Fatal(err) }
// }
// if obj.Date != example.out {
// if obj.Date != example.out { t.Errorf("expected '%s', got '%s'", example.out, obj.Date)
// t.Errorf("expected '%s', got '%s'", example.out, obj.Date) }
// } })
// })
// t.Run(fmt.Sprintf("ToTime_%d_%s", i, example.name), func(t *testing.T) {
// t.Run(fmt.Sprintf("ToTime_%d_%s", i, example.name), func(t *testing.T) { type dateStruct struct {
// type dateStruct struct { Date time.Time
// Date time.Time }
// }
// var obj dateStruct
// var obj dateStruct
// err := toml.Unmarshal([]byte(doc), &obj)
// err := toml.Unmarshal([]byte(doc), &obj)
// if err != nil {
// if err != nil { t.Fatal(err)
// t.Fatal(err) }
// }
// if obj.Date.Year() != example.out.Date.Year {
// if obj.Date.Year() != example.out.Date.Year { t.Errorf("expected year %d, got %d", example.out.Date.Year, obj.Date.Year())
// t.Errorf("expected year %d, got %d", example.out.Date.Year, obj.Date.Year()) }
// } if obj.Date.Month() != example.out.Date.Month {
// if obj.Date.Month() != example.out.Date.Month { t.Errorf("expected month %d, got %d", example.out.Date.Month, obj.Date.Month())
// t.Errorf("expected month %d, got %d", example.out.Date.Month, obj.Date.Month()) }
// } if obj.Date.Day() != example.out.Date.Day {
// if obj.Date.Day() != example.out.Date.Day { t.Errorf("expected day %d, got %d", example.out.Date.Day, obj.Date.Day())
// t.Errorf("expected day %d, got %d", example.out.Date.Day, obj.Date.Day()) }
// } if obj.Date.Hour() != example.out.Time.Hour {
// if obj.Date.Hour() != example.out.Time.Hour { t.Errorf("expected hour %d, got %d", example.out.Time.Hour, obj.Date.Hour())
// t.Errorf("expected hour %d, got %d", example.out.Time.Hour, obj.Date.Hour()) }
// } if obj.Date.Minute() != example.out.Time.Minute {
// if obj.Date.Minute() != example.out.Time.Minute { t.Errorf("expected minute %d, got %d", example.out.Time.Minute, obj.Date.Minute())
// t.Errorf("expected minute %d, got %d", example.out.Time.Minute, obj.Date.Minute()) }
// } if obj.Date.Second() != example.out.Time.Second {
// if obj.Date.Second() != example.out.Time.Second { t.Errorf("expected second %d, got %d", example.out.Time.Second, obj.Date.Second())
// t.Errorf("expected second %d, got %d", example.out.Time.Second, obj.Date.Second()) }
// } if obj.Date.Nanosecond() != example.out.Time.Nanosecond {
// if obj.Date.Nanosecond() != example.out.Time.Nanosecond { t.Errorf("expected nanoseconds %d, got %d", example.out.Time.Nanosecond, obj.Date.Nanosecond())
// t.Errorf("expected nanoseconds %d, got %d", example.out.Time.Nanosecond, obj.Date.Nanosecond()) }
// } })
// }) }
// } }
//}
// func TestUnmarshalLocalTime(t *testing.T) {
//func TestUnmarshalLocalTime(t *testing.T) { examples := []struct {
// examples := []struct { name string
// name string in string
// in string out toml.LocalTime
// out toml.LocalTime }{
// }{ {
// { name: "normal",
// name: "normal", in: "07:32:00",
// in: "07:32:00", out: toml.LocalTime{
// out: toml.LocalTime{ Hour: 7,
// Hour: 7, Minute: 32,
// Minute: 32, Second: 0,
// Second: 0, Nanosecond: 0,
// Nanosecond: 0, },
// }, },
// }, {
// { name: "with nanoseconds",
// name: "with nanoseconds", in: "00:32:00.999999",
// in: "00:32:00.999999", out: toml.LocalTime{
// out: toml.LocalTime{ Hour: 0,
// Hour: 0, Minute: 32,
// Minute: 32, Second: 0,
// Second: 0, Nanosecond: 999999000,
// Nanosecond: 999999000, },
// }, },
// }, }
// }
// for i, example := range examples {
// for i, example := range examples { doc := fmt.Sprintf(`Time = %s`, example.in)
// doc := fmt.Sprintf(`Time = %s`, example.in)
// t.Run(fmt.Sprintf("ToLocalTime_%d_%s", i, example.name), func(t *testing.T) {
// t.Run(fmt.Sprintf("ToLocalTime_%d_%s", i, example.name), func(t *testing.T) { type dateStruct struct {
// type dateStruct struct { Time toml.LocalTime
// Time toml.LocalTime }
// }
// var obj dateStruct
// var obj dateStruct
// err := toml.Unmarshal([]byte(doc), &obj)
// err := toml.Unmarshal([]byte(doc), &obj)
// if err != nil {
// if err != nil { t.Fatal(err)
// t.Fatal(err) }
// }
// if obj.Time != example.out {
// if obj.Time != example.out { t.Errorf("expected '%s', got '%s'", example.out, obj.Time)
// t.Errorf("expected '%s', got '%s'", example.out, obj.Time) }
// } })
// }) }
// } }
//}
// test case for issue #339 // test case for issue #339
func TestUnmarshalSameInnerField(t *testing.T) { func TestUnmarshalSameInnerField(t *testing.T) {
+5 -1
View File
@@ -286,7 +286,7 @@ func tryTextUnmarshaler(x target, node ast.Node) (bool, error) {
return false, nil return false, nil
} }
// Special case for time, becase we allow to unmarshal to it from // Special case for time, because we allow to unmarshal to it from
// different kind of AST nodes. // different kind of AST nodes.
if v.Type() == timeType { if v.Type() == timeType {
return false, nil return false, nil
@@ -374,6 +374,10 @@ func unmarshalDateTime(x target, node ast.Node) error {
} }
func setLocalDateTime(x target, v LocalDateTime) error { func setLocalDateTime(x target, v LocalDateTime) error {
if x.get().Type() == timeType {
cast := v.In(time.Local)
return setDateTime(x, cast)
}
return x.set(reflect.ValueOf(v)) return x.set(reflect.ValueOf(v))
} }
+67
View File
@@ -960,6 +960,73 @@ world'`,
} }
} }
func TestLocalDateTime(t *testing.T) {
t.Parallel()
examples := []struct {
desc string
input string
}{
{
desc: "9 digits",
input: "2006-01-02T15:04:05.123456789",
},
{
desc: "8 digits",
input: "2006-01-02T15:04:05.12345678",
},
{
desc: "7 digits",
input: "2006-01-02T15:04:05.1234567",
},
{
desc: "6 digits",
input: "2006-01-02T15:04:05.123456",
},
{
desc: "5 digits",
input: "2006-01-02T15:04:05.12345",
},
{
desc: "4 digits",
input: "2006-01-02T15:04:05.1234",
},
{
desc: "3 digits",
input: "2006-01-02T15:04:05.123",
},
{
desc: "2 digits",
input: "2006-01-02T15:04:05.12",
},
{
desc: "1 digit",
input: "2006-01-02T15:04:05.1",
},
{
desc: "0 digit",
input: "2006-01-02T15:04:05",
},
}
for _, e := range examples {
e := e
t.Run(e.desc, func(t *testing.T) {
t.Parallel()
t.Log("input:", e.input)
doc := `a = ` + e.input
m := map[string]toml.LocalDateTime{}
err := toml.Unmarshal([]byte(doc), &m)
require.NoError(t, err)
actual := m["a"]
golang, err := time.Parse("2006-01-02T15:04:05.999999999", e.input)
require.NoError(t, err)
expected := toml.LocalDateTimeOf(golang)
require.Equal(t, expected, actual)
})
}
}
func TestIssue287(t *testing.T) { func TestIssue287(t *testing.T) {
b := `y=[[{}]]` b := `y=[[{}]]`
v := map[string]interface{}{} v := map[string]interface{}{}