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 (
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")
)
@@ -144,8 +144,8 @@ var (
func parseLocalDateTime(b []byte) (LocalDateTime, []byte, error) {
var dt LocalDateTime
const localDateTimeByteLen = 11
if len(b) < localDateTimeByteLen {
const localDateTimeByteMinLen = 11
if len(b) < localDateTimeByteMinLen {
return dt, nil, errParseLocalDateTimeWrongLength
}
@@ -207,18 +207,41 @@ func parseLocalTime(b []byte) (LocalTime, []byte, error) {
return t, nil, err
}
if len(b) >= 15 && b[8] == '.' {
t.Nanosecond, err = parseDecimalDigits(b[9:15])
if err != nil {
return t, nil, err
if len(b) >= 9 && b[8] == '.' {
frac := 0
digits := 0
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")
}
return t, b[15:], nil
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++
}
t.Nanosecond = frac * nanosecPower(digits)
return t, b[9+digits:], 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 (
errParseFloatStartDot = errors.New("float cannot start 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) {
// t.Run("ToLocalDate", func(t *testing.T) {
// type dateStruct struct {
// Date toml.LocalDate
// }
//
// doc := `date = 1979-05-27`
//
// var obj dateStruct
//
// err := toml.Unmarshal([]byte(doc), &obj)
//
// if err != nil {
// t.Fatal(err)
// }
//
// if obj.Date.Year != 1979 {
// t.Errorf("expected year 1979, got %d", obj.Date.Year)
// }
// if obj.Date.Month != 5 {
// t.Errorf("expected month 5, got %d", obj.Date.Month)
// }
// if obj.Date.Day != 27 {
// t.Errorf("expected day 27, got %d", obj.Date.Day)
// }
// })
//
// t.Run("ToLocalDate", func(t *testing.T) {
// type dateStruct struct {
// Date time.Time
// }
//
// doc := `date = 1979-05-27`
//
// var obj dateStruct
//
// err := toml.Unmarshal([]byte(doc), &obj)
//
// if err != nil {
// t.Fatal(err)
// }
//
// if obj.Date.Year() != 1979 {
// t.Errorf("expected year 1979, got %d", obj.Date.Year())
// }
// if obj.Date.Month() != 5 {
// t.Errorf("expected month 5, got %d", obj.Date.Month())
// }
// if obj.Date.Day() != 27 {
// t.Errorf("expected day 27, got %d", obj.Date.Day())
// }
// })
//}
//
//func TestUnmarshalLocalDateTime(t *testing.T) {
// examples := []struct {
// name string
// in string
// out toml.LocalDateTime
// }{
// {
// name: "normal",
// in: "1979-05-27T07:32:00",
// out: toml.LocalDateTime{
// Date: toml.LocalDate{
// Year: 1979,
// Month: 5,
// Day: 27,
// },
// Time: toml.LocalTime{
// Hour: 7,
// Minute: 32,
// Second: 0,
// Nanosecond: 0,
// },
// }},
// {
// name: "with nanoseconds",
// in: "1979-05-27T00:32:00.999999",
// out: toml.LocalDateTime{
// Date: toml.LocalDate{
// Year: 1979,
// Month: 5,
// Day: 27,
// },
// Time: toml.LocalTime{
// Hour: 0,
// Minute: 32,
// Second: 0,
// Nanosecond: 999999000,
// },
// },
// },
// }
//
// for i, example := range examples {
// doc := fmt.Sprintf(`date = %s`, example.in)
//
// t.Run(fmt.Sprintf("ToLocalDateTime_%d_%s", i, example.name), func(t *testing.T) {
// type dateStruct struct {
// Date toml.LocalDateTime
// }
//
// var obj dateStruct
//
// err := toml.Unmarshal([]byte(doc), &obj)
//
// if err != nil {
// t.Fatal(err)
// }
//
// if obj.Date != example.out {
// t.Errorf("expected '%s', got '%s'", example.out, obj.Date)
// }
// })
//
// t.Run(fmt.Sprintf("ToTime_%d_%s", i, example.name), func(t *testing.T) {
// type dateStruct struct {
// Date time.Time
// }
//
// var obj dateStruct
//
// err := toml.Unmarshal([]byte(doc), &obj)
//
// if err != nil {
// t.Fatal(err)
// }
//
// if obj.Date.Year() != example.out.Date.Year {
// t.Errorf("expected year %d, got %d", example.out.Date.Year, obj.Date.Year())
// }
// if obj.Date.Month() != example.out.Date.Month {
// t.Errorf("expected month %d, got %d", example.out.Date.Month, obj.Date.Month())
// }
// if obj.Date.Day() != example.out.Date.Day {
// t.Errorf("expected day %d, got %d", example.out.Date.Day, obj.Date.Day())
// }
// if obj.Date.Hour() != example.out.Time.Hour {
// t.Errorf("expected hour %d, got %d", example.out.Time.Hour, obj.Date.Hour())
// }
// if obj.Date.Minute() != example.out.Time.Minute {
// t.Errorf("expected minute %d, got %d", example.out.Time.Minute, obj.Date.Minute())
// }
// if obj.Date.Second() != example.out.Time.Second {
// t.Errorf("expected second %d, got %d", example.out.Time.Second, obj.Date.Second())
// }
// if obj.Date.Nanosecond() != example.out.Time.Nanosecond {
// t.Errorf("expected nanoseconds %d, got %d", example.out.Time.Nanosecond, obj.Date.Nanosecond())
// }
// })
// }
//}
//
//func TestUnmarshalLocalTime(t *testing.T) {
// examples := []struct {
// name string
// in string
// out toml.LocalTime
// }{
// {
// name: "normal",
// in: "07:32:00",
// out: toml.LocalTime{
// Hour: 7,
// Minute: 32,
// Second: 0,
// Nanosecond: 0,
// },
// },
// {
// name: "with nanoseconds",
// in: "00:32:00.999999",
// out: toml.LocalTime{
// Hour: 0,
// Minute: 32,
// Second: 0,
// Nanosecond: 999999000,
// },
// },
// }
//
// for i, example := range examples {
// doc := fmt.Sprintf(`Time = %s`, example.in)
//
// t.Run(fmt.Sprintf("ToLocalTime_%d_%s", i, example.name), func(t *testing.T) {
// type dateStruct struct {
// Time toml.LocalTime
// }
//
// var obj dateStruct
//
// err := toml.Unmarshal([]byte(doc), &obj)
//
// if err != nil {
// t.Fatal(err)
// }
//
// if obj.Time != example.out {
// t.Errorf("expected '%s', got '%s'", example.out, obj.Time)
// }
// })
// }
//}
func TestUnmarshalLocalDate(t *testing.T) {
t.Run("ToLocalDate", func(t *testing.T) {
type dateStruct struct {
Date toml.LocalDate
}
doc := `date = 1979-05-27`
var obj dateStruct
err := toml.Unmarshal([]byte(doc), &obj)
if err != nil {
t.Fatal(err)
}
if obj.Date.Year != 1979 {
t.Errorf("expected year 1979, got %d", obj.Date.Year)
}
if obj.Date.Month != 5 {
t.Errorf("expected month 5, got %d", obj.Date.Month)
}
if obj.Date.Day != 27 {
t.Errorf("expected day 27, got %d", obj.Date.Day)
}
})
t.Run("ToLocalDate", func(t *testing.T) {
type dateStruct struct {
Date time.Time
}
doc := `date = 1979-05-27`
var obj dateStruct
err := toml.Unmarshal([]byte(doc), &obj)
if err != nil {
t.Fatal(err)
}
if obj.Date.Year() != 1979 {
t.Errorf("expected year 1979, got %d", obj.Date.Year())
}
if obj.Date.Month() != 5 {
t.Errorf("expected month 5, got %d", obj.Date.Month())
}
if obj.Date.Day() != 27 {
t.Errorf("expected day 27, got %d", obj.Date.Day())
}
})
}
func TestUnmarshalLocalDateTime(t *testing.T) {
examples := []struct {
name string
in string
out toml.LocalDateTime
}{
{
name: "normal",
in: "1979-05-27T07:32:00",
out: toml.LocalDateTime{
Date: toml.LocalDate{
Year: 1979,
Month: 5,
Day: 27,
},
Time: toml.LocalTime{
Hour: 7,
Minute: 32,
Second: 0,
Nanosecond: 0,
},
}},
{
name: "with nanoseconds",
in: "1979-05-27T00:32:00.999999",
out: toml.LocalDateTime{
Date: toml.LocalDate{
Year: 1979,
Month: 5,
Day: 27,
},
Time: toml.LocalTime{
Hour: 0,
Minute: 32,
Second: 0,
Nanosecond: 999999000,
},
},
},
}
for i, example := range examples {
doc := fmt.Sprintf(`date = %s`, example.in)
t.Run(fmt.Sprintf("ToLocalDateTime_%d_%s", i, example.name), func(t *testing.T) {
type dateStruct struct {
Date toml.LocalDateTime
}
var obj dateStruct
err := toml.Unmarshal([]byte(doc), &obj)
if err != nil {
t.Fatal(err)
}
if obj.Date != example.out {
t.Errorf("expected '%s', got '%s'", example.out, obj.Date)
}
})
t.Run(fmt.Sprintf("ToTime_%d_%s", i, example.name), func(t *testing.T) {
type dateStruct struct {
Date time.Time
}
var obj dateStruct
err := toml.Unmarshal([]byte(doc), &obj)
if err != nil {
t.Fatal(err)
}
if obj.Date.Year() != example.out.Date.Year {
t.Errorf("expected year %d, got %d", example.out.Date.Year, obj.Date.Year())
}
if obj.Date.Month() != example.out.Date.Month {
t.Errorf("expected month %d, got %d", example.out.Date.Month, obj.Date.Month())
}
if obj.Date.Day() != example.out.Date.Day {
t.Errorf("expected day %d, got %d", example.out.Date.Day, obj.Date.Day())
}
if obj.Date.Hour() != example.out.Time.Hour {
t.Errorf("expected hour %d, got %d", example.out.Time.Hour, obj.Date.Hour())
}
if obj.Date.Minute() != example.out.Time.Minute {
t.Errorf("expected minute %d, got %d", example.out.Time.Minute, obj.Date.Minute())
}
if obj.Date.Second() != example.out.Time.Second {
t.Errorf("expected second %d, got %d", example.out.Time.Second, obj.Date.Second())
}
if obj.Date.Nanosecond() != example.out.Time.Nanosecond {
t.Errorf("expected nanoseconds %d, got %d", example.out.Time.Nanosecond, obj.Date.Nanosecond())
}
})
}
}
func TestUnmarshalLocalTime(t *testing.T) {
examples := []struct {
name string
in string
out toml.LocalTime
}{
{
name: "normal",
in: "07:32:00",
out: toml.LocalTime{
Hour: 7,
Minute: 32,
Second: 0,
Nanosecond: 0,
},
},
{
name: "with nanoseconds",
in: "00:32:00.999999",
out: toml.LocalTime{
Hour: 0,
Minute: 32,
Second: 0,
Nanosecond: 999999000,
},
},
}
for i, example := range examples {
doc := fmt.Sprintf(`Time = %s`, example.in)
t.Run(fmt.Sprintf("ToLocalTime_%d_%s", i, example.name), func(t *testing.T) {
type dateStruct struct {
Time toml.LocalTime
}
var obj dateStruct
err := toml.Unmarshal([]byte(doc), &obj)
if err != nil {
t.Fatal(err)
}
if obj.Time != example.out {
t.Errorf("expected '%s', got '%s'", example.out, obj.Time)
}
})
}
}
// test case for issue #339
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
}
// 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.
if v.Type() == timeType {
return false, nil
@@ -374,6 +374,10 @@ func unmarshalDateTime(x target, node ast.Node) 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))
}
+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) {
b := `y=[[{}]]`
v := map[string]interface{}{}