1// Copyright 2016 Google LLC
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package toml
16
17import (
18	"encoding/json"
19	"reflect"
20	"testing"
21	"time"
22)
23
24func cmpEqual(x, y interface{}) bool {
25	return reflect.DeepEqual(x, y)
26}
27
28func TestDates(t *testing.T) {
29	for _, test := range []struct {
30		date     LocalDate
31		loc      *time.Location
32		wantStr  string
33		wantTime time.Time
34	}{
35		{
36			date:     LocalDate{2014, 7, 29},
37			loc:      time.Local,
38			wantStr:  "2014-07-29",
39			wantTime: time.Date(2014, time.July, 29, 0, 0, 0, 0, time.Local),
40		},
41		{
42			date:     LocalDateOf(time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local)),
43			loc:      time.UTC,
44			wantStr:  "2014-08-20",
45			wantTime: time.Date(2014, 8, 20, 0, 0, 0, 0, time.UTC),
46		},
47		{
48			date:     LocalDateOf(time.Date(999, time.January, 26, 0, 0, 0, 0, time.Local)),
49			loc:      time.UTC,
50			wantStr:  "0999-01-26",
51			wantTime: time.Date(999, 1, 26, 0, 0, 0, 0, time.UTC),
52		},
53	} {
54		if got := test.date.String(); got != test.wantStr {
55			t.Errorf("%#v.String() = %q, want %q", test.date, got, test.wantStr)
56		}
57		if got := test.date.In(test.loc); !got.Equal(test.wantTime) {
58			t.Errorf("%#v.In(%v) = %v, want %v", test.date, test.loc, got, test.wantTime)
59		}
60	}
61}
62
63func TestDateIsValid(t *testing.T) {
64	for _, test := range []struct {
65		date LocalDate
66		want bool
67	}{
68		{LocalDate{2014, 7, 29}, true},
69		{LocalDate{2000, 2, 29}, true},
70		{LocalDate{10000, 12, 31}, true},
71		{LocalDate{1, 1, 1}, true},
72		{LocalDate{0, 1, 1}, true},  // year zero is OK
73		{LocalDate{-1, 1, 1}, true}, // negative year is OK
74		{LocalDate{1, 0, 1}, false},
75		{LocalDate{1, 1, 0}, false},
76		{LocalDate{2016, 1, 32}, false},
77		{LocalDate{2016, 13, 1}, false},
78		{LocalDate{1, -1, 1}, false},
79		{LocalDate{1, 1, -1}, false},
80	} {
81		got := test.date.IsValid()
82		if got != test.want {
83			t.Errorf("%#v: got %t, want %t", test.date, got, test.want)
84		}
85	}
86}
87
88func TestParseDate(t *testing.T) {
89	for _, test := range []struct {
90		str  string
91		want LocalDate // if empty, expect an error
92	}{
93		{"2016-01-02", LocalDate{2016, 1, 2}},
94		{"2016-12-31", LocalDate{2016, 12, 31}},
95		{"0003-02-04", LocalDate{3, 2, 4}},
96		{"999-01-26", LocalDate{}},
97		{"", LocalDate{}},
98		{"2016-01-02x", LocalDate{}},
99	} {
100		got, err := ParseLocalDate(test.str)
101		if got != test.want {
102			t.Errorf("ParseLocalDate(%q) = %+v, want %+v", test.str, got, test.want)
103		}
104		if err != nil && test.want != (LocalDate{}) {
105			t.Errorf("Unexpected error %v from ParseLocalDate(%q)", err, test.str)
106		}
107	}
108}
109
110func TestDateArithmetic(t *testing.T) {
111	for _, test := range []struct {
112		desc  string
113		start LocalDate
114		end   LocalDate
115		days  int
116	}{
117		{
118			desc:  "zero days noop",
119			start: LocalDate{2014, 5, 9},
120			end:   LocalDate{2014, 5, 9},
121			days:  0,
122		},
123		{
124			desc:  "crossing a year boundary",
125			start: LocalDate{2014, 12, 31},
126			end:   LocalDate{2015, 1, 1},
127			days:  1,
128		},
129		{
130			desc:  "negative number of days",
131			start: LocalDate{2015, 1, 1},
132			end:   LocalDate{2014, 12, 31},
133			days:  -1,
134		},
135		{
136			desc:  "full leap year",
137			start: LocalDate{2004, 1, 1},
138			end:   LocalDate{2005, 1, 1},
139			days:  366,
140		},
141		{
142			desc:  "full non-leap year",
143			start: LocalDate{2001, 1, 1},
144			end:   LocalDate{2002, 1, 1},
145			days:  365,
146		},
147		{
148			desc:  "crossing a leap second",
149			start: LocalDate{1972, 6, 30},
150			end:   LocalDate{1972, 7, 1},
151			days:  1,
152		},
153		{
154			desc:  "dates before the unix epoch",
155			start: LocalDate{101, 1, 1},
156			end:   LocalDate{102, 1, 1},
157			days:  365,
158		},
159	} {
160		if got := test.start.AddDays(test.days); got != test.end {
161			t.Errorf("[%s] %#v.AddDays(%v) = %#v, want %#v", test.desc, test.start, test.days, got, test.end)
162		}
163		if got := test.end.DaysSince(test.start); got != test.days {
164			t.Errorf("[%s] %#v.Sub(%#v) = %v, want %v", test.desc, test.end, test.start, got, test.days)
165		}
166	}
167}
168
169func TestDateBefore(t *testing.T) {
170	for _, test := range []struct {
171		d1, d2 LocalDate
172		want   bool
173	}{
174		{LocalDate{2016, 12, 31}, LocalDate{2017, 1, 1}, true},
175		{LocalDate{2016, 1, 1}, LocalDate{2016, 1, 1}, false},
176		{LocalDate{2016, 12, 30}, LocalDate{2016, 12, 31}, true},
177		{LocalDate{2016, 1, 30}, LocalDate{2016, 12, 31}, true},
178	} {
179		if got := test.d1.Before(test.d2); got != test.want {
180			t.Errorf("%v.Before(%v): got %t, want %t", test.d1, test.d2, got, test.want)
181		}
182	}
183}
184
185func TestDateAfter(t *testing.T) {
186	for _, test := range []struct {
187		d1, d2 LocalDate
188		want   bool
189	}{
190		{LocalDate{2016, 12, 31}, LocalDate{2017, 1, 1}, false},
191		{LocalDate{2016, 1, 1}, LocalDate{2016, 1, 1}, false},
192		{LocalDate{2016, 12, 30}, LocalDate{2016, 12, 31}, false},
193	} {
194		if got := test.d1.After(test.d2); got != test.want {
195			t.Errorf("%v.After(%v): got %t, want %t", test.d1, test.d2, got, test.want)
196		}
197	}
198}
199
200func TestTimeToString(t *testing.T) {
201	for _, test := range []struct {
202		str       string
203		time      LocalTime
204		roundTrip bool // ParseLocalTime(str).String() == str?
205	}{
206		{"13:26:33", LocalTime{13, 26, 33, 0}, true},
207		{"01:02:03.000023456", LocalTime{1, 2, 3, 23456}, true},
208		{"00:00:00.000000001", LocalTime{0, 0, 0, 1}, true},
209		{"13:26:03.1", LocalTime{13, 26, 3, 100000000}, false},
210		{"13:26:33.0000003", LocalTime{13, 26, 33, 300}, false},
211	} {
212		gotTime, err := ParseLocalTime(test.str)
213		if err != nil {
214			t.Errorf("ParseLocalTime(%q): got error: %v", test.str, err)
215			continue
216		}
217		if gotTime != test.time {
218			t.Errorf("ParseLocalTime(%q) = %+v, want %+v", test.str, gotTime, test.time)
219		}
220		if test.roundTrip {
221			gotStr := test.time.String()
222			if gotStr != test.str {
223				t.Errorf("%#v.String() = %q, want %q", test.time, gotStr, test.str)
224			}
225		}
226	}
227}
228
229func TestTimeOf(t *testing.T) {
230	for _, test := range []struct {
231		time time.Time
232		want LocalTime
233	}{
234		{time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local), LocalTime{15, 8, 43, 1}},
235		{time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), LocalTime{0, 0, 0, 0}},
236	} {
237		if got := LocalTimeOf(test.time); got != test.want {
238			t.Errorf("LocalTimeOf(%v) = %+v, want %+v", test.time, got, test.want)
239		}
240	}
241}
242
243func TestTimeIsValid(t *testing.T) {
244	for _, test := range []struct {
245		time LocalTime
246		want bool
247	}{
248		{LocalTime{0, 0, 0, 0}, true},
249		{LocalTime{23, 0, 0, 0}, true},
250		{LocalTime{23, 59, 59, 999999999}, true},
251		{LocalTime{24, 59, 59, 999999999}, false},
252		{LocalTime{23, 60, 59, 999999999}, false},
253		{LocalTime{23, 59, 60, 999999999}, false},
254		{LocalTime{23, 59, 59, 1000000000}, false},
255		{LocalTime{-1, 0, 0, 0}, false},
256		{LocalTime{0, -1, 0, 0}, false},
257		{LocalTime{0, 0, -1, 0}, false},
258		{LocalTime{0, 0, 0, -1}, false},
259	} {
260		got := test.time.IsValid()
261		if got != test.want {
262			t.Errorf("%#v: got %t, want %t", test.time, got, test.want)
263		}
264	}
265}
266
267func TestDateTimeToString(t *testing.T) {
268	for _, test := range []struct {
269		str       string
270		dateTime  LocalDateTime
271		roundTrip bool // ParseLocalDateTime(str).String() == str?
272	}{
273		{"2016-03-22T13:26:33", LocalDateTime{LocalDate{2016, 03, 22}, LocalTime{13, 26, 33, 0}}, true},
274		{"2016-03-22T13:26:33.000000600", LocalDateTime{LocalDate{2016, 03, 22}, LocalTime{13, 26, 33, 600}}, true},
275		{"2016-03-22t13:26:33", LocalDateTime{LocalDate{2016, 03, 22}, LocalTime{13, 26, 33, 0}}, false},
276	} {
277		gotDateTime, err := ParseLocalDateTime(test.str)
278		if err != nil {
279			t.Errorf("ParseLocalDateTime(%q): got error: %v", test.str, err)
280			continue
281		}
282		if gotDateTime != test.dateTime {
283			t.Errorf("ParseLocalDateTime(%q) = %+v, want %+v", test.str, gotDateTime, test.dateTime)
284		}
285		if test.roundTrip {
286			gotStr := test.dateTime.String()
287			if gotStr != test.str {
288				t.Errorf("%#v.String() = %q, want %q", test.dateTime, gotStr, test.str)
289			}
290		}
291	}
292}
293
294func TestParseDateTimeErrors(t *testing.T) {
295	for _, str := range []string{
296		"",
297		"2016-03-22",           // just a date
298		"13:26:33",             // just a time
299		"2016-03-22 13:26:33",  // wrong separating character
300		"2016-03-22T13:26:33x", // extra at end
301	} {
302		if _, err := ParseLocalDateTime(str); err == nil {
303			t.Errorf("ParseLocalDateTime(%q) succeeded, want error", str)
304		}
305	}
306}
307
308func TestDateTimeOf(t *testing.T) {
309	for _, test := range []struct {
310		time time.Time
311		want LocalDateTime
312	}{
313		{time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local),
314			LocalDateTime{LocalDate{2014, 8, 20}, LocalTime{15, 8, 43, 1}}},
315		{time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC),
316			LocalDateTime{LocalDate{1, 1, 1}, LocalTime{0, 0, 0, 0}}},
317	} {
318		if got := LocalDateTimeOf(test.time); got != test.want {
319			t.Errorf("LocalDateTimeOf(%v) = %+v, want %+v", test.time, got, test.want)
320		}
321	}
322}
323
324func TestDateTimeIsValid(t *testing.T) {
325	// No need to be exhaustive here; it's just LocalDate.IsValid && LocalTime.IsValid.
326	for _, test := range []struct {
327		dt   LocalDateTime
328		want bool
329	}{
330		{LocalDateTime{LocalDate{2016, 3, 20}, LocalTime{0, 0, 0, 0}}, true},
331		{LocalDateTime{LocalDate{2016, -3, 20}, LocalTime{0, 0, 0, 0}}, false},
332		{LocalDateTime{LocalDate{2016, 3, 20}, LocalTime{24, 0, 0, 0}}, false},
333	} {
334		got := test.dt.IsValid()
335		if got != test.want {
336			t.Errorf("%#v: got %t, want %t", test.dt, got, test.want)
337		}
338	}
339}
340
341func TestDateTimeIn(t *testing.T) {
342	dt := LocalDateTime{LocalDate{2016, 1, 2}, LocalTime{3, 4, 5, 6}}
343	got := dt.In(time.UTC)
344	want := time.Date(2016, 1, 2, 3, 4, 5, 6, time.UTC)
345	if !got.Equal(want) {
346		t.Errorf("got %v, want %v", got, want)
347	}
348}
349
350func TestDateTimeBefore(t *testing.T) {
351	d1 := LocalDate{2016, 12, 31}
352	d2 := LocalDate{2017, 1, 1}
353	t1 := LocalTime{5, 6, 7, 8}
354	t2 := LocalTime{5, 6, 7, 9}
355	for _, test := range []struct {
356		dt1, dt2 LocalDateTime
357		want     bool
358	}{
359		{LocalDateTime{d1, t1}, LocalDateTime{d2, t1}, true},
360		{LocalDateTime{d1, t1}, LocalDateTime{d1, t2}, true},
361		{LocalDateTime{d2, t1}, LocalDateTime{d1, t1}, false},
362		{LocalDateTime{d2, t1}, LocalDateTime{d2, t1}, false},
363	} {
364		if got := test.dt1.Before(test.dt2); got != test.want {
365			t.Errorf("%v.Before(%v): got %t, want %t", test.dt1, test.dt2, got, test.want)
366		}
367	}
368}
369
370func TestDateTimeAfter(t *testing.T) {
371	d1 := LocalDate{2016, 12, 31}
372	d2 := LocalDate{2017, 1, 1}
373	t1 := LocalTime{5, 6, 7, 8}
374	t2 := LocalTime{5, 6, 7, 9}
375	for _, test := range []struct {
376		dt1, dt2 LocalDateTime
377		want     bool
378	}{
379		{LocalDateTime{d1, t1}, LocalDateTime{d2, t1}, false},
380		{LocalDateTime{d1, t1}, LocalDateTime{d1, t2}, false},
381		{LocalDateTime{d2, t1}, LocalDateTime{d1, t1}, true},
382		{LocalDateTime{d2, t1}, LocalDateTime{d2, t1}, false},
383	} {
384		if got := test.dt1.After(test.dt2); got != test.want {
385			t.Errorf("%v.After(%v): got %t, want %t", test.dt1, test.dt2, got, test.want)
386		}
387	}
388}
389
390func TestMarshalJSON(t *testing.T) {
391	for _, test := range []struct {
392		value interface{}
393		want  string
394	}{
395		{LocalDate{1987, 4, 15}, `"1987-04-15"`},
396		{LocalTime{18, 54, 2, 0}, `"18:54:02"`},
397		{LocalDateTime{LocalDate{1987, 4, 15}, LocalTime{18, 54, 2, 0}}, `"1987-04-15T18:54:02"`},
398	} {
399		bgot, err := json.Marshal(test.value)
400		if err != nil {
401			t.Fatal(err)
402		}
403		if got := string(bgot); got != test.want {
404			t.Errorf("%#v: got %s, want %s", test.value, got, test.want)
405		}
406	}
407}
408
409func TestUnmarshalJSON(t *testing.T) {
410	var d LocalDate
411	var tm LocalTime
412	var dt LocalDateTime
413	for _, test := range []struct {
414		data string
415		ptr  interface{}
416		want interface{}
417	}{
418		{`"1987-04-15"`, &d, &LocalDate{1987, 4, 15}},
419		{`"1987-04-\u0031\u0035"`, &d, &LocalDate{1987, 4, 15}},
420		{`"18:54:02"`, &tm, &LocalTime{18, 54, 2, 0}},
421		{`"1987-04-15T18:54:02"`, &dt, &LocalDateTime{LocalDate{1987, 4, 15}, LocalTime{18, 54, 2, 0}}},
422	} {
423		if err := json.Unmarshal([]byte(test.data), test.ptr); err != nil {
424			t.Fatalf("%s: %v", test.data, err)
425		}
426		if !cmpEqual(test.ptr, test.want) {
427			t.Errorf("%s: got %#v, want %#v", test.data, test.ptr, test.want)
428		}
429	}
430
431	for _, bad := range []string{"", `""`, `"bad"`, `"1987-04-15x"`,
432		`19870415`,     // a JSON number
433		`11987-04-15x`, // not a JSON string
434
435	} {
436		if json.Unmarshal([]byte(bad), &d) == nil {
437			t.Errorf("%q, LocalDate: got nil, want error", bad)
438		}
439		if json.Unmarshal([]byte(bad), &tm) == nil {
440			t.Errorf("%q, LocalTime: got nil, want error", bad)
441		}
442		if json.Unmarshal([]byte(bad), &dt) == nil {
443			t.Errorf("%q, LocalDateTime: got nil, want error", bad)
444		}
445	}
446}
447