Skip to content

Commit 2f82fb4

Browse files
committed
encoding/toml: support decoding timestamps
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 Reviewed-on: https://review.gerrithub.io/c/cue-lang/cue/+/1200888 Reviewed-by: Roger Peppe <[email protected]> TryBot-Result: CUEcueckoo <[email protected]> Unity-Result: CUE porcuepine <[email protected]>
1 parent 7648cbd commit 2f82fb4

File tree

3 files changed

+113
-43
lines changed

3 files changed

+113
-43
lines changed

encoding/toml/decode.go

+33-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"io"
2424
"strconv"
2525
"strings"
26+
"time"
2627

2728
toml "github.com/pelletier/go-toml/v2/unstable"
2829

@@ -463,7 +464,38 @@ func (d *Decoder) decodeExpr(rkey rootedKey, tnode *toml.Node) (ast.Expr, error)
463464
strct.Elts = append(strct.Elts, field)
464465
}
465466
expr = strct
466-
// TODO(mvdan): dates and times
467+
case toml.LocalDate, toml.LocalTime, toml.LocalDateTime, toml.DateTime:
468+
// CUE does not have native date nor time literal kinds,
469+
// so we decode these as strings exactly as they came in
470+
// and we validate them with time.Format using the corresponding format string.
471+
// Not only does this ensure that the resulting CUE can be used with our time package,
472+
// but it also means that we can roundtrip a TOML timestamp without confusing it for a string.
473+
var format ast.Expr
474+
switch tnode.Kind {
475+
case toml.LocalDate:
476+
// TODO(mvdan): rename time.RFC3339Date to time.DateOnly to mirror Go
477+
format = ast.NewSel(&ast.Ident{
478+
Name: "time",
479+
Node: ast.NewImport(nil, "time"),
480+
}, "RFC3339Date")
481+
case toml.LocalTime:
482+
// TODO(mvdan): add TimeOnly to CUE's time package to mirror Go
483+
format = ast.NewString(time.TimeOnly)
484+
case toml.LocalDateTime:
485+
// RFC3339 minus the timezone; this seems like a format peculiar to TOML.
486+
format = ast.NewString("2006-01-02T15:04:05")
487+
default: // DateTime
488+
format = ast.NewSel(&ast.Ident{
489+
Name: "time",
490+
Node: ast.NewImport(nil, "time"),
491+
}, "RFC3339")
492+
}
493+
expr = ast.NewBinExpr(token.AND, ast.NewString(data), ast.NewCall(
494+
ast.NewSel(&ast.Ident{
495+
Name: "time",
496+
Node: ast.NewImport(nil, "time"),
497+
}, "Format"), format),
498+
)
467499
default:
468500
return nil, fmt.Errorf("encoding/toml.Decoder.decodeExpr: unknown %s %#v", tnode.Kind, tnode)
469501
}

encoding/toml/decode_test.go

+34
Original file line numberDiff line numberDiff line change
@@ -316,6 +316,40 @@ line two.\
316316
positive: true
317317
negative: false
318318
`,
319+
}, {
320+
name: "DateTimes",
321+
input: `
322+
offsetDateTime1 = 1979-05-27T07:32:00Z
323+
offsetDateTime2 = 1979-05-27T00:32:00-07:00
324+
offsetDateTime3 = 1979-05-27T00:32:00.999999-07:00
325+
localDateTime1 = 1979-05-27T07:32:00
326+
localDateTime2 = 1979-05-27T00:32:00.999999
327+
localDate1 = 1979-05-27
328+
localTime1 = 07:32:00
329+
localTime2 = 00:32:00.999999
330+
331+
inlineArray = [1979-05-27, 07:32:00]
332+
333+
notActuallyDate = "1979-05-27"
334+
notActuallyTime = "07:32:00"
335+
inlineArrayNotActually = ["1979-05-27", "07:32:00"]
336+
`,
337+
wantCUE: `
338+
import "time"
339+
340+
offsetDateTime1: "1979-05-27T07:32:00Z" & time.Format(time.RFC3339)
341+
offsetDateTime2: "1979-05-27T00:32:00-07:00" & time.Format(time.RFC3339)
342+
offsetDateTime3: "1979-05-27T00:32:00.999999-07:00" & time.Format(time.RFC3339)
343+
localDateTime1: "1979-05-27T07:32:00" & time.Format("2006-01-02T15:04:05")
344+
localDateTime2: "1979-05-27T00:32:00.999999" & time.Format("2006-01-02T15:04:05")
345+
localDate1: "1979-05-27" & time.Format(time.RFC3339Date)
346+
localTime1: "07:32:00" & time.Format("15:04:05")
347+
localTime2: "00:32:00.999999" & time.Format("15:04:05")
348+
inlineArray: ["1979-05-27" & time.Format(time.RFC3339Date), "07:32:00" & time.Format("15:04:05")]
349+
notActuallyDate: "1979-05-27"
350+
notActuallyTime: "07:32:00"
351+
inlineArrayNotActually: ["1979-05-27", "07:32:00"]
352+
`,
319353
}, {
320354
name: "Arrays",
321355
input: `

encoding/toml/testdata/decode/example.txtar

+46-42
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@ title = "TOML Example"
66

77
[owner]
88
name = "Tom Preston-Werner"
9-
# TODO: support dates
10-
# dob = 1979-05-27T07:32:00-08:00
9+
dob = 1979-05-27T07:32:00-08:00
1110

1211
[database]
1312
enabled = true
@@ -62,60 +61,65 @@ color = "gray"
6261
ValuePos: token.Pos("example.toml:7:8")
6362
}
6463
}
64+
*ast.Field{
65+
Label: *ast.Ident{
66+
NamePos: token.Pos("example.toml:8:1", newline)
67+
}
68+
}
6569
}
6670
Rbrace: token.Pos("-", newline)
6771
}
6872
}
6973
*ast.Field{
7074
Label: *ast.Ident{
71-
NamePos: token.Pos("example.toml:11:2", newline)
75+
NamePos: token.Pos("example.toml:10:2", newline)
7276
}
7377
Value: *ast.StructLit{
7478
Lbrace: token.Pos("-", blank)
7579
Elts: []ast.Decl{
7680
*ast.Field{
7781
Label: *ast.Ident{
78-
NamePos: token.Pos("example.toml:12:1", newline)
82+
NamePos: token.Pos("example.toml:11:1", newline)
7983
}
8084
}
8185
*ast.Field{
8286
Label: *ast.Ident{
83-
NamePos: token.Pos("example.toml:13:1", newline)
87+
NamePos: token.Pos("example.toml:12:1", newline)
8488
}
8589
Value: *ast.ListLit{
8690
Elts: []ast.Expr{
8791
*ast.BasicLit{
88-
ValuePos: token.Pos("example.toml:13:11")
92+
ValuePos: token.Pos("example.toml:12:11")
8993
}
9094
*ast.BasicLit{
91-
ValuePos: token.Pos("example.toml:13:17")
95+
ValuePos: token.Pos("example.toml:12:17")
9296
}
9397
*ast.BasicLit{
94-
ValuePos: token.Pos("example.toml:13:23")
98+
ValuePos: token.Pos("example.toml:12:23")
9599
}
96100
}
97101
}
98102
}
99103
*ast.Field{
100104
Label: *ast.Ident{
101-
NamePos: token.Pos("example.toml:14:1", newline)
105+
NamePos: token.Pos("example.toml:13:1", newline)
102106
}
103107
Value: *ast.ListLit{
104108
Elts: []ast.Expr{
105109
*ast.ListLit{
106110
Elts: []ast.Expr{
107111
*ast.BasicLit{
108-
ValuePos: token.Pos("example.toml:14:11")
112+
ValuePos: token.Pos("example.toml:13:11")
109113
}
110114
*ast.BasicLit{
111-
ValuePos: token.Pos("example.toml:14:20")
115+
ValuePos: token.Pos("example.toml:13:20")
112116
}
113117
}
114118
}
115119
*ast.ListLit{
116120
Elts: []ast.Expr{
117121
*ast.BasicLit{
118-
ValuePos: token.Pos("example.toml:14:29")
122+
ValuePos: token.Pos("example.toml:13:29")
119123
}
120124
}
121125
}
@@ -124,25 +128,25 @@ color = "gray"
124128
}
125129
*ast.Field{
126130
Label: *ast.Ident{
127-
NamePos: token.Pos("example.toml:15:1", newline)
131+
NamePos: token.Pos("example.toml:14:1", newline)
128132
}
129133
Value: *ast.StructLit{
130-
Lbrace: token.Pos("example.toml:15:16")
134+
Lbrace: token.Pos("example.toml:14:16")
131135
Elts: []ast.Decl{
132136
*ast.Field{
133137
Label: *ast.Ident{
134-
NamePos: token.Pos("example.toml:15:18", blank)
138+
NamePos: token.Pos("example.toml:14:18", blank)
135139
}
136140
Value: *ast.BasicLit{
137-
ValuePos: token.Pos("example.toml:15:24")
141+
ValuePos: token.Pos("example.toml:14:24")
138142
}
139143
}
140144
*ast.Field{
141145
Label: *ast.Ident{
142-
NamePos: token.Pos("example.toml:15:30", blank)
146+
NamePos: token.Pos("example.toml:14:30", blank)
143147
}
144148
Value: *ast.BasicLit{
145-
ValuePos: token.Pos("example.toml:15:37")
149+
ValuePos: token.Pos("example.toml:14:37")
146150
}
147151
}
148152
}
@@ -155,7 +159,7 @@ color = "gray"
155159
}
156160
*ast.Field{
157161
Label: *ast.Ident{
158-
NamePos: token.Pos("example.toml:17:2", newline)
162+
NamePos: token.Pos("example.toml:16:2", newline)
159163
}
160164
Value: *ast.StructLit{
161165
Lbrace: token.Pos("-", blank)
@@ -164,31 +168,31 @@ color = "gray"
164168
}
165169
*ast.Field{
166170
Label: *ast.Ident{
167-
NamePos: token.Pos("example.toml:19:2", newline)
171+
NamePos: token.Pos("example.toml:18:2", newline)
168172
}
169173
Value: *ast.StructLit{
170174
Elts: []ast.Decl{
171175
*ast.Field{
172176
Label: *ast.Ident{
173-
NamePos: token.Pos("example.toml:19:10", blank)
177+
NamePos: token.Pos("example.toml:18:10", blank)
174178
}
175179
Value: *ast.StructLit{
176180
Lbrace: token.Pos("-", blank)
177181
Elts: []ast.Decl{
178182
*ast.Field{
179183
Label: *ast.Ident{
180-
NamePos: token.Pos("example.toml:20:1", newline)
184+
NamePos: token.Pos("example.toml:19:1", newline)
181185
}
182186
Value: *ast.BasicLit{
183-
ValuePos: token.Pos("example.toml:20:6")
187+
ValuePos: token.Pos("example.toml:19:6")
184188
}
185189
}
186190
*ast.Field{
187191
Label: *ast.Ident{
188-
NamePos: token.Pos("example.toml:21:1", newline)
192+
NamePos: token.Pos("example.toml:20:1", newline)
189193
}
190194
Value: *ast.BasicLit{
191-
ValuePos: token.Pos("example.toml:21:8")
195+
ValuePos: token.Pos("example.toml:20:8")
192196
}
193197
}
194198
}
@@ -200,31 +204,31 @@ color = "gray"
200204
}
201205
*ast.Field{
202206
Label: *ast.Ident{
203-
NamePos: token.Pos("example.toml:23:2", newline)
207+
NamePos: token.Pos("example.toml:22:2", newline)
204208
}
205209
Value: *ast.StructLit{
206210
Elts: []ast.Decl{
207211
*ast.Field{
208212
Label: *ast.Ident{
209-
NamePos: token.Pos("example.toml:23:10", blank)
213+
NamePos: token.Pos("example.toml:22:10", blank)
210214
}
211215
Value: *ast.StructLit{
212216
Lbrace: token.Pos("-", blank)
213217
Elts: []ast.Decl{
214218
*ast.Field{
215219
Label: *ast.Ident{
216-
NamePos: token.Pos("example.toml:24:1", newline)
220+
NamePos: token.Pos("example.toml:23:1", newline)
217221
}
218222
Value: *ast.BasicLit{
219-
ValuePos: token.Pos("example.toml:24:6")
223+
ValuePos: token.Pos("example.toml:23:6")
220224
}
221225
}
222226
*ast.Field{
223227
Label: *ast.Ident{
224-
NamePos: token.Pos("example.toml:25:1", newline)
228+
NamePos: token.Pos("example.toml:24:1", newline)
225229
}
226230
Value: *ast.BasicLit{
227-
ValuePos: token.Pos("example.toml:25:8")
231+
ValuePos: token.Pos("example.toml:24:8")
228232
}
229233
}
230234
}
@@ -236,7 +240,7 @@ color = "gray"
236240
}
237241
*ast.Field{
238242
Label: *ast.Ident{
239-
NamePos: token.Pos("example.toml:27:3", newline)
243+
NamePos: token.Pos("example.toml:26:3", newline)
240244
}
241245
Value: *ast.ListLit{
242246
Lbrack: token.Pos("-", blank)
@@ -246,18 +250,18 @@ color = "gray"
246250
Elts: []ast.Decl{
247251
*ast.Field{
248252
Label: *ast.Ident{
249-
NamePos: token.Pos("example.toml:28:1", newline)
253+
NamePos: token.Pos("example.toml:27:1", newline)
250254
}
251255
Value: *ast.BasicLit{
252-
ValuePos: token.Pos("example.toml:28:8")
256+
ValuePos: token.Pos("example.toml:27:8")
253257
}
254258
}
255259
*ast.Field{
256260
Label: *ast.Ident{
257-
NamePos: token.Pos("example.toml:29:1", newline)
261+
NamePos: token.Pos("example.toml:28:1", newline)
258262
}
259263
Value: *ast.BasicLit{
260-
ValuePos: token.Pos("example.toml:29:7")
264+
ValuePos: token.Pos("example.toml:28:7")
261265
}
262266
}
263267
}
@@ -272,26 +276,26 @@ color = "gray"
272276
Elts: []ast.Decl{
273277
*ast.Field{
274278
Label: *ast.Ident{
275-
NamePos: token.Pos("example.toml:34:1", newline)
279+
NamePos: token.Pos("example.toml:33:1", newline)
276280
}
277281
Value: *ast.BasicLit{
278-
ValuePos: token.Pos("example.toml:34:8")
282+
ValuePos: token.Pos("example.toml:33:8")
279283
}
280284
}
281285
*ast.Field{
282286
Label: *ast.Ident{
283-
NamePos: token.Pos("example.toml:35:1", newline)
287+
NamePos: token.Pos("example.toml:34:1", newline)
284288
}
285289
Value: *ast.BasicLit{
286-
ValuePos: token.Pos("example.toml:35:7")
290+
ValuePos: token.Pos("example.toml:34:7")
287291
}
288292
}
289293
*ast.Field{
290294
Label: *ast.Ident{
291-
NamePos: token.Pos("example.toml:37:1", newline)
295+
NamePos: token.Pos("example.toml:36:1", newline)
292296
}
293297
Value: *ast.BasicLit{
294-
ValuePos: token.Pos("example.toml:37:9")
298+
ValuePos: token.Pos("example.toml:36:9")
295299
}
296300
}
297301
}

0 commit comments

Comments
 (0)