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