diff --git a/internal/ast/kind.go b/internal/ast/kind.go index bcf6c91c..2b50c67f 100644 --- a/internal/ast/kind.go +++ b/internal/ast/kind.go @@ -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)) } diff --git a/marshaler_test.go b/marshaler_test.go index 333c93c1..8667a813 100644 --- a/marshaler_test.go +++ b/marshaler_test.go @@ -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{}) +} diff --git a/parser.go b/parser.go index 5040b5e9..03385c12 100644 --- a/parser.go +++ b/parser.go @@ -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 diff --git a/parser_test.go b/parser_test.go index fb260e05..1f260d16 100644 --- a/parser_test.go +++ b/parser_test.go @@ -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()) + } + }) + } +} diff --git a/unmarshaler.go b/unmarshaler.go index 63469852..71793bd6 100644 --- a/unmarshaler.go +++ b/unmarshaler.go @@ -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 { diff --git a/unmarshaler_test.go b/unmarshaler_test.go index 6b134609..b4900f9b 100644 --- a/unmarshaler_test.go +++ b/unmarshaler_test.go @@ -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"