Add LocalTime to interface{} decode support (#567)
Co-authored-by: Thomas Pelletier <thomas@pelletier.codes>
This commit is contained in:
@@ -25,9 +25,9 @@ const (
|
||||
Float
|
||||
Integer
|
||||
LocalDate
|
||||
LocalTime
|
||||
LocalDateTime
|
||||
DateTime
|
||||
Time
|
||||
)
|
||||
|
||||
func (k Kind) String() string {
|
||||
@@ -58,12 +58,12 @@ func (k Kind) String() string {
|
||||
return "Integer"
|
||||
case LocalDate:
|
||||
return "LocalDate"
|
||||
case LocalTime:
|
||||
return "LocalTime"
|
||||
case LocalDateTime:
|
||||
return "LocalDateTime"
|
||||
case DateTime:
|
||||
return "DateTime"
|
||||
case Time:
|
||||
return "Time"
|
||||
}
|
||||
panic(fmt.Errorf("Kind.String() not implemented for '%d'", k))
|
||||
}
|
||||
|
||||
@@ -806,3 +806,10 @@ func ExampleMarshal() {
|
||||
// Name = '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{})
|
||||
}
|
||||
|
||||
@@ -862,6 +862,7 @@ func digitsToInt(b []byte) int {
|
||||
func (p *parser) scanDateTime(b []byte) (ast.Reference, []byte, error) {
|
||||
// scans for contiguous characters in [0-9T:Z.+-], and up to one space if
|
||||
// followed by a digit.
|
||||
hasDate := false
|
||||
hasTime := false
|
||||
hasTz := false
|
||||
seenSpace := false
|
||||
@@ -874,6 +875,7 @@ byteLoop:
|
||||
switch {
|
||||
case isDigit(c):
|
||||
case c == '-':
|
||||
hasDate = true
|
||||
const minOffsetOfTz = 8
|
||||
if i >= minOffsetOfTz {
|
||||
hasTz = true
|
||||
@@ -898,10 +900,14 @@ byteLoop:
|
||||
var kind ast.Kind
|
||||
|
||||
if hasTime {
|
||||
if hasTz {
|
||||
kind = ast.DateTime
|
||||
if hasDate {
|
||||
if hasTz {
|
||||
kind = ast.DateTime
|
||||
} else {
|
||||
kind = ast.LocalDateTime
|
||||
}
|
||||
} else {
|
||||
kind = ast.LocalDateTime
|
||||
kind = ast.LocalTime
|
||||
}
|
||||
} else {
|
||||
kind = ast.LocalDate
|
||||
|
||||
@@ -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())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -585,6 +585,8 @@ func (d *decoder) handleValue(value *ast.Node, v reflect.Value) error {
|
||||
return d.unmarshalDateTime(value, v)
|
||||
case ast.LocalDate:
|
||||
return d.unmarshalLocalDate(value, v)
|
||||
case ast.LocalTime:
|
||||
return d.unmarshalLocalTime(value, v)
|
||||
case ast.LocalDateTime:
|
||||
return d.unmarshalLocalDateTime(value, v)
|
||||
case ast.InlineTable:
|
||||
@@ -730,6 +732,20 @@ func (d *decoder) unmarshalLocalDate(value *ast.Node, v reflect.Value) error {
|
||||
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 {
|
||||
ldt, rest, err := parseLocalDateTime(value.Data)
|
||||
if err != nil {
|
||||
|
||||
@@ -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",
|
||||
input: `fruit. color = "yellow"
|
||||
|
||||
Reference in New Issue
Block a user