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