]> git.immae.eu Git - github/fretlink/terraform-provider-statuscake.git/blob - vendor/github.com/zclconf/go-cty/cty/function/stdlib/datetime.go
Upgrade to 0.12
[github/fretlink/terraform-provider-statuscake.git] / vendor / github.com / zclconf / go-cty / cty / function / stdlib / datetime.go
1 package stdlib
2
3 import (
4 "bufio"
5 "bytes"
6 "fmt"
7 "strings"
8 "time"
9
10 "github.com/zclconf/go-cty/cty"
11 "github.com/zclconf/go-cty/cty/function"
12 )
13
14 var FormatDateFunc = function.New(&function.Spec{
15 Params: []function.Parameter{
16 {
17 Name: "format",
18 Type: cty.String,
19 },
20 {
21 Name: "time",
22 Type: cty.String,
23 },
24 },
25 Type: function.StaticReturnType(cty.String),
26 Impl: func(args []cty.Value, retType cty.Type) (cty.Value, error) {
27 formatStr := args[0].AsString()
28 timeStr := args[1].AsString()
29 t, err := parseTimestamp(timeStr)
30 if err != nil {
31 return cty.DynamicVal, function.NewArgError(1, err)
32 }
33
34 var buf bytes.Buffer
35 sc := bufio.NewScanner(strings.NewReader(formatStr))
36 sc.Split(splitDateFormat)
37 const esc = '\''
38 for sc.Scan() {
39 tok := sc.Bytes()
40
41 // The leading byte signals the token type
42 switch {
43 case tok[0] == esc:
44 if tok[len(tok)-1] != esc || len(tok) == 1 {
45 return cty.DynamicVal, function.NewArgErrorf(0, "unterminated literal '")
46 }
47 if len(tok) == 2 {
48 // Must be a single escaped quote, ''
49 buf.WriteByte(esc)
50 } else {
51 // The content (until a closing esc) is printed out verbatim
52 // except that we must un-double any double-esc escapes in
53 // the middle of the string.
54 raw := tok[1 : len(tok)-1]
55 for i := 0; i < len(raw); i++ {
56 buf.WriteByte(raw[i])
57 if raw[i] == esc {
58 i++ // skip the escaped quote
59 }
60 }
61 }
62
63 case startsDateFormatVerb(tok[0]):
64 switch tok[0] {
65 case 'Y':
66 y := t.Year()
67 switch len(tok) {
68 case 2:
69 fmt.Fprintf(&buf, "%02d", y%100)
70 case 4:
71 fmt.Fprintf(&buf, "%04d", y)
72 default:
73 return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: year must either be \"YY\" or \"YYYY\"", tok)
74 }
75 case 'M':
76 m := t.Month()
77 switch len(tok) {
78 case 1:
79 fmt.Fprintf(&buf, "%d", m)
80 case 2:
81 fmt.Fprintf(&buf, "%02d", m)
82 case 3:
83 buf.WriteString(m.String()[:3])
84 case 4:
85 buf.WriteString(m.String())
86 default:
87 return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: month must be \"M\", \"MM\", \"MMM\", or \"MMMM\"", tok)
88 }
89 case 'D':
90 d := t.Day()
91 switch len(tok) {
92 case 1:
93 fmt.Fprintf(&buf, "%d", d)
94 case 2:
95 fmt.Fprintf(&buf, "%02d", d)
96 default:
97 return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: day of month must either be \"D\" or \"DD\"", tok)
98 }
99 case 'E':
100 d := t.Weekday()
101 switch len(tok) {
102 case 3:
103 buf.WriteString(d.String()[:3])
104 case 4:
105 buf.WriteString(d.String())
106 default:
107 return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: day of week must either be \"EEE\" or \"EEEE\"", tok)
108 }
109 case 'h':
110 h := t.Hour()
111 switch len(tok) {
112 case 1:
113 fmt.Fprintf(&buf, "%d", h)
114 case 2:
115 fmt.Fprintf(&buf, "%02d", h)
116 default:
117 return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: 24-hour must either be \"h\" or \"hh\"", tok)
118 }
119 case 'H':
120 h := t.Hour() % 12
121 if h == 0 {
122 h = 12
123 }
124 switch len(tok) {
125 case 1:
126 fmt.Fprintf(&buf, "%d", h)
127 case 2:
128 fmt.Fprintf(&buf, "%02d", h)
129 default:
130 return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: 12-hour must either be \"H\" or \"HH\"", tok)
131 }
132 case 'A', 'a':
133 if len(tok) != 2 {
134 return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: must be \"%s%s\"", tok, tok[0:1], tok[0:1])
135 }
136 upper := tok[0] == 'A'
137 switch t.Hour() / 12 {
138 case 0:
139 if upper {
140 buf.WriteString("AM")
141 } else {
142 buf.WriteString("am")
143 }
144 case 1:
145 if upper {
146 buf.WriteString("PM")
147 } else {
148 buf.WriteString("pm")
149 }
150 }
151 case 'm':
152 m := t.Minute()
153 switch len(tok) {
154 case 1:
155 fmt.Fprintf(&buf, "%d", m)
156 case 2:
157 fmt.Fprintf(&buf, "%02d", m)
158 default:
159 return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: minute must either be \"m\" or \"mm\"", tok)
160 }
161 case 's':
162 s := t.Second()
163 switch len(tok) {
164 case 1:
165 fmt.Fprintf(&buf, "%d", s)
166 case 2:
167 fmt.Fprintf(&buf, "%02d", s)
168 default:
169 return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: second must either be \"s\" or \"ss\"", tok)
170 }
171 case 'Z':
172 // We'll just lean on Go's own formatter for this one, since
173 // the necessary information is unexported.
174 switch len(tok) {
175 case 1:
176 buf.WriteString(t.Format("Z07:00"))
177 case 3:
178 str := t.Format("-0700")
179 switch str {
180 case "+0000":
181 buf.WriteString("UTC")
182 default:
183 buf.WriteString(str)
184 }
185 case 4:
186 buf.WriteString(t.Format("-0700"))
187 case 5:
188 buf.WriteString(t.Format("-07:00"))
189 default:
190 return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q: timezone must be Z, ZZZZ, or ZZZZZ", tok)
191 }
192 default:
193 return cty.DynamicVal, function.NewArgErrorf(0, "invalid date format verb %q", tok)
194 }
195
196 default:
197 // Any other starting character indicates a literal sequence
198 buf.Write(tok)
199 }
200 }
201
202 return cty.StringVal(buf.String()), nil
203 },
204 })
205
206 // FormatDate reformats a timestamp given in RFC3339 syntax into another time
207 // syntax defined by a given format string.
208 //
209 // The format string uses letter mnemonics to represent portions of the
210 // timestamp, with repetition signifying length variants of each portion.
211 // Single quote characters ' can be used to quote sequences of literal letters
212 // that should not be interpreted as formatting mnemonics.
213 //
214 // The full set of supported mnemonic sequences is listed below:
215 //
216 // YY Year modulo 100 zero-padded to two digits, like "06".
217 // YYYY Four (or more) digit year, like "2006".
218 // M Month number, like "1" for January.
219 // MM Month number zero-padded to two digits, like "01".
220 // MMM English month name abbreviated to three letters, like "Jan".
221 // MMMM English month name unabbreviated, like "January".
222 // D Day of month number, like "2".
223 // DD Day of month number zero-padded to two digits, like "02".
224 // EEE English day of week name abbreviated to three letters, like "Mon".
225 // EEEE English day of week name unabbreviated, like "Monday".
226 // h 24-hour number, like "2".
227 // hh 24-hour number zero-padded to two digits, like "02".
228 // H 12-hour number, like "2".
229 // HH 12-hour number zero-padded to two digits, like "02".
230 // AA Hour AM/PM marker in uppercase, like "AM".
231 // aa Hour AM/PM marker in lowercase, like "am".
232 // m Minute within hour, like "5".
233 // mm Minute within hour zero-padded to two digits, like "05".
234 // s Second within minute, like "9".
235 // ss Second within minute zero-padded to two digits, like "09".
236 // ZZZZ Timezone offset with just sign and digit, like "-0800".
237 // ZZZZZ Timezone offset with colon separating hours and minutes, like "-08:00".
238 // Z Like ZZZZZ but with a special case "Z" for UTC.
239 // ZZZ Like ZZZZ but with a special case "UTC" for UTC.
240 //
241 // The format syntax is optimized mainly for generating machine-oriented
242 // timestamps rather than human-oriented timestamps; the English language
243 // portions of the output reflect the use of English names in a number of
244 // machine-readable date formatting standards. For presentation to humans,
245 // a locale-aware time formatter (not included in this package) is a better
246 // choice.
247 //
248 // The format syntax is not compatible with that of any other language, but
249 // is optimized so that patterns for common standard date formats can be
250 // recognized quickly even by a reader unfamiliar with the format syntax.
251 func FormatDate(format cty.Value, timestamp cty.Value) (cty.Value, error) {
252 return FormatDateFunc.Call([]cty.Value{format, timestamp})
253 }
254
255 func parseTimestamp(ts string) (time.Time, error) {
256 t, err := time.Parse(time.RFC3339, ts)
257 if err != nil {
258 switch err := err.(type) {
259 case *time.ParseError:
260 // If err is s time.ParseError then its string representation is not
261 // appropriate since it relies on details of Go's strange date format
262 // representation, which a caller of our functions is not expected
263 // to be familiar with.
264 //
265 // Therefore we do some light transformation to get a more suitable
266 // error that should make more sense to our callers. These are
267 // still not awesome error messages, but at least they refer to
268 // the timestamp portions by name rather than by Go's example
269 // values.
270 if err.LayoutElem == "" && err.ValueElem == "" && err.Message != "" {
271 // For some reason err.Message is populated with a ": " prefix
272 // by the time package.
273 return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp%s", err.Message)
274 }
275 var what string
276 switch err.LayoutElem {
277 case "2006":
278 what = "year"
279 case "01":
280 what = "month"
281 case "02":
282 what = "day of month"
283 case "15":
284 what = "hour"
285 case "04":
286 what = "minute"
287 case "05":
288 what = "second"
289 case "Z07:00":
290 what = "UTC offset"
291 case "T":
292 return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: missing required time introducer 'T'")
293 case ":", "-":
294 if err.ValueElem == "" {
295 return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: end of string where %q is expected", err.LayoutElem)
296 } else {
297 return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: found %q where %q is expected", err.ValueElem, err.LayoutElem)
298 }
299 default:
300 // Should never get here, because time.RFC3339 includes only the
301 // above portions, but since that might change in future we'll
302 // be robust here.
303 what = "timestamp segment"
304 }
305 if err.ValueElem == "" {
306 return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: end of string before %s", what)
307 } else {
308 return time.Time{}, fmt.Errorf("not a valid RFC3339 timestamp: cannot use %q as %s", err.ValueElem, what)
309 }
310 }
311 return time.Time{}, err
312 }
313 return t, nil
314 }
315
316 // splitDataFormat is a bufio.SplitFunc used to tokenize a date format.
317 func splitDateFormat(data []byte, atEOF bool) (advance int, token []byte, err error) {
318 if len(data) == 0 {
319 return 0, nil, nil
320 }
321
322 const esc = '\''
323
324 switch {
325
326 case data[0] == esc:
327 // If we have another quote immediately after then this is a single
328 // escaped escape.
329 if len(data) > 1 && data[1] == esc {
330 return 2, data[:2], nil
331 }
332
333 // Beginning of quoted sequence, so we will seek forward until we find
334 // the closing quote, ignoring escaped quotes along the way.
335 for i := 1; i < len(data); i++ {
336 if data[i] == esc {
337 if (i + 1) == len(data) {
338 // We need at least one more byte to decide if this is an
339 // escape or a terminator.
340 return 0, nil, nil
341 }
342 if data[i+1] == esc {
343 i++ // doubled-up quotes are an escape sequence
344 continue
345 }
346 // We've found the closing quote
347 return i + 1, data[:i+1], nil
348 }
349 }
350 // If we fall out here then we need more bytes to find the end,
351 // unless we're already at the end with an unclosed quote.
352 if atEOF {
353 return len(data), data, nil
354 }
355 return 0, nil, nil
356
357 case startsDateFormatVerb(data[0]):
358 rep := data[0]
359 for i := 1; i < len(data); i++ {
360 if data[i] != rep {
361 return i, data[:i], nil
362 }
363 }
364 if atEOF {
365 return len(data), data, nil
366 }
367 // We need more data to decide if we've found the end
368 return 0, nil, nil
369
370 default:
371 for i := 1; i < len(data); i++ {
372 if data[i] == esc || startsDateFormatVerb(data[i]) {
373 return i, data[:i], nil
374 }
375 }
376 // We might not actually be at the end of a literal sequence,
377 // but that doesn't matter since we'll concat them back together
378 // anyway.
379 return len(data), data, nil
380 }
381 }
382
383 func startsDateFormatVerb(b byte) bool {
384 return (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z')
385 }