Add LocalTime to interface{} decode support (#567)

Co-authored-by: Thomas Pelletier <thomas@pelletier.codes>
This commit is contained in:
kkHAIKE
2021-07-21 23:50:12 +08:00
committed by GitHub
parent a93b34d984
commit 8be357dfa1
6 changed files with 146 additions and 6 deletions
+3 -3
View File
@@ -25,9 +25,9 @@ const (
Float Float
Integer Integer
LocalDate LocalDate
LocalTime
LocalDateTime LocalDateTime
DateTime DateTime
Time
) )
func (k Kind) String() string { func (k Kind) String() string {
@@ -58,12 +58,12 @@ func (k Kind) String() string {
return "Integer" return "Integer"
case LocalDate: case LocalDate:
return "LocalDate" return "LocalDate"
case LocalTime:
return "LocalTime"
case LocalDateTime: case LocalDateTime:
return "LocalDateTime" return "LocalDateTime"
case DateTime: case DateTime:
return "DateTime" return "DateTime"
case Time:
return "Time"
} }
panic(fmt.Errorf("Kind.String() not implemented for '%d'", k)) panic(fmt.Errorf("Kind.String() not implemented for '%d'", k))
} }
+7
View File
@@ -806,3 +806,10 @@ func ExampleMarshal() {
// Name = 'go-toml' // Name = 'go-toml'
// Tags = ['go', 'toml'] // Tags = ['go', 'toml']
} }
func TestIssue567(t *testing.T) {
var m map[string]interface{}
err := toml.Unmarshal([]byte("A = 12:08:05"), &m)
require.NoError(t, err)
require.IsType(t, m["A"], toml.LocalTime{})
}
+9 -3
View File
@@ -862,6 +862,7 @@ func digitsToInt(b []byte) int {
func (p *parser) scanDateTime(b []byte) (ast.Reference, []byte, error) { func (p *parser) scanDateTime(b []byte) (ast.Reference, []byte, error) {
// scans for contiguous characters in [0-9T:Z.+-], and up to one space if // scans for contiguous characters in [0-9T:Z.+-], and up to one space if
// followed by a digit. // followed by a digit.
hasDate := false
hasTime := false hasTime := false
hasTz := false hasTz := false
seenSpace := false seenSpace := false
@@ -874,6 +875,7 @@ byteLoop:
switch { switch {
case isDigit(c): case isDigit(c):
case c == '-': case c == '-':
hasDate = true
const minOffsetOfTz = 8 const minOffsetOfTz = 8
if i >= minOffsetOfTz { if i >= minOffsetOfTz {
hasTz = true hasTz = true
@@ -898,10 +900,14 @@ byteLoop:
var kind ast.Kind var kind ast.Kind
if hasTime { if hasTime {
if hasTz { if hasDate {
kind = ast.DateTime if hasTz {
kind = ast.DateTime
} else {
kind = ast.LocalDateTime
}
} else { } else {
kind = ast.LocalDateTime kind = ast.LocalTime
} }
} else { } else {
kind = ast.LocalDate kind = ast.LocalDate
+59
View File
@@ -370,3 +370,62 @@ func BenchmarkParseBasicStringWithUnicode(b *testing.B) {
} }
}) })
} }
func TestParser_AST_DateTimes(t *testing.T) {
examples := []struct {
desc string
input string
kind ast.Kind
err bool
}{
{
desc: "offset-date-time with delim 'T' and UTC offset",
input: `2021-07-21T12:08:05Z`,
kind: ast.DateTime,
},
{
desc: "offset-date-time with space delim and +8hours offset",
input: `2021-07-21 12:08:05+08:00`,
kind: ast.DateTime,
},
{
desc: "local-date-time with nano second",
input: `2021-07-21T12:08:05.666666666`,
kind: ast.LocalDateTime,
},
{
desc: "local-date-time",
input: `2021-07-21T12:08:05`,
kind: ast.LocalDateTime,
},
{
desc: "local-date",
input: `2021-07-21`,
kind: ast.LocalDate,
},
}
for _, e := range examples {
e := e
t.Run(e.desc, func(t *testing.T) {
p := parser{}
p.Reset([]byte(`A = ` + e.input))
p.NextExpression()
err := p.Error()
if e.err {
require.Error(t, err)
} else {
require.NoError(t, err)
expected := astNode{
Kind: ast.KeyValue,
Children: []astNode{
{Kind: e.kind, Data: []byte(e.input)},
{Kind: ast.Key, Data: []byte(`A`)},
},
}
compareNode(t, expected, p.Expression())
}
})
}
}
+16
View File
@@ -585,6 +585,8 @@ func (d *decoder) handleValue(value *ast.Node, v reflect.Value) error {
return d.unmarshalDateTime(value, v) return d.unmarshalDateTime(value, v)
case ast.LocalDate: case ast.LocalDate:
return d.unmarshalLocalDate(value, v) return d.unmarshalLocalDate(value, v)
case ast.LocalTime:
return d.unmarshalLocalTime(value, v)
case ast.LocalDateTime: case ast.LocalDateTime:
return d.unmarshalLocalDateTime(value, v) return d.unmarshalLocalDateTime(value, v)
case ast.InlineTable: case ast.InlineTable:
@@ -730,6 +732,20 @@ func (d *decoder) unmarshalLocalDate(value *ast.Node, v reflect.Value) error {
return nil return nil
} }
func (d *decoder) unmarshalLocalTime(value *ast.Node, v reflect.Value) error {
lt, rest, err := parseLocalTime(value.Data)
if err != nil {
return err
}
if len(rest) > 0 {
return newDecodeError(rest, "extra characters at the end of a local time")
}
v.Set(reflect.ValueOf(lt))
return nil
}
func (d *decoder) unmarshalLocalDateTime(value *ast.Node, v reflect.Value) error { func (d *decoder) unmarshalLocalDateTime(value *ast.Node, v reflect.Value) error {
ldt, rest, err := parseLocalDateTime(value.Data) ldt, rest, err := parseLocalDateTime(value.Data)
if err != nil { if err != nil {
+52
View File
@@ -339,6 +339,58 @@ func TestUnmarshal(t *testing.T) {
} }
}, },
}, },
{
desc: "local-time with nano second",
input: `a = 12:08:05.666666666`,
gen: func() test {
var v map[string]interface{}
return test{
target: &v,
expected: &map[string]interface{}{
"a": toml.LocalTime{Hour: 12, Minute: 8, Second: 5, Nanosecond: 666666666},
},
}
},
},
{
desc: "local-time",
input: `a = 12:08:05`,
gen: func() test {
var v map[string]interface{}
return test{
target: &v,
expected: &map[string]interface{}{
"a": toml.LocalTime{Hour: 12, Minute: 8, Second: 5},
},
}
},
},
{
desc: "local-time missing digit",
input: `a = 12:08:0`,
gen: func() test {
var v map[string]interface{}
return test{
target: &v,
err: true,
}
},
},
{
desc: "local-time extra digit",
input: `a = 12:08:000`,
gen: func() test {
var v map[string]interface{}
return test{
target: &v,
err: true,
}
},
},
{ {
desc: "issue 475 - space between dots in key", desc: "issue 475 - space between dots in key",
input: `fruit. color = "yellow" input: `fruit. color = "yellow"