1 /* $NetBSD: calendar.c,v 1.1.1.5 2016/01/08 21:21:33 christos Exp $ */ 2 3 #include "config.h" 4 5 #include "ntp_stdlib.h" /* test fail without this include, for some reason */ 6 #include "ntp_calendar.h" 7 #include "unity.h" 8 9 #include <string.h> 10 11 static int leapdays(int year); 12 13 void setUp(void); 14 int isGT(int first, int second); 15 int leapdays(int year); 16 char * CalendarFromCalToString(const struct calendar *cal); 17 char * CalendarFromIsoToString(const struct isodate *iso); 18 int IsEqualCal(const struct calendar *expected, const struct calendar *actual); 19 int IsEqualIso(const struct isodate *expected, const struct isodate *actual); 20 char * DateFromCalToString(const struct calendar *cal); 21 char * DateFromIsoToString(const struct isodate *iso); 22 int IsEqualDateCal(const struct calendar *expected, const struct calendar *actual); 23 int IsEqualDateIso(const struct isodate *expected, const struct isodate *actual); 24 void test_DaySplitMerge(void); 25 void test_SplitYearDays1(void); 26 void test_SplitYearDays2(void); 27 void test_RataDie1(void); 28 void test_LeapYears1(void); 29 void test_LeapYears2(void); 30 void test_RoundTripDate(void); 31 void test_RoundTripYearStart(void); 32 void test_RoundTripMonthStart(void); 33 void test_RoundTripWeekStart(void); 34 void test_RoundTripDayStart(void); 35 void test_IsoCalYearsToWeeks(void); 36 void test_IsoCalWeeksToYearStart(void); 37 void test_IsoCalWeeksToYearEnd(void); 38 void test_DaySecToDate(void); 39 40 41 void 42 setUp(void) 43 { 44 init_lib(); 45 46 return; 47 } 48 49 50 /* 51 * --------------------------------------------------------------------- 52 * test support stuff 53 * --------------------------------------------------------------------- 54 */ 55 int 56 isGT(int first, int second) 57 { 58 if(first > second) { 59 return TRUE; 60 } else { 61 return FALSE; 62 } 63 } 64 65 int 66 leapdays(int year) 67 { 68 if (year % 400 == 0) 69 return 1; 70 if (year % 100 == 0) 71 return 0; 72 if (year % 4 == 0) 73 return 1; 74 return 0; 75 } 76 77 char * 78 CalendarFromCalToString( 79 const struct calendar *cal) 80 { 81 char * str = malloc(sizeof (char) * 100); 82 snprintf(str, 100, "%u-%02u-%02u (%u) %02u:%02u:%02u", 83 cal->year, (u_int)cal->month, (u_int)cal->monthday, 84 cal->yearday, 85 (u_int)cal->hour, (u_int)cal->minute, (u_int)cal->second); 86 str[99] = '\0'; /* paranoia rulez! */ 87 return str; 88 } 89 90 char * 91 CalendarFromIsoToString( 92 const struct isodate *iso) 93 { 94 char * str = emalloc (sizeof (char) * 100); 95 snprintf(str, 100, "%u-W%02u-%02u %02u:%02u:%02u", 96 iso->year, (u_int)iso->week, (u_int)iso->weekday, 97 (u_int)iso->hour, (u_int)iso->minute, (u_int)iso->second); 98 str[99] = '\0'; /* paranoia rulez! */ 99 return str; 100 } 101 102 int 103 IsEqualCal( 104 const struct calendar *expected, 105 const struct calendar *actual) 106 { 107 if (expected->year == actual->year && 108 (!expected->yearday || expected->yearday == actual->yearday) && 109 expected->month == actual->month && 110 expected->monthday == actual->monthday && 111 expected->hour == actual->hour && 112 expected->minute == actual->minute && 113 expected->second == actual->second) { 114 return TRUE; 115 } else { 116 char *p_exp = CalendarFromCalToString(expected); 117 char *p_act = CalendarFromCalToString(actual); 118 119 printf("expected: %s but was %s", p_exp, p_act); 120 121 free(p_exp); 122 free(p_act); 123 124 return FALSE; 125 } 126 } 127 128 int 129 IsEqualIso( 130 const struct isodate *expected, 131 const struct isodate *actual) 132 { 133 if (expected->year == actual->year && 134 expected->week == actual->week && 135 expected->weekday == actual->weekday && 136 expected->hour == actual->hour && 137 expected->minute == actual->minute && 138 expected->second == actual->second) { 139 return TRUE; 140 } else { 141 printf("expected: %s but was %s", 142 CalendarFromIsoToString(expected), 143 CalendarFromIsoToString(actual)); 144 return FALSE; 145 } 146 } 147 148 char * 149 DateFromCalToString( 150 const struct calendar *cal) 151 { 152 153 char * str = emalloc (sizeof (char) * 100); 154 snprintf(str, 100, "%u-%02u-%02u (%u)", 155 cal->year, (u_int)cal->month, (u_int)cal->monthday, 156 cal->yearday); 157 str[99] = '\0'; /* paranoia rulez! */ 158 return str; 159 } 160 161 char * 162 DateFromIsoToString( 163 const struct isodate *iso) 164 { 165 166 char * str = emalloc (sizeof (char) * 100); 167 snprintf(str, 100, "%u-W%02u-%02u", 168 iso->year, (u_int)iso->week, (u_int)iso->weekday); 169 str[99] = '\0'; /* paranoia rulez! */ 170 return str; 171 } 172 173 int/*BOOL*/ 174 IsEqualDateCal( 175 const struct calendar *expected, 176 const struct calendar *actual) 177 { 178 if (expected->year == actual->year && 179 (!expected->yearday || expected->yearday == actual->yearday) && 180 expected->month == actual->month && 181 expected->monthday == actual->monthday) { 182 return TRUE; 183 } else { 184 printf("expected: %s but was %s", 185 DateFromCalToString(expected), 186 DateFromCalToString(actual)); 187 return FALSE; 188 } 189 } 190 191 int/*BOOL*/ 192 IsEqualDateIso( 193 const struct isodate *expected, 194 const struct isodate *actual) 195 { 196 if (expected->year == actual->year && 197 expected->week == actual->week && 198 expected->weekday == actual->weekday) { 199 return TRUE; 200 } else { 201 printf("expected: %s but was %s", 202 DateFromIsoToString(expected), 203 DateFromIsoToString(actual)); 204 return FALSE; 205 } 206 } 207 208 209 /* 210 * --------------------------------------------------------------------- 211 * test cases 212 * --------------------------------------------------------------------- 213 */ 214 215 /* days before month, with a full-year pad at the upper end */ 216 static const u_short real_month_table[2][13] = { 217 /* -*- table for regular years -*- */ 218 { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, 219 /* -*- table for leap years -*- */ 220 { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } 221 }; 222 223 /* days in month, with one month wrap-around at both ends */ 224 static const u_short real_month_days[2][14] = { 225 /* -*- table for regular years -*- */ 226 { 31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31 }, 227 /* -*- table for leap years -*- */ 228 { 31, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31 } 229 }; 230 231 /* test the day/sec join & split ops, making sure that 32bit 232 * intermediate results would definitely overflow and the hi DWORD of 233 * the 'vint64' is definitely needed. 234 */ 235 void 236 test_DaySplitMerge(void) 237 { 238 int32 day,sec; 239 240 for (day = -1000000; day <= 1000000; day += 100) { 241 for (sec = -100000; sec <= 186400; sec += 10000) { 242 vint64 merge; 243 ntpcal_split split; 244 int32 eday; 245 int32 esec; 246 247 merge = ntpcal_dayjoin(day, sec); 248 split = ntpcal_daysplit(&merge); 249 eday = day; 250 esec = sec; 251 252 while (esec >= 86400) { 253 eday += 1; 254 esec -= 86400; 255 } 256 while (esec < 0) { 257 eday -= 1; 258 esec += 86400; 259 } 260 261 TEST_ASSERT_EQUAL(eday, split.hi); 262 TEST_ASSERT_EQUAL(esec, split.lo); 263 } 264 } 265 266 return; 267 } 268 269 void 270 test_SplitYearDays1(void) 271 { 272 int32 eyd; 273 274 for (eyd = -1; eyd <= 365; eyd++) { 275 ntpcal_split split = ntpcal_split_yeardays(eyd, 0); 276 if (split.lo >= 0 && split.hi >= 0) { 277 TEST_ASSERT_TRUE(isGT(12,split.hi)); 278 TEST_ASSERT_TRUE(isGT(real_month_days[0][split.hi+1], split.lo)); 279 int32 tyd = real_month_table[0][split.hi] + split.lo; 280 TEST_ASSERT_EQUAL(eyd, tyd); 281 } else 282 TEST_ASSERT_TRUE(eyd < 0 || eyd > 364); 283 } 284 285 return; 286 } 287 288 void 289 test_SplitYearDays2(void) 290 { 291 int32 eyd; 292 293 for (eyd = -1; eyd <= 366; eyd++) { 294 ntpcal_split split = ntpcal_split_yeardays(eyd, 1); 295 if (split.lo >= 0 && split.hi >= 0) { 296 /* basic checks do not work on compunds :( */ 297 /* would like: TEST_ASSERT_TRUE(12 > split.hi); */ 298 TEST_ASSERT_TRUE(isGT(12,split.hi)); 299 TEST_ASSERT_TRUE(isGT(real_month_days[1][split.hi+1], split.lo)); 300 int32 tyd = real_month_table[1][split.hi] + split.lo; 301 TEST_ASSERT_EQUAL(eyd, tyd); 302 } else 303 TEST_ASSERT_TRUE(eyd < 0 || eyd > 365); 304 } 305 306 return; 307 } 308 309 void 310 test_RataDie1(void) 311 { 312 int32 testDate = 1; /* 0001-01-01 (proleptic date) */ 313 struct calendar expected = { 1, 1, 1, 1 }; 314 struct calendar actual; 315 316 ntpcal_rd_to_date(&actual, testDate); 317 TEST_ASSERT_TRUE(IsEqualDateCal(&expected, &actual)); 318 319 return; 320 } 321 322 /* check last day of february for first 10000 years */ 323 void 324 test_LeapYears1(void) 325 { 326 struct calendar dateIn, dateOut; 327 328 for (dateIn.year = 1; dateIn.year < 10000; ++dateIn.year) { 329 dateIn.month = 2; 330 dateIn.monthday = 28 + leapdays(dateIn.year); 331 dateIn.yearday = 31 + dateIn.monthday; 332 333 ntpcal_rd_to_date(&dateOut, ntpcal_date_to_rd(&dateIn)); 334 335 TEST_ASSERT_TRUE(IsEqualDateCal(&dateIn, &dateOut)); 336 } 337 338 return; 339 } 340 341 /* check first day of march for first 10000 years */ 342 void 343 test_LeapYears2(void) 344 { 345 struct calendar dateIn, dateOut; 346 347 for (dateIn.year = 1; dateIn.year < 10000; ++dateIn.year) { 348 dateIn.month = 3; 349 dateIn.monthday = 1; 350 dateIn.yearday = 60 + leapdays(dateIn.year); 351 352 ntpcal_rd_to_date(&dateOut, ntpcal_date_to_rd(&dateIn)); 353 TEST_ASSERT_TRUE(IsEqualDateCal(&dateIn, &dateOut)); 354 } 355 356 return; 357 } 358 359 /* Full roundtrip from 1601-01-01 to 2400-12-31 360 * checks sequence of rata die numbers and validates date output 361 * (since the input is all nominal days of the calendar in that range 362 * and the result of the inverse calculation must match the input no 363 * invalid output can occur.) 364 */ 365 void 366 test_RoundTripDate(void) 367 { 368 struct calendar truDate, expDate = { 1600, 0, 12, 31 };; 369 int leaps; 370 int32 truRdn, expRdn = ntpcal_date_to_rd(&expDate); 371 372 while (expDate.year < 2400) { 373 expDate.year++; 374 expDate.month = 0; 375 expDate.yearday = 0; 376 leaps = leapdays(expDate.year); 377 while (expDate.month < 12) { 378 expDate.month++; 379 expDate.monthday = 0; 380 while (expDate.monthday < real_month_days[leaps][expDate.month]) { 381 expDate.monthday++; 382 expDate.yearday++; 383 expRdn++; 384 385 truRdn = ntpcal_date_to_rd(&expDate); 386 TEST_ASSERT_EQUAL(expRdn, truRdn); 387 388 ntpcal_rd_to_date(&truDate, truRdn); 389 TEST_ASSERT_TRUE(IsEqualDateCal(&expDate, &truDate)); 390 } 391 } 392 } 393 394 return; 395 } 396 397 /* Roundtrip testing on calyearstart */ 398 void 399 test_RoundTripYearStart(void) 400 { 401 static const time_t pivot = 0; 402 u_int32 ntp, expys, truys; 403 struct calendar date; 404 405 for (ntp = 0; ntp < 0xFFFFFFFFu - 30000000u; ntp += 30000000u) { 406 truys = calyearstart(ntp, &pivot); 407 ntpcal_ntp_to_date(&date, ntp, &pivot); 408 date.month = date.monthday = 1; 409 date.hour = date.minute = date.second = 0; 410 expys = ntpcal_date_to_ntp(&date); 411 TEST_ASSERT_EQUAL(expys, truys); 412 } 413 414 return; 415 } 416 417 /* Roundtrip testing on calmonthstart */ 418 void 419 test_RoundTripMonthStart(void) 420 { 421 static const time_t pivot = 0; 422 u_int32 ntp, expms, trums; 423 struct calendar date; 424 425 for (ntp = 0; ntp < 0xFFFFFFFFu - 2000000u; ntp += 2000000u) { 426 trums = calmonthstart(ntp, &pivot); 427 ntpcal_ntp_to_date(&date, ntp, &pivot); 428 date.monthday = 1; 429 date.hour = date.minute = date.second = 0; 430 expms = ntpcal_date_to_ntp(&date); 431 TEST_ASSERT_EQUAL(expms, trums); 432 } 433 434 return; 435 } 436 437 /* Roundtrip testing on calweekstart */ 438 void 439 test_RoundTripWeekStart(void) 440 { 441 static const time_t pivot = 0; 442 u_int32 ntp, expws, truws; 443 struct isodate date; 444 445 for (ntp = 0; ntp < 0xFFFFFFFFu - 600000u; ntp += 600000u) { 446 truws = calweekstart(ntp, &pivot); 447 isocal_ntp_to_date(&date, ntp, &pivot); 448 date.hour = date.minute = date.second = 0; 449 date.weekday = 1; 450 expws = isocal_date_to_ntp(&date); 451 TEST_ASSERT_EQUAL(expws, truws); 452 } 453 454 return; 455 } 456 457 /* Roundtrip testing on caldaystart */ 458 void 459 test_RoundTripDayStart(void) 460 { 461 static const time_t pivot = 0; 462 u_int32 ntp, expds, truds; 463 struct calendar date; 464 465 for (ntp = 0; ntp < 0xFFFFFFFFu - 80000u; ntp += 80000u) { 466 truds = caldaystart(ntp, &pivot); 467 ntpcal_ntp_to_date(&date, ntp, &pivot); 468 date.hour = date.minute = date.second = 0; 469 expds = ntpcal_date_to_ntp(&date); 470 TEST_ASSERT_EQUAL(expds, truds); 471 } 472 473 return; 474 } 475 476 /* --------------------------------------------------------------------- 477 * ISO8601 week calendar internals 478 * 479 * The ISO8601 week calendar implementation is simple in the terms of 480 * the math involved, but the implementation of the calculations must 481 * take care of a few things like overflow, floor division, and sign 482 * corrections. 483 * 484 * Most of the functions are straight forward, but converting from years 485 * to weeks and from weeks to years warrants some extra tests. These use 486 * an independent reference implementation of the conversion from years 487 * to weeks. 488 * --------------------------------------------------------------------- 489 */ 490 491 /* helper / reference implementation for the first week of year in the 492 * ISO8601 week calendar. This is based on the reference definition of 493 * the ISO week calendar start: The Monday closest to January,1st of the 494 * corresponding year in the Gregorian calendar. 495 */ 496 static int32_t 497 refimpl_WeeksInIsoYears( 498 int32_t years) 499 { 500 int32_t days, weeks; 501 502 days = ntpcal_weekday_close( 503 ntpcal_days_in_years(years) + 1, 504 CAL_MONDAY) - 1; 505 /* the weekday functions operate on RDN, while we want elapsed 506 * units here -- we have to add / sub 1 in the midlle / at the 507 * end of the operation that gets us the first day of the ISO 508 * week calendar day. 509 */ 510 weeks = days / 7; 511 days = days % 7; 512 TEST_ASSERT_EQUAL(0, days); /* paranoia check... */ 513 514 return weeks; 515 } 516 517 /* The next tests loop over 5000yrs, but should still be very fast. If 518 * they are not, the calendar needs a better implementation... 519 */ 520 void 521 test_IsoCalYearsToWeeks(void) 522 { 523 int32_t years; 524 int32_t wref, wcal; 525 526 for (years = -1000; years < 4000; ++years) { 527 /* get number of weeks before years (reference) */ 528 wref = refimpl_WeeksInIsoYears(years); 529 /* get number of weeks before years (object-under-test) */ 530 wcal = isocal_weeks_in_years(years); 531 TEST_ASSERT_EQUAL(wref, wcal); 532 } 533 534 return; 535 } 536 537 void 538 test_IsoCalWeeksToYearStart(void) 539 { 540 int32_t years; 541 int32_t wref; 542 ntpcal_split ysplit; 543 544 for (years = -1000; years < 4000; ++years) { 545 /* get number of weeks before years (reference) */ 546 wref = refimpl_WeeksInIsoYears(years); 547 /* reverse split */ 548 ysplit = isocal_split_eraweeks(wref); 549 /* check invariants: same year, week 0 */ 550 TEST_ASSERT_EQUAL(years, ysplit.hi); 551 TEST_ASSERT_EQUAL(0, ysplit.lo); 552 } 553 554 return; 555 } 556 557 void 558 test_IsoCalWeeksToYearEnd(void) 559 { 560 int32_t years; 561 int32_t wref; 562 ntpcal_split ysplit; 563 564 for (years = -1000; years < 4000; ++years) { 565 /* get last week of previous year */ 566 wref = refimpl_WeeksInIsoYears(years) - 1; 567 /* reverse split */ 568 ysplit = isocal_split_eraweeks(wref); 569 /* check invariants: previous year, week 51 or 52 */ 570 TEST_ASSERT_EQUAL(years-1, ysplit.hi); 571 TEST_ASSERT(ysplit.lo == 51 || ysplit.lo == 52); 572 } 573 574 return; 575 } 576 577 void 578 test_DaySecToDate(void) 579 { 580 struct calendar cal; 581 int32_t days; 582 583 days = ntpcal_daysec_to_date(&cal, -86400); 584 TEST_ASSERT_MESSAGE((days==-1 && cal.hour==0 && cal.minute==0 && cal.second==0), 585 "failed for -86400"); 586 587 days = ntpcal_daysec_to_date(&cal, -86399); 588 TEST_ASSERT_MESSAGE((days==-1 && cal.hour==0 && cal.minute==0 && cal.second==1), 589 "failed for -86399"); 590 591 days = ntpcal_daysec_to_date(&cal, -1); 592 TEST_ASSERT_MESSAGE((days==-1 && cal.hour==23 && cal.minute==59 && cal.second==59), 593 "failed for -1"); 594 595 days = ntpcal_daysec_to_date(&cal, 0); 596 TEST_ASSERT_MESSAGE((days==0 && cal.hour==0 && cal.minute==0 && cal.second==0), 597 "failed for 0"); 598 599 days = ntpcal_daysec_to_date(&cal, 1); 600 TEST_ASSERT_MESSAGE((days==0 && cal.hour==0 && cal.minute==0 && cal.second==1), 601 "failed for 1"); 602 603 days = ntpcal_daysec_to_date(&cal, 86399); 604 TEST_ASSERT_MESSAGE((days==0 && cal.hour==23 && cal.minute==59 && cal.second==59), 605 "failed for 86399"); 606 607 days = ntpcal_daysec_to_date(&cal, 86400); 608 TEST_ASSERT_MESSAGE((days==1 && cal.hour==0 && cal.minute==0 && cal.second==0), 609 "failed for 86400"); 610 611 return; 612 } 613