// Copyright 2016 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package toml import ( "encoding/json" "reflect" "testing" "time" ) func cmpEqual(x, y interface{}) bool { return reflect.DeepEqual(x, y) } func TestDates(t *testing.T) { for _, test := range []struct { date LocalDate loc *time.Location wantStr string wantTime time.Time }{ { date: LocalDate{2014, 7, 29}, loc: time.Local, wantStr: "2014-07-29", wantTime: time.Date(2014, time.July, 29, 0, 0, 0, 0, time.Local), }, { date: LocalDateOf(time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local)), loc: time.UTC, wantStr: "2014-08-20", wantTime: time.Date(2014, 8, 20, 0, 0, 0, 0, time.UTC), }, { date: LocalDateOf(time.Date(999, time.January, 26, 0, 0, 0, 0, time.Local)), loc: time.UTC, wantStr: "0999-01-26", wantTime: time.Date(999, 1, 26, 0, 0, 0, 0, time.UTC), }, } { if got := test.date.String(); got != test.wantStr { t.Errorf("%#v.String() = %q, want %q", test.date, got, test.wantStr) } if got := test.date.In(test.loc); !got.Equal(test.wantTime) { t.Errorf("%#v.In(%v) = %v, want %v", test.date, test.loc, got, test.wantTime) } } } func TestDateIsValid(t *testing.T) { for _, test := range []struct { date LocalDate want bool }{ {LocalDate{2014, 7, 29}, true}, {LocalDate{2000, 2, 29}, true}, {LocalDate{10000, 12, 31}, true}, {LocalDate{1, 1, 1}, true}, {LocalDate{0, 1, 1}, true}, // year zero is OK {LocalDate{-1, 1, 1}, true}, // negative year is OK {LocalDate{1, 0, 1}, false}, {LocalDate{1, 1, 0}, false}, {LocalDate{2016, 1, 32}, false}, {LocalDate{2016, 13, 1}, false}, {LocalDate{1, -1, 1}, false}, {LocalDate{1, 1, -1}, false}, } { got := test.date.IsValid() if got != test.want { t.Errorf("%#v: got %t, want %t", test.date, got, test.want) } } } func TestParseDate(t *testing.T) { for _, test := range []struct { str string want LocalDate // if empty, expect an error }{ {"2016-01-02", LocalDate{2016, 1, 2}}, {"2016-12-31", LocalDate{2016, 12, 31}}, {"0003-02-04", LocalDate{3, 2, 4}}, {"999-01-26", LocalDate{}}, {"", LocalDate{}}, {"2016-01-02x", LocalDate{}}, } { got, err := ParseLocalDate(test.str) if got != test.want { t.Errorf("ParseLocalDate(%q) = %+v, want %+v", test.str, got, test.want) } if err != nil && test.want != (LocalDate{}) { t.Errorf("Unexpected error %v from ParseLocalDate(%q)", err, test.str) } } } func TestDateArithmetic(t *testing.T) { for _, test := range []struct { desc string start LocalDate end LocalDate days int }{ { desc: "zero days noop", start: LocalDate{2014, 5, 9}, end: LocalDate{2014, 5, 9}, days: 0, }, { desc: "crossing a year boundary", start: LocalDate{2014, 12, 31}, end: LocalDate{2015, 1, 1}, days: 1, }, { desc: "negative number of days", start: LocalDate{2015, 1, 1}, end: LocalDate{2014, 12, 31}, days: -1, }, { desc: "full leap year", start: LocalDate{2004, 1, 1}, end: LocalDate{2005, 1, 1}, days: 366, }, { desc: "full non-leap year", start: LocalDate{2001, 1, 1}, end: LocalDate{2002, 1, 1}, days: 365, }, { desc: "crossing a leap second", start: LocalDate{1972, 6, 30}, end: LocalDate{1972, 7, 1}, days: 1, }, { desc: "dates before the unix epoch", start: LocalDate{101, 1, 1}, end: LocalDate{102, 1, 1}, days: 365, }, } { if got := test.start.AddDays(test.days); got != test.end { t.Errorf("[%s] %#v.AddDays(%v) = %#v, want %#v", test.desc, test.start, test.days, got, test.end) } if got := test.end.DaysSince(test.start); got != test.days { t.Errorf("[%s] %#v.Sub(%#v) = %v, want %v", test.desc, test.end, test.start, got, test.days) } } } func TestDateBefore(t *testing.T) { for _, test := range []struct { d1, d2 LocalDate want bool }{ {LocalDate{2016, 12, 31}, LocalDate{2017, 1, 1}, true}, {LocalDate{2016, 1, 1}, LocalDate{2016, 1, 1}, false}, {LocalDate{2016, 12, 30}, LocalDate{2016, 12, 31}, true}, {LocalDate{2016, 1, 30}, LocalDate{2016, 12, 31}, true}, } { if got := test.d1.Before(test.d2); got != test.want { t.Errorf("%v.Before(%v): got %t, want %t", test.d1, test.d2, got, test.want) } } } func TestDateAfter(t *testing.T) { for _, test := range []struct { d1, d2 LocalDate want bool }{ {LocalDate{2016, 12, 31}, LocalDate{2017, 1, 1}, false}, {LocalDate{2016, 1, 1}, LocalDate{2016, 1, 1}, false}, {LocalDate{2016, 12, 30}, LocalDate{2016, 12, 31}, false}, } { if got := test.d1.After(test.d2); got != test.want { t.Errorf("%v.After(%v): got %t, want %t", test.d1, test.d2, got, test.want) } } } func TestTimeToString(t *testing.T) { for _, test := range []struct { str string time LocalTime roundTrip bool // ParseLocalTime(str).String() == str? }{ {"13:26:33", LocalTime{13, 26, 33, 0}, true}, {"01:02:03.000023456", LocalTime{1, 2, 3, 23456}, true}, {"00:00:00.000000001", LocalTime{0, 0, 0, 1}, true}, {"13:26:03.1", LocalTime{13, 26, 3, 100000000}, false}, {"13:26:33.0000003", LocalTime{13, 26, 33, 300}, false}, } { gotTime, err := ParseLocalTime(test.str) if err != nil { t.Errorf("ParseLocalTime(%q): got error: %v", test.str, err) continue } if gotTime != test.time { t.Errorf("ParseLocalTime(%q) = %+v, want %+v", test.str, gotTime, test.time) } if test.roundTrip { gotStr := test.time.String() if gotStr != test.str { t.Errorf("%#v.String() = %q, want %q", test.time, gotStr, test.str) } } } } func TestTimeOf(t *testing.T) { for _, test := range []struct { time time.Time want LocalTime }{ {time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local), LocalTime{15, 8, 43, 1}}, {time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), LocalTime{0, 0, 0, 0}}, } { if got := LocalTimeOf(test.time); got != test.want { t.Errorf("LocalTimeOf(%v) = %+v, want %+v", test.time, got, test.want) } } } func TestTimeIsValid(t *testing.T) { for _, test := range []struct { time LocalTime want bool }{ {LocalTime{0, 0, 0, 0}, true}, {LocalTime{23, 0, 0, 0}, true}, {LocalTime{23, 59, 59, 999999999}, true}, {LocalTime{24, 59, 59, 999999999}, false}, {LocalTime{23, 60, 59, 999999999}, false}, {LocalTime{23, 59, 60, 999999999}, false}, {LocalTime{23, 59, 59, 1000000000}, false}, {LocalTime{-1, 0, 0, 0}, false}, {LocalTime{0, -1, 0, 0}, false}, {LocalTime{0, 0, -1, 0}, false}, {LocalTime{0, 0, 0, -1}, false}, } { got := test.time.IsValid() if got != test.want { t.Errorf("%#v: got %t, want %t", test.time, got, test.want) } } } func TestDateTimeToString(t *testing.T) { for _, test := range []struct { str string dateTime LocalDateTime roundTrip bool // ParseLocalDateTime(str).String() == str? }{ {"2016-03-22T13:26:33", LocalDateTime{LocalDate{2016, 03, 22}, LocalTime{13, 26, 33, 0}}, true}, {"2016-03-22T13:26:33.000000600", LocalDateTime{LocalDate{2016, 03, 22}, LocalTime{13, 26, 33, 600}}, true}, {"2016-03-22t13:26:33", LocalDateTime{LocalDate{2016, 03, 22}, LocalTime{13, 26, 33, 0}}, false}, } { gotDateTime, err := ParseLocalDateTime(test.str) if err != nil { t.Errorf("ParseLocalDateTime(%q): got error: %v", test.str, err) continue } if gotDateTime != test.dateTime { t.Errorf("ParseLocalDateTime(%q) = %+v, want %+v", test.str, gotDateTime, test.dateTime) } if test.roundTrip { gotStr := test.dateTime.String() if gotStr != test.str { t.Errorf("%#v.String() = %q, want %q", test.dateTime, gotStr, test.str) } } } } func TestParseDateTimeErrors(t *testing.T) { for _, str := range []string{ "", "2016-03-22", // just a date "13:26:33", // just a time "2016-03-22 13:26:33", // wrong separating character "2016-03-22T13:26:33x", // extra at end } { if _, err := ParseLocalDateTime(str); err == nil { t.Errorf("ParseLocalDateTime(%q) succeeded, want error", str) } } } func TestDateTimeOf(t *testing.T) { for _, test := range []struct { time time.Time want LocalDateTime }{ {time.Date(2014, 8, 20, 15, 8, 43, 1, time.Local), LocalDateTime{LocalDate{2014, 8, 20}, LocalTime{15, 8, 43, 1}}}, {time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC), LocalDateTime{LocalDate{1, 1, 1}, LocalTime{0, 0, 0, 0}}}, } { if got := LocalDateTimeOf(test.time); got != test.want { t.Errorf("LocalDateTimeOf(%v) = %+v, want %+v", test.time, got, test.want) } } } func TestDateTimeIsValid(t *testing.T) { // No need to be exhaustive here; it's just LocalDate.IsValid && LocalTime.IsValid. for _, test := range []struct { dt LocalDateTime want bool }{ {LocalDateTime{LocalDate{2016, 3, 20}, LocalTime{0, 0, 0, 0}}, true}, {LocalDateTime{LocalDate{2016, -3, 20}, LocalTime{0, 0, 0, 0}}, false}, {LocalDateTime{LocalDate{2016, 3, 20}, LocalTime{24, 0, 0, 0}}, false}, } { got := test.dt.IsValid() if got != test.want { t.Errorf("%#v: got %t, want %t", test.dt, got, test.want) } } } func TestDateTimeIn(t *testing.T) { dt := LocalDateTime{LocalDate{2016, 1, 2}, LocalTime{3, 4, 5, 6}} got := dt.In(time.UTC) want := time.Date(2016, 1, 2, 3, 4, 5, 6, time.UTC) if !got.Equal(want) { t.Errorf("got %v, want %v", got, want) } } func TestDateTimeBefore(t *testing.T) { d1 := LocalDate{2016, 12, 31} d2 := LocalDate{2017, 1, 1} t1 := LocalTime{5, 6, 7, 8} t2 := LocalTime{5, 6, 7, 9} for _, test := range []struct { dt1, dt2 LocalDateTime want bool }{ {LocalDateTime{d1, t1}, LocalDateTime{d2, t1}, true}, {LocalDateTime{d1, t1}, LocalDateTime{d1, t2}, true}, {LocalDateTime{d2, t1}, LocalDateTime{d1, t1}, false}, {LocalDateTime{d2, t1}, LocalDateTime{d2, t1}, false}, } { if got := test.dt1.Before(test.dt2); got != test.want { t.Errorf("%v.Before(%v): got %t, want %t", test.dt1, test.dt2, got, test.want) } } } func TestDateTimeAfter(t *testing.T) { d1 := LocalDate{2016, 12, 31} d2 := LocalDate{2017, 1, 1} t1 := LocalTime{5, 6, 7, 8} t2 := LocalTime{5, 6, 7, 9} for _, test := range []struct { dt1, dt2 LocalDateTime want bool }{ {LocalDateTime{d1, t1}, LocalDateTime{d2, t1}, false}, {LocalDateTime{d1, t1}, LocalDateTime{d1, t2}, false}, {LocalDateTime{d2, t1}, LocalDateTime{d1, t1}, true}, {LocalDateTime{d2, t1}, LocalDateTime{d2, t1}, false}, } { if got := test.dt1.After(test.dt2); got != test.want { t.Errorf("%v.After(%v): got %t, want %t", test.dt1, test.dt2, got, test.want) } } } func TestMarshalJSON(t *testing.T) { for _, test := range []struct { value interface{} want string }{ {LocalDate{1987, 4, 15}, `"1987-04-15"`}, {LocalTime{18, 54, 2, 0}, `"18:54:02"`}, {LocalDateTime{LocalDate{1987, 4, 15}, LocalTime{18, 54, 2, 0}}, `"1987-04-15T18:54:02"`}, } { bgot, err := json.Marshal(test.value) if err != nil { t.Fatal(err) } if got := string(bgot); got != test.want { t.Errorf("%#v: got %s, want %s", test.value, got, test.want) } } } func TestUnmarshalJSON(t *testing.T) { var d LocalDate var tm LocalTime var dt LocalDateTime for _, test := range []struct { data string ptr interface{} want interface{} }{ {`"1987-04-15"`, &d, &LocalDate{1987, 4, 15}}, {`"1987-04-\u0031\u0035"`, &d, &LocalDate{1987, 4, 15}}, {`"18:54:02"`, &tm, &LocalTime{18, 54, 2, 0}}, {`"1987-04-15T18:54:02"`, &dt, &LocalDateTime{LocalDate{1987, 4, 15}, LocalTime{18, 54, 2, 0}}}, } { if err := json.Unmarshal([]byte(test.data), test.ptr); err != nil { t.Fatalf("%s: %v", test.data, err) } if !cmpEqual(test.ptr, test.want) { t.Errorf("%s: got %#v, want %#v", test.data, test.ptr, test.want) } } for _, bad := range []string{"", `""`, `"bad"`, `"1987-04-15x"`, `19870415`, // a JSON number `11987-04-15x`, // not a JSON string } { if json.Unmarshal([]byte(bad), &d) == nil { t.Errorf("%q, LocalDate: got nil, want error", bad) } if json.Unmarshal([]byte(bad), &tm) == nil { t.Errorf("%q, LocalTime: got nil, want error", bad) } if json.Unmarshal([]byte(bad), &dt) == nil { t.Errorf("%q, LocalDateTime: got nil, want error", bad) } } }