decoder: fix time fractional parsing
This commit is contained in:
@@ -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")
|
||||||
|
|||||||
@@ -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
@@ -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))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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{}{}
|
||||||
|
|||||||
Reference in New Issue
Block a user