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