1package stdlib
2
3import (
4	"fmt"
5	"testing"
6	"time"
7
8	"github.com/zclconf/go-cty/cty"
9)
10
11func TestFormatDate(t *testing.T) {
12	tests := []struct {
13		Format cty.Value
14		Want   cty.Value
15		Err    string
16	}{
17		{
18			cty.StringVal(""), // pointless, but valid
19			cty.StringVal(""),
20			``,
21		},
22		{
23			cty.StringVal("YYYY-MM-DD"),
24			cty.StringVal("2006-01-02"),
25			``,
26		},
27		{
28			cty.StringVal("EEE, MMM D ''YY"),
29			cty.StringVal("Mon, Jan 2 '06"),
30			``,
31		},
32		{
33			cty.StringVal("hh:mm:ss"),
34			cty.StringVal("15:04:05"),
35			``,
36		},
37		{
38			cty.StringVal("H 'o''clock' AA"),
39			cty.StringVal("3 o'clock PM"),
40			``,
41		},
42		{
43			cty.StringVal("H 'o''clock'"),
44			cty.StringVal("3 o'clock"),
45			``,
46		},
47		{
48			cty.StringVal("hh:mm:ssZZZZ"),
49			cty.StringVal("15:04:05+0000"),
50			``,
51		},
52		{
53			cty.StringVal("hh:mm:ssZZZZZ"),
54			cty.StringVal("15:04:05+00:00"),
55			``,
56		},
57		{
58			cty.StringVal("MMMM"),
59			cty.StringVal("January"),
60			``,
61		},
62		{
63			cty.StringVal("EEEE"),
64			cty.StringVal("Monday"),
65			``,
66		},
67		{
68			cty.StringVal("aa"),
69			cty.StringVal("pm"),
70			``,
71		},
72
73		// Some common standard machine-oriented formats
74		{
75			cty.StringVal("YYYY-MM-DD'T'hh:mm:ssZ"), // RFC3339
76			cty.StringVal("2006-01-02T15:04:05Z"),   // (since RFC3339 is the input format too, this is a bit pointless)
77			``,
78		},
79		{
80			cty.StringVal("DD MMM YYYY hh:mm ZZZ"), // RFC822
81			cty.StringVal("02 Jan 2006 15:04 UTC"),
82			``,
83		},
84		{
85			cty.StringVal("EEEE, DD-MMM-YY hh:mm:ss ZZZ"), // RFC850
86			cty.StringVal("Monday, 02-Jan-06 15:04:05 UTC"),
87			``,
88		},
89		{
90			cty.StringVal("EEE, DD MMM YYYY hh:mm:ss ZZZ"), // RFC1123
91			cty.StringVal("Mon, 02 Jan 2006 15:04:05 UTC"),
92			``,
93		},
94
95		// Invalids
96		{
97			cty.StringVal("Y"),
98			cty.NilVal,
99			`invalid date format verb "Y": year must either be "YY" or "YYYY"`,
100		},
101		{
102			cty.StringVal("YYYYY"),
103			cty.NilVal,
104			`invalid date format verb "YYYYY": year must either be "YY" or "YYYY"`,
105		},
106		{
107			cty.StringVal("A"),
108			cty.NilVal,
109			`invalid date format verb "A": must be "AA"`,
110		},
111		{
112			cty.StringVal("a"),
113			cty.NilVal,
114			`invalid date format verb "a": must be "aa"`,
115		},
116		{
117			cty.StringVal("'blah blah"),
118			cty.NilVal,
119			`unterminated literal '`,
120		},
121		{
122			cty.StringVal("'"),
123			cty.NilVal,
124			`unterminated literal '`,
125		},
126	}
127	ts := time.Date(2006, time.January, 2, 15, 04, 05, 0, time.UTC)
128	timeVal := cty.StringVal(ts.Format(time.RFC3339))
129	for _, test := range tests {
130		t.Run(test.Format.GoString(), func(t *testing.T) {
131			got, err := FormatDate(test.Format, timeVal)
132
133			if test.Err != "" {
134				if err == nil {
135					t.Fatalf("no error; want error %q", test.Err)
136				}
137
138				if got, want := err.Error(), test.Err; got != want {
139					t.Fatalf("wrong error\ngot:  %s\nwant: %s", got, want)
140				}
141			} else {
142				if err != nil {
143					t.Fatalf("unexpected error: %s", err)
144				}
145
146				if !got.RawEquals(test.Want) {
147					t.Errorf("wrong result\ngot:  %#v\nwant: %#v", got, test.Want)
148				}
149			}
150		})
151	}
152
153	parseErrTests := []struct {
154		Timestamp cty.Value
155		Err       string
156	}{
157		{
158			cty.StringVal(""),
159			`not a valid RFC3339 timestamp: end of string before year`,
160		},
161		{
162			cty.StringVal("2017-01-02"),
163			`not a valid RFC3339 timestamp: missing required time introducer 'T'`,
164		},
165		{
166			cty.StringVal(`2017-12-02t00:00:00Z`),
167			`not a valid RFC3339 timestamp: missing required time introducer 'T'`,
168		},
169		{
170			cty.StringVal("2017:01:02"),
171			`not a valid RFC3339 timestamp: found ":01:02" where "-" is expected`,
172		},
173		{
174			cty.StringVal("2017"),
175			`not a valid RFC3339 timestamp: end of string where "-" is expected`,
176		},
177		{
178			cty.StringVal("2017-01-02T"),
179			`not a valid RFC3339 timestamp: end of string before hour`,
180		},
181		{
182			cty.StringVal("2017-01-02T00"),
183			`not a valid RFC3339 timestamp: end of string where ":" is expected`,
184		},
185		{
186			cty.StringVal("2017-01-02T00:00:00"),
187			`not a valid RFC3339 timestamp: end of string before UTC offset`,
188		},
189		{
190			cty.StringVal("2017-01-02T26:00:00Z"),
191			// This one generates an odd message due to an apparent quirk in
192			// the Go time parser. Ideally it would use "26" as the errant string.
193			`not a valid RFC3339 timestamp: cannot use ":00:00Z" as hour`,
194		},
195		{
196			cty.StringVal("2017-13-02T00:00:00Z"),
197			// This one generates an odd message due to an apparent quirk in
198			// the Go time parser. Ideally it would use "13" as the errant string.
199			`not a valid RFC3339 timestamp: cannot use "-02T00:00:00Z" as month`,
200		},
201		{
202			cty.StringVal("2017-02-31T00:00:00Z"),
203			`not a valid RFC3339 timestamp: day out of range`,
204		},
205		{
206			cty.StringVal(`"2017-12-02T00:00:00Z"`),
207			`not a valid RFC3339 timestamp: cannot use "\"2017-12-02T00:00:00Z\"" as year`,
208		},
209		{
210			cty.StringVal(`2-12-02T00:00:00Z`),
211			// Go parser seems to be trying to parse "2-12" as a year here,
212			// producing a confusing error message.
213			`not a valid RFC3339 timestamp: cannot use "-02T00:00:00Z" as year`,
214		},
215	}
216	for _, test := range parseErrTests {
217		t.Run(fmt.Sprintf("%s parse error", test.Timestamp.AsString()), func(t *testing.T) {
218			_, err := FormatDate(cty.StringVal(""), test.Timestamp)
219
220			if err == nil {
221				t.Fatalf("no error; want error %q", test.Err)
222			}
223
224			if got, want := err.Error(), test.Err; got != want {
225				t.Fatalf("wrong error\ngot:  %s\nwant: %s", got, want)
226			}
227		})
228	}
229}
230