Skip to content

Commit

Permalink
encoding/toml: support decoding timestamps
Browse files Browse the repository at this point in the history
Given that the CUE language does not have time or date literals,
and instead treats timestamps as either strings or numbers,
we decode the string-like TOML timestamps as CUE strings.

However, we still need to differentiate timestamps like the local time

    foo = 07:32:00

from the string

    foo = "07:32:00"

so that the user may know which to treat as a timestamp, and so that
the encoding/toml package can roundtrip without converting TOML
timestamps to strings, or vice versa.

For this reason, decode TOML timestamps as a unification with pkg/time's
Format validator, with the correct format string per the TOML spec.

Note that example.txtar has position diff noise as we add an import.

Fixes #3345.

Signed-off-by: Daniel Martí <[email protected]>
Change-Id: If17dc3b87f95179a956acf004fbf8e37abf2b376
Dispatch-Trailer: {"type":"trybot","CL":1200888,"patchset":1,"ref":"refs/changes/88/1200888/1","targetBranch":"master"}
  • Loading branch information
mvdan authored and cueckoo committed Sep 9, 2024
1 parent 641d9b7 commit d8d4912
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 43 deletions.
34 changes: 33 additions & 1 deletion encoding/toml/decode.go
Original file line number Diff line number Diff line change
Expand Up @@ -463,7 +463,39 @@ func (d *Decoder) decodeExpr(rkey rootedKey, tnode *toml.Node) (ast.Expr, error)
strct.Elts = append(strct.Elts, field)
}
expr = strct
// TODO(mvdan): dates and times
case toml.LocalDate, toml.LocalTime, toml.LocalDateTime, toml.DateTime:
// CUE does not have native date nor time litera kinds,
// so we decode these as strings exactly as they came in.
// The user can use CUE's `time` package to interact with these,
// such as time.Split.
// We could consider adding a field attribute to signal that the original TOML
// TODO: should we produce a CUE field attribute to signal what th
var format ast.Expr
switch tnode.Kind {
case toml.LocalDate:
// TODO(mvdan): rename time.RFC3339Date to time.DateOnly to mirror Go
format = ast.NewSel(&ast.Ident{
Name: "time",
Node: ast.NewImport(nil, "time"),
}, "RFC3339Date")
case toml.LocalTime:
// TODO(mvdan): add time.TimeOnly to mirror Go
format = ast.NewString("15:04:05")
case toml.LocalDateTime:
// RFC3339 minus the timezone; this seems like a format peculiar to TOML.
format = ast.NewString("2006-01-02T15:04:05")
default: // DateTime
format = ast.NewSel(&ast.Ident{
Name: "time",
Node: ast.NewImport(nil, "time"),
}, "RFC3339")
}
expr = ast.NewBinExpr(token.AND, ast.NewString(data), ast.NewCall(
ast.NewSel(&ast.Ident{
Name: "time",
Node: ast.NewImport(nil, "time"),
}, "Format"), format),
)
default:
return nil, fmt.Errorf("encoding/toml.Decoder.decodeExpr: unknown %s %#v", tnode.Kind, tnode)
}
Expand Down
34 changes: 34 additions & 0 deletions encoding/toml/decode_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,40 @@ line two.\
positive: true
negative: false
`,
}, {
name: "DateTimes",
input: `
offsetDateTime1 = 1979-05-27T07:32:00Z
offsetDateTime2 = 1979-05-27T00:32:00-07:00
offsetDateTime3 = 1979-05-27T00:32:00.999999-07:00
localDateTime1 = 1979-05-27T07:32:00
localDateTime2 = 1979-05-27T00:32:00.999999
localDate1 = 1979-05-27
localTime1 = 07:32:00
localTime2 = 00:32:00.999999
inlineArray = [1979-05-27, 07:32:00]
notActuallyDate = "1979-05-27"
notActuallyTime = "07:32:00"
inlineArrayNotActually = ["1979-05-27", "07:32:00"]
`,
wantCUE: `
import "time"
offsetDateTime1: "1979-05-27T07:32:00Z" & time.Format(time.RFC3339)
offsetDateTime2: "1979-05-27T00:32:00-07:00" & time.Format(time.RFC3339)
offsetDateTime3: "1979-05-27T00:32:00.999999-07:00" & time.Format(time.RFC3339)
localDateTime1: "1979-05-27T07:32:00" & time.Format("2006-01-02T15:04:05")
localDateTime2: "1979-05-27T00:32:00.999999" & time.Format("2006-01-02T15:04:05")
localDate1: "1979-05-27" & time.Format(time.RFC3339Date)
localTime1: "07:32:00" & time.Format("15:04:05")
localTime2: "00:32:00.999999" & time.Format("15:04:05")
inlineArray: ["1979-05-27" & time.Format(time.RFC3339Date), "07:32:00" & time.Format("15:04:05")]
notActuallyDate: "1979-05-27"
notActuallyTime: "07:32:00"
inlineArrayNotActually: ["1979-05-27", "07:32:00"]
`,
}, {
name: "Arrays",
input: `
Expand Down
88 changes: 46 additions & 42 deletions encoding/toml/testdata/decode/example.txtar
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ title = "TOML Example"

[owner]
name = "Tom Preston-Werner"
# TODO: support dates
# dob = 1979-05-27T07:32:00-08:00
dob = 1979-05-27T07:32:00-08:00

[database]
enabled = true
Expand Down Expand Up @@ -62,60 +61,65 @@ color = "gray"
ValuePos: token.Pos("example.toml:7:8")
}
}
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:8:1", newline)
}
}
}
Rbrace: token.Pos("-", newline)
}
}
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:11:2", newline)
NamePos: token.Pos("example.toml:10:2", newline)
}
Value: *ast.StructLit{
Lbrace: token.Pos("-", blank)
Elts: []ast.Decl{
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:12:1", newline)
NamePos: token.Pos("example.toml:11:1", newline)
}
}
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:13:1", newline)
NamePos: token.Pos("example.toml:12:1", newline)
}
Value: *ast.ListLit{
Elts: []ast.Expr{
*ast.BasicLit{
ValuePos: token.Pos("example.toml:13:11")
ValuePos: token.Pos("example.toml:12:11")
}
*ast.BasicLit{
ValuePos: token.Pos("example.toml:13:17")
ValuePos: token.Pos("example.toml:12:17")
}
*ast.BasicLit{
ValuePos: token.Pos("example.toml:13:23")
ValuePos: token.Pos("example.toml:12:23")
}
}
}
}
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:14:1", newline)
NamePos: token.Pos("example.toml:13:1", newline)
}
Value: *ast.ListLit{
Elts: []ast.Expr{
*ast.ListLit{
Elts: []ast.Expr{
*ast.BasicLit{
ValuePos: token.Pos("example.toml:14:11")
ValuePos: token.Pos("example.toml:13:11")
}
*ast.BasicLit{
ValuePos: token.Pos("example.toml:14:20")
ValuePos: token.Pos("example.toml:13:20")
}
}
}
*ast.ListLit{
Elts: []ast.Expr{
*ast.BasicLit{
ValuePos: token.Pos("example.toml:14:29")
ValuePos: token.Pos("example.toml:13:29")
}
}
}
Expand All @@ -124,25 +128,25 @@ color = "gray"
}
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:15:1", newline)
NamePos: token.Pos("example.toml:14:1", newline)
}
Value: *ast.StructLit{
Lbrace: token.Pos("example.toml:15:16")
Lbrace: token.Pos("example.toml:14:16")
Elts: []ast.Decl{
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:15:18", blank)
NamePos: token.Pos("example.toml:14:18", blank)
}
Value: *ast.BasicLit{
ValuePos: token.Pos("example.toml:15:24")
ValuePos: token.Pos("example.toml:14:24")
}
}
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:15:30", blank)
NamePos: token.Pos("example.toml:14:30", blank)
}
Value: *ast.BasicLit{
ValuePos: token.Pos("example.toml:15:37")
ValuePos: token.Pos("example.toml:14:37")
}
}
}
Expand All @@ -155,7 +159,7 @@ color = "gray"
}
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:17:2", newline)
NamePos: token.Pos("example.toml:16:2", newline)
}
Value: *ast.StructLit{
Lbrace: token.Pos("-", blank)
Expand All @@ -164,31 +168,31 @@ color = "gray"
}
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:19:2", newline)
NamePos: token.Pos("example.toml:18:2", newline)
}
Value: *ast.StructLit{
Elts: []ast.Decl{
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:19:10", blank)
NamePos: token.Pos("example.toml:18:10", blank)
}
Value: *ast.StructLit{
Lbrace: token.Pos("-", blank)
Elts: []ast.Decl{
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:20:1", newline)
NamePos: token.Pos("example.toml:19:1", newline)
}
Value: *ast.BasicLit{
ValuePos: token.Pos("example.toml:20:6")
ValuePos: token.Pos("example.toml:19:6")
}
}
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:21:1", newline)
NamePos: token.Pos("example.toml:20:1", newline)
}
Value: *ast.BasicLit{
ValuePos: token.Pos("example.toml:21:8")
ValuePos: token.Pos("example.toml:20:8")
}
}
}
Expand All @@ -200,31 +204,31 @@ color = "gray"
}
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:23:2", newline)
NamePos: token.Pos("example.toml:22:2", newline)
}
Value: *ast.StructLit{
Elts: []ast.Decl{
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:23:10", blank)
NamePos: token.Pos("example.toml:22:10", blank)
}
Value: *ast.StructLit{
Lbrace: token.Pos("-", blank)
Elts: []ast.Decl{
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:24:1", newline)
NamePos: token.Pos("example.toml:23:1", newline)
}
Value: *ast.BasicLit{
ValuePos: token.Pos("example.toml:24:6")
ValuePos: token.Pos("example.toml:23:6")
}
}
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:25:1", newline)
NamePos: token.Pos("example.toml:24:1", newline)
}
Value: *ast.BasicLit{
ValuePos: token.Pos("example.toml:25:8")
ValuePos: token.Pos("example.toml:24:8")
}
}
}
Expand All @@ -236,7 +240,7 @@ color = "gray"
}
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:27:3", newline)
NamePos: token.Pos("example.toml:26:3", newline)
}
Value: *ast.ListLit{
Lbrack: token.Pos("-", blank)
Expand All @@ -246,18 +250,18 @@ color = "gray"
Elts: []ast.Decl{
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:28:1", newline)
NamePos: token.Pos("example.toml:27:1", newline)
}
Value: *ast.BasicLit{
ValuePos: token.Pos("example.toml:28:8")
ValuePos: token.Pos("example.toml:27:8")
}
}
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:29:1", newline)
NamePos: token.Pos("example.toml:28:1", newline)
}
Value: *ast.BasicLit{
ValuePos: token.Pos("example.toml:29:7")
ValuePos: token.Pos("example.toml:28:7")
}
}
}
Expand All @@ -272,26 +276,26 @@ color = "gray"
Elts: []ast.Decl{
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:34:1", newline)
NamePos: token.Pos("example.toml:33:1", newline)
}
Value: *ast.BasicLit{
ValuePos: token.Pos("example.toml:34:8")
ValuePos: token.Pos("example.toml:33:8")
}
}
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:35:1", newline)
NamePos: token.Pos("example.toml:34:1", newline)
}
Value: *ast.BasicLit{
ValuePos: token.Pos("example.toml:35:7")
ValuePos: token.Pos("example.toml:34:7")
}
}
*ast.Field{
Label: *ast.Ident{
NamePos: token.Pos("example.toml:37:1", newline)
NamePos: token.Pos("example.toml:36:1", newline)
}
Value: *ast.BasicLit{
ValuePos: token.Pos("example.toml:37:9")
ValuePos: token.Pos("example.toml:36:9")
}
}
}
Expand Down

0 comments on commit d8d4912

Please sign in to comment.