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