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