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
|
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))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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{})
|
||||||
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
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 {
|
||||||
|
|||||||
@@ -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"
|
||||||
|
|||||||
Reference in New Issue
Block a user