1/* $NetBSD: calendar.c,v 1.2 2020/05/25 20:47:36 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 "ntp_calgps.h" 8#include "ntp_unixtime.h" 9#include "ntp_fp.h" 10#include "unity.h" 11 12#include <string.h> 13 14static char mbuf[128]; 15 16static int leapdays(int year); 17 18void setUp(void); 19int isGT(int first, int second); 20int leapdays(int year); 21char * CalendarFromCalToString(const struct calendar *cal); 22char * CalendarFromIsoToString(const struct isodate *iso); 23int IsEqualCal(const struct calendar *expected, const struct calendar *actual); 24int IsEqualIso(const struct isodate *expected, const struct isodate *actual); 25char * DateFromCalToString(const struct calendar *cal); 26char * DateFromIsoToString(const struct isodate *iso); 27int IsEqualDateCal(const struct calendar *expected, const struct calendar *actual); 28int IsEqualDateIso(const struct isodate *expected, const struct isodate *actual); 29 30void test_Constants(void); 31void test_DaySplitMerge(void); 32void test_WeekSplitMerge(void); 33void test_SplitYearDays1(void); 34void test_SplitYearDays2(void); 35void test_SplitEraDays(void); 36void test_SplitEraWeeks(void); 37void test_RataDie1(void); 38void test_LeapYears1(void); 39void test_LeapYears2(void); 40void test_LeapYears3(void); 41void test_RoundTripDate(void); 42void test_RoundTripYearStart(void); 43void test_RoundTripMonthStart(void); 44void test_RoundTripWeekStart(void); 45void test_RoundTripDayStart(void); 46void test_IsoCalYearsToWeeks(void); 47void test_IsoCalWeeksToYearStart(void); 48void test_IsoCalWeeksToYearEnd(void); 49void test_DaySecToDate(void); 50void test_GpsRollOver(void); 51void test_GpsRemapFunny(void); 52 53void test_GpsNtpFixpoints(void); 54void test_NtpToNtp(void); 55void test_NtpToTime(void); 56 57void test_CalUMod7(void); 58void test_CalIMod7(void); 59void test_RellezCentury1_1(void); 60void test_RellezCentury3_1(void); 61void test_RellezYearZero(void); 62 63 64void 65setUp(void) 66{ 67 init_lib(); 68 69 return; 70} 71 72 73/* 74 * --------------------------------------------------------------------- 75 * test support stuff 76 * --------------------------------------------------------------------- 77 */ 78int 79isGT(int first, int second) 80{ 81 if(first > second) { 82 return TRUE; 83 } else { 84 return FALSE; 85 } 86} 87 88int 89leapdays(int year) 90{ 91 if (year % 400 == 0) 92 return 1; 93 if (year % 100 == 0) 94 return 0; 95 if (year % 4 == 0) 96 return 1; 97 return 0; 98} 99 100char * 101CalendarFromCalToString( 102 const struct calendar *cal) 103{ 104 char * str = malloc(sizeof (char) * 100); 105 snprintf(str, 100, "%u-%02u-%02u (%u) %02u:%02u:%02u", 106 cal->year, (u_int)cal->month, (u_int)cal->monthday, 107 cal->yearday, 108 (u_int)cal->hour, (u_int)cal->minute, (u_int)cal->second); 109 str[99] = '\0'; /* paranoia rulez! */ 110 return str; 111} 112 113char * 114CalendarFromIsoToString( 115 const struct isodate *iso) 116{ 117 char * str = emalloc (sizeof (char) * 100); 118 snprintf(str, 100, "%u-W%02u-%02u %02u:%02u:%02u", 119 iso->year, (u_int)iso->week, (u_int)iso->weekday, 120 (u_int)iso->hour, (u_int)iso->minute, (u_int)iso->second); 121 str[99] = '\0'; /* paranoia rulez! */ 122 return str; 123} 124 125int 126IsEqualCal( 127 const struct calendar *expected, 128 const struct calendar *actual) 129{ 130 if (expected->year == actual->year && 131 (!expected->yearday || expected->yearday == actual->yearday) && 132 expected->month == actual->month && 133 expected->monthday == actual->monthday && 134 expected->hour == actual->hour && 135 expected->minute == actual->minute && 136 expected->second == actual->second) { 137 return TRUE; 138 } else { 139 char *p_exp = CalendarFromCalToString(expected); 140 char *p_act = CalendarFromCalToString(actual); 141 142 printf("expected: %s but was %s", p_exp, p_act); 143 144 free(p_exp); 145 free(p_act); 146 147 return FALSE; 148 } 149} 150 151int 152IsEqualIso( 153 const struct isodate *expected, 154 const struct isodate *actual) 155{ 156 if (expected->year == actual->year && 157 expected->week == actual->week && 158 expected->weekday == actual->weekday && 159 expected->hour == actual->hour && 160 expected->minute == actual->minute && 161 expected->second == actual->second) { 162 return TRUE; 163 } else { 164 printf("expected: %s but was %s", 165 CalendarFromIsoToString(expected), 166 CalendarFromIsoToString(actual)); 167 return FALSE; 168 } 169} 170 171char * 172DateFromCalToString( 173 const struct calendar *cal) 174{ 175 176 char * str = emalloc (sizeof (char) * 100); 177 snprintf(str, 100, "%u-%02u-%02u (%u)", 178 cal->year, (u_int)cal->month, (u_int)cal->monthday, 179 cal->yearday); 180 str[99] = '\0'; /* paranoia rulez! */ 181 return str; 182} 183 184char * 185DateFromIsoToString( 186 const struct isodate *iso) 187{ 188 189 char * str = emalloc (sizeof (char) * 100); 190 snprintf(str, 100, "%u-W%02u-%02u", 191 iso->year, (u_int)iso->week, (u_int)iso->weekday); 192 str[99] = '\0'; /* paranoia rulez! */ 193 return str; 194} 195 196int/*BOOL*/ 197IsEqualDateCal( 198 const struct calendar *expected, 199 const struct calendar *actual) 200{ 201 if (expected->year == actual->year && 202 (!expected->yearday || expected->yearday == actual->yearday) && 203 expected->month == actual->month && 204 expected->monthday == actual->monthday) { 205 return TRUE; 206 } else { 207 printf("expected: %s but was %s", 208 DateFromCalToString(expected), 209 DateFromCalToString(actual)); 210 return FALSE; 211 } 212} 213 214int/*BOOL*/ 215IsEqualDateIso( 216 const struct isodate *expected, 217 const struct isodate *actual) 218{ 219 if (expected->year == actual->year && 220 expected->week == actual->week && 221 expected->weekday == actual->weekday) { 222 return TRUE; 223 } else { 224 printf("expected: %s but was %s", 225 DateFromIsoToString(expected), 226 DateFromIsoToString(actual)); 227 return FALSE; 228 } 229} 230 231static int/*BOOL*/ 232strToCal( 233 struct calendar * jd, 234 const char * str 235 ) 236{ 237 unsigned short y,m,d, H,M,S; 238 239 if (6 == sscanf(str, "%hu-%2hu-%2huT%2hu:%2hu:%2hu", 240 &y, &m, &d, &H, &M, &S)) { 241 memset(jd, 0, sizeof(*jd)); 242 jd->year = y; 243 jd->month = (uint8_t)m; 244 jd->monthday = (uint8_t)d; 245 jd->hour = (uint8_t)H; 246 jd->minute = (uint8_t)M; 247 jd->second = (uint8_t)S; 248 249 return TRUE; 250 } 251 return FALSE; 252} 253 254/* 255 * --------------------------------------------------------------------- 256 * test cases 257 * --------------------------------------------------------------------- 258 */ 259 260/* days before month, with a full-year pad at the upper end */ 261static const u_short real_month_table[2][13] = { 262 /* -*- table for regular years -*- */ 263 { 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, 264 /* -*- table for leap years -*- */ 265 { 0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366 } 266}; 267 268/* days in month, with one month wrap-around at both ends */ 269static const u_short real_month_days[2][14] = { 270 /* -*- table for regular years -*- */ 271 { 31, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31 }, 272 /* -*- table for leap years -*- */ 273 { 31, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 31 } 274}; 275 276void 277test_Constants(void) 278{ 279 int32_t rdn; 280 struct calendar jdn; 281 282 jdn.year = 1900; 283 jdn.month = 1; 284 jdn.monthday = 1; 285 rdn = ntpcal_date_to_rd(&jdn); 286 TEST_ASSERT_EQUAL_MESSAGE(DAY_NTP_STARTS, rdn, "(NTP EPOCH)"); 287 288 jdn.year = 1980; 289 jdn.month = 1; 290 jdn.monthday = 6; 291 rdn = ntpcal_date_to_rd(&jdn); 292 TEST_ASSERT_EQUAL_MESSAGE(DAY_GPS_STARTS, rdn, "(GPS EPOCH)"); 293} 294 295/* test the day/sec join & split ops, making sure that 32bit 296 * intermediate results would definitely overflow and the hi DWORD of 297 * the 'vint64' is definitely needed. 298 */ 299void 300test_DaySplitMerge(void) 301{ 302 int32 day,sec; 303 304 for (day = -1000000; day <= 1000000; day += 100) { 305 for (sec = -100000; sec <= 186400; sec += 10000) { 306 vint64 merge; 307 ntpcal_split split; 308 int32 eday; 309 int32 esec; 310 311 merge = ntpcal_dayjoin(day, sec); 312 split = ntpcal_daysplit(&merge); 313 eday = day; 314 esec = sec; 315 316 while (esec >= 86400) { 317 eday += 1; 318 esec -= 86400; 319 } 320 while (esec < 0) { 321 eday -= 1; 322 esec += 86400; 323 } 324 325 TEST_ASSERT_EQUAL(eday, split.hi); 326 TEST_ASSERT_EQUAL(esec, split.lo); 327 } 328 } 329 330 return; 331} 332 333void 334test_WeekSplitMerge(void) 335{ 336 int32 wno,sec; 337 338 for (wno = -1000000; wno <= 1000000; wno += 100) { 339 for (sec = -100000; sec <= 2*SECSPERWEEK; sec += 10000) { 340 vint64 merge; 341 ntpcal_split split; 342 int32 ewno; 343 int32 esec; 344 345 merge = ntpcal_weekjoin(wno, sec); 346 split = ntpcal_weeksplit(&merge); 347 ewno = wno; 348 esec = sec; 349 350 while (esec >= SECSPERWEEK) { 351 ewno += 1; 352 esec -= SECSPERWEEK; 353 } 354 while (esec < 0) { 355 ewno -= 1; 356 esec += SECSPERWEEK; 357 } 358 359 TEST_ASSERT_EQUAL(ewno, split.hi); 360 TEST_ASSERT_EQUAL(esec, split.lo); 361 } 362 } 363 364 return; 365} 366 367void 368test_SplitYearDays1(void) 369{ 370 int32 eyd; 371 372 for (eyd = -1; eyd <= 365; eyd++) { 373 ntpcal_split split = ntpcal_split_yeardays(eyd, 0); 374 if (split.lo >= 0 && split.hi >= 0) { 375 TEST_ASSERT_TRUE(isGT(12,split.hi)); 376 TEST_ASSERT_TRUE(isGT(real_month_days[0][split.hi+1], split.lo)); 377 int32 tyd = real_month_table[0][split.hi] + split.lo; 378 TEST_ASSERT_EQUAL(eyd, tyd); 379 } else 380 TEST_ASSERT_TRUE(eyd < 0 || eyd > 364); 381 } 382 383 return; 384} 385 386void 387test_SplitYearDays2(void) 388{ 389 int32 eyd; 390 391 for (eyd = -1; eyd <= 366; eyd++) { 392 ntpcal_split split = ntpcal_split_yeardays(eyd, 1); 393 if (split.lo >= 0 && split.hi >= 0) { 394 /* basic checks do not work on compunds :( */ 395 /* would like: TEST_ASSERT_TRUE(12 > split.hi); */ 396 TEST_ASSERT_TRUE(isGT(12,split.hi)); 397 TEST_ASSERT_TRUE(isGT(real_month_days[1][split.hi+1], split.lo)); 398 int32 tyd = real_month_table[1][split.hi] + split.lo; 399 TEST_ASSERT_EQUAL(eyd, tyd); 400 } else 401 TEST_ASSERT_TRUE(eyd < 0 || eyd > 365); 402 } 403 404 return; 405} 406 407void 408test_SplitEraDays(void) 409{ 410 int32_t ed, rd; 411 ntpcal_split sd; 412 for (ed = -10000; ed < 1000000; ++ed) { 413 sd = ntpcal_split_eradays(ed, NULL); 414 rd = ntpcal_days_in_years(sd.hi) + sd.lo; 415 TEST_ASSERT_EQUAL(ed, rd); 416 TEST_ASSERT_TRUE(0 <= sd.lo && sd.lo <= 365); 417 } 418} 419 420void 421test_SplitEraWeeks(void) 422{ 423 int32_t ew, rw; 424 ntpcal_split sw; 425 for (ew = -10000; ew < 1000000; ++ew) { 426 sw = isocal_split_eraweeks(ew); 427 rw = isocal_weeks_in_years(sw.hi) + sw.lo; 428 TEST_ASSERT_EQUAL(ew, rw); 429 TEST_ASSERT_TRUE(0 <= sw.lo && sw.lo <= 52); 430 } 431} 432 433void 434test_RataDie1(void) 435{ 436 int32 testDate = 1; /* 0001-01-01 (proleptic date) */ 437 struct calendar expected = { 1, 1, 1, 1 }; 438 struct calendar actual; 439 440 ntpcal_rd_to_date(&actual, testDate); 441 TEST_ASSERT_TRUE(IsEqualDateCal(&expected, &actual)); 442 443 return; 444} 445 446/* check last day of february for first 10000 years */ 447void 448test_LeapYears1(void) 449{ 450 struct calendar dateIn, dateOut; 451 452 for (dateIn.year = 1; dateIn.year < 10000; ++dateIn.year) { 453 dateIn.month = 2; 454 dateIn.monthday = 28 + leapdays(dateIn.year); 455 dateIn.yearday = 31 + dateIn.monthday; 456 457 ntpcal_rd_to_date(&dateOut, ntpcal_date_to_rd(&dateIn)); 458 459 TEST_ASSERT_TRUE(IsEqualDateCal(&dateIn, &dateOut)); 460 } 461 462 return; 463} 464 465/* check first day of march for first 10000 years */ 466void 467test_LeapYears2(void) 468{ 469 struct calendar dateIn, dateOut; 470 471 for (dateIn.year = 1; dateIn.year < 10000; ++dateIn.year) { 472 dateIn.month = 3; 473 dateIn.monthday = 1; 474 dateIn.yearday = 60 + leapdays(dateIn.year); 475 476 ntpcal_rd_to_date(&dateOut, ntpcal_date_to_rd(&dateIn)); 477 TEST_ASSERT_TRUE(IsEqualDateCal(&dateIn, &dateOut)); 478 } 479 480 return; 481} 482 483/* check the 'is_leapyear()' implementation for 4400 years */ 484void 485test_LeapYears3(void) 486{ 487 int32_t year; 488 int l1, l2; 489 490 for (year = -399; year < 4000; ++year) { 491 l1 = (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0)); 492 l2 = is_leapyear(year); 493 snprintf(mbuf, sizeof(mbuf), "y=%d", year); 494 TEST_ASSERT_EQUAL_MESSAGE(l1, l2, mbuf); 495 } 496} 497 498/* Full roundtrip from 1601-01-01 to 2400-12-31 499 * checks sequence of rata die numbers and validates date output 500 * (since the input is all nominal days of the calendar in that range 501 * and the result of the inverse calculation must match the input no 502 * invalid output can occur.) 503 */ 504void 505test_RoundTripDate(void) 506{ 507 struct calendar truDate, expDate = { 1600, 0, 12, 31 };; 508 int leaps; 509 int32 truRdn, expRdn = ntpcal_date_to_rd(&expDate); 510 511 while (expDate.year < 2400) { 512 expDate.year++; 513 expDate.month = 0; 514 expDate.yearday = 0; 515 leaps = leapdays(expDate.year); 516 while (expDate.month < 12) { 517 expDate.month++; 518 expDate.monthday = 0; 519 while (expDate.monthday < real_month_days[leaps][expDate.month]) { 520 expDate.monthday++; 521 expDate.yearday++; 522 expRdn++; 523 524 truRdn = ntpcal_date_to_rd(&expDate); 525 TEST_ASSERT_EQUAL(expRdn, truRdn); 526 527 ntpcal_rd_to_date(&truDate, truRdn); 528 TEST_ASSERT_TRUE(IsEqualDateCal(&expDate, &truDate)); 529 } 530 } 531 } 532 533 return; 534} 535 536/* Roundtrip testing on calyearstart */ 537void 538test_RoundTripYearStart(void) 539{ 540 static const time_t pivot = 0; 541 u_int32 ntp, expys, truys; 542 struct calendar date; 543 544 for (ntp = 0; ntp < 0xFFFFFFFFu - 30000000u; ntp += 30000000u) { 545 truys = calyearstart(ntp, &pivot); 546 ntpcal_ntp_to_date(&date, ntp, &pivot); 547 date.month = date.monthday = 1; 548 date.hour = date.minute = date.second = 0; 549 expys = ntpcal_date_to_ntp(&date); 550 TEST_ASSERT_EQUAL(expys, truys); 551 } 552 553 return; 554} 555 556/* Roundtrip testing on calmonthstart */ 557void 558test_RoundTripMonthStart(void) 559{ 560 static const time_t pivot = 0; 561 u_int32 ntp, expms, trums; 562 struct calendar date; 563 564 for (ntp = 0; ntp < 0xFFFFFFFFu - 2000000u; ntp += 2000000u) { 565 trums = calmonthstart(ntp, &pivot); 566 ntpcal_ntp_to_date(&date, ntp, &pivot); 567 date.monthday = 1; 568 date.hour = date.minute = date.second = 0; 569 expms = ntpcal_date_to_ntp(&date); 570 TEST_ASSERT_EQUAL(expms, trums); 571 } 572 573 return; 574} 575 576/* Roundtrip testing on calweekstart */ 577void 578test_RoundTripWeekStart(void) 579{ 580 static const time_t pivot = 0; 581 u_int32 ntp, expws, truws; 582 struct isodate date; 583 584 for (ntp = 0; ntp < 0xFFFFFFFFu - 600000u; ntp += 600000u) { 585 truws = calweekstart(ntp, &pivot); 586 isocal_ntp_to_date(&date, ntp, &pivot); 587 date.hour = date.minute = date.second = 0; 588 date.weekday = 1; 589 expws = isocal_date_to_ntp(&date); 590 TEST_ASSERT_EQUAL(expws, truws); 591 } 592 593 return; 594} 595 596/* Roundtrip testing on caldaystart */ 597void 598test_RoundTripDayStart(void) 599{ 600 static const time_t pivot = 0; 601 u_int32 ntp, expds, truds; 602 struct calendar date; 603 604 for (ntp = 0; ntp < 0xFFFFFFFFu - 80000u; ntp += 80000u) { 605 truds = caldaystart(ntp, &pivot); 606 ntpcal_ntp_to_date(&date, ntp, &pivot); 607 date.hour = date.minute = date.second = 0; 608 expds = ntpcal_date_to_ntp(&date); 609 TEST_ASSERT_EQUAL(expds, truds); 610 } 611 612 return; 613} 614 615/* --------------------------------------------------------------------- 616 * ISO8601 week calendar internals 617 * 618 * The ISO8601 week calendar implementation is simple in the terms of 619 * the math involved, but the implementation of the calculations must 620 * take care of a few things like overflow, floor division, and sign 621 * corrections. 622 * 623 * Most of the functions are straight forward, but converting from years 624 * to weeks and from weeks to years warrants some extra tests. These use 625 * an independent reference implementation of the conversion from years 626 * to weeks. 627 * --------------------------------------------------------------------- 628 */ 629 630/* helper / reference implementation for the first week of year in the 631 * ISO8601 week calendar. This is based on the reference definition of 632 * the ISO week calendar start: The Monday closest to January,1st of the 633 * corresponding year in the Gregorian calendar. 634 */ 635static int32_t 636refimpl_WeeksInIsoYears( 637 int32_t years) 638{ 639 int32_t days, weeks; 640 641 days = ntpcal_weekday_close( 642 ntpcal_days_in_years(years) + 1, 643 CAL_MONDAY) - 1; 644 /* the weekday functions operate on RDN, while we want elapsed 645 * units here -- we have to add / sub 1 in the midlle / at the 646 * end of the operation that gets us the first day of the ISO 647 * week calendar day. 648 */ 649 weeks = days / 7; 650 days = days % 7; 651 TEST_ASSERT_EQUAL(0, days); /* paranoia check... */ 652 653 return weeks; 654} 655 656/* The next tests loop over 5000yrs, but should still be very fast. If 657 * they are not, the calendar needs a better implementation... 658 */ 659void 660test_IsoCalYearsToWeeks(void) 661{ 662 int32_t years; 663 int32_t wref, wcal; 664 665 for (years = -1000; years < 4000; ++years) { 666 /* get number of weeks before years (reference) */ 667 wref = refimpl_WeeksInIsoYears(years); 668 /* get number of weeks before years (object-under-test) */ 669 wcal = isocal_weeks_in_years(years); 670 TEST_ASSERT_EQUAL(wref, wcal); 671 } 672 673 return; 674} 675 676void 677test_IsoCalWeeksToYearStart(void) 678{ 679 int32_t years; 680 int32_t wref; 681 ntpcal_split ysplit; 682 683 for (years = -1000; years < 4000; ++years) { 684 /* get number of weeks before years (reference) */ 685 wref = refimpl_WeeksInIsoYears(years); 686 /* reverse split */ 687 ysplit = isocal_split_eraweeks(wref); 688 /* check invariants: same year, week 0 */ 689 TEST_ASSERT_EQUAL(years, ysplit.hi); 690 TEST_ASSERT_EQUAL(0, ysplit.lo); 691 } 692 693 return; 694} 695 696void 697test_IsoCalWeeksToYearEnd(void) 698{ 699 int32_t years; 700 int32_t wref; 701 ntpcal_split ysplit; 702 703 for (years = -1000; years < 4000; ++years) { 704 /* get last week of previous year */ 705 wref = refimpl_WeeksInIsoYears(years) - 1; 706 /* reverse split */ 707 ysplit = isocal_split_eraweeks(wref); 708 /* check invariants: previous year, week 51 or 52 */ 709 TEST_ASSERT_EQUAL(years-1, ysplit.hi); 710 TEST_ASSERT(ysplit.lo == 51 || ysplit.lo == 52); 711 } 712 713 return; 714} 715 716void 717test_DaySecToDate(void) 718{ 719 struct calendar cal; 720 int32_t days; 721 722 days = ntpcal_daysec_to_date(&cal, -86400); 723 TEST_ASSERT_MESSAGE((days==-1 && cal.hour==0 && cal.minute==0 && cal.second==0), 724 "failed for -86400"); 725 726 days = ntpcal_daysec_to_date(&cal, -86399); 727 TEST_ASSERT_MESSAGE((days==-1 && cal.hour==0 && cal.minute==0 && cal.second==1), 728 "failed for -86399"); 729 730 days = ntpcal_daysec_to_date(&cal, -1); 731 TEST_ASSERT_MESSAGE((days==-1 && cal.hour==23 && cal.minute==59 && cal.second==59), 732 "failed for -1"); 733 734 days = ntpcal_daysec_to_date(&cal, 0); 735 TEST_ASSERT_MESSAGE((days==0 && cal.hour==0 && cal.minute==0 && cal.second==0), 736 "failed for 0"); 737 738 days = ntpcal_daysec_to_date(&cal, 1); 739 TEST_ASSERT_MESSAGE((days==0 && cal.hour==0 && cal.minute==0 && cal.second==1), 740 "failed for 1"); 741 742 days = ntpcal_daysec_to_date(&cal, 86399); 743 TEST_ASSERT_MESSAGE((days==0 && cal.hour==23 && cal.minute==59 && cal.second==59), 744 "failed for 86399"); 745 746 days = ntpcal_daysec_to_date(&cal, 86400); 747 TEST_ASSERT_MESSAGE((days==1 && cal.hour==0 && cal.minute==0 && cal.second==0), 748 "failed for 86400"); 749 750 return; 751} 752 753/* -------------------------------------------------------------------- 754 * unfolding of (truncated) NTP time stamps to full 64bit values. 755 * 756 * Note: These tests need a 64bit time_t to be useful. 757 */ 758 759void 760test_NtpToNtp(void) 761{ 762# if SIZEOF_TIME_T <= 4 763 764 TEST_IGNORE_MESSAGE("test only useful for sizeof(time_t) > 4, skipped"); 765 766# else 767 768 static const uint32_t ntp_vals[6] = { 769 UINT32_C(0x00000000), 770 UINT32_C(0x00000001), 771 UINT32_C(0x7FFFFFFF), 772 UINT32_C(0x80000000), 773 UINT32_C(0x80000001), 774 UINT32_C(0xFFFFFFFF) 775 }; 776 777 static char lbuf[128]; 778 vint64 hold; 779 time_t pivot, texp, diff; 780 int loops, iloop; 781 782 pivot = 0; 783 for (loops = 0; loops < 16; ++loops) { 784 for (iloop = 0; iloop < 6; ++iloop) { 785 hold = ntpcal_ntp_to_ntp( 786 ntp_vals[iloop], &pivot); 787 texp = vint64_to_time(&hold); 788 789 /* constraint 1: texp must be in the 790 * (right-open) intervall [p-(2^31), p+(2^31)[, 791 * but the pivot 'p' must be taken in full NTP 792 * time scale! 793 */ 794 diff = texp - (pivot + JAN_1970); 795 snprintf(lbuf, sizeof(lbuf), 796 "bounds check: piv=%lld exp=%lld dif=%lld", 797 (long long)pivot, 798 (long long)texp, 799 (long long)diff); 800 TEST_ASSERT_MESSAGE((diff >= INT32_MIN) && (diff <= INT32_MAX), 801 lbuf); 802 803 /* constraint 2: low word must be equal to 804 * input 805 */ 806 snprintf(lbuf, sizeof(lbuf), 807 "low check: ntp(in)=$%08lu ntp(out[0:31])=$%08lu", 808 (unsigned long)ntp_vals[iloop], 809 (unsigned long)hold.D_s.lo); 810 TEST_ASSERT_EQUAL_MESSAGE(ntp_vals[iloop], hold.D_s.lo, lbuf); 811 } 812 pivot += 0x20000000; 813 } 814# endif 815} 816 817void 818test_NtpToTime(void) 819{ 820# if SIZEOF_TIME_T <= 4 821 822 TEST_IGNORE_MESSAGE("test only useful for sizeof(time_t) > 4, skipped"); 823 824# else 825 826 static const uint32_t ntp_vals[6] = { 827 UINT32_C(0x00000000), 828 UINT32_C(0x00000001), 829 UINT32_C(0x7FFFFFFF), 830 UINT32_C(0x80000000), 831 UINT32_C(0x80000001), 832 UINT32_C(0xFFFFFFFF) 833 }; 834 835 static char lbuf[128]; 836 vint64 hold; 837 time_t pivot, texp, diff; 838 uint32_t back; 839 int loops, iloop; 840 841 pivot = 0; 842 for (loops = 0; loops < 16; ++loops) { 843 for (iloop = 0; iloop < 6; ++iloop) { 844 hold = ntpcal_ntp_to_time( 845 ntp_vals[iloop], &pivot); 846 texp = vint64_to_time(&hold); 847 848 /* constraint 1: texp must be in the 849 * (right-open) intervall [p-(2^31), p+(2^31)[ 850 */ 851 diff = texp - pivot; 852 snprintf(lbuf, sizeof(lbuf), 853 "bounds check: piv=%lld exp=%lld dif=%lld", 854 (long long)pivot, 855 (long long)texp, 856 (long long)diff); 857 TEST_ASSERT_MESSAGE((diff >= INT32_MIN) && (diff <= INT32_MAX), 858 lbuf); 859 860 /* constraint 2: conversion from full time back 861 * to truncated NTP time must yield same result 862 * as input. 863 */ 864 back = (uint32_t)texp + JAN_1970; 865 snprintf(lbuf, sizeof(lbuf), 866 "modulo check: ntp(in)=$%08lu ntp(out)=$%08lu", 867 (unsigned long)ntp_vals[iloop], 868 (unsigned long)back); 869 TEST_ASSERT_EQUAL_MESSAGE(ntp_vals[iloop], back, lbuf); 870 } 871 pivot += 0x20000000; 872 } 873# endif 874} 875 876/* -------------------------------------------------------------------- 877 * GPS rollover 878 * -------------------------------------------------------------------- 879 */ 880void 881test_GpsRollOver(void) 882{ 883 /* we test on wednesday, noon, and on the border */ 884 static const int32_t wsec1 = 3*SECSPERDAY + SECSPERDAY/2; 885 static const int32_t wsec2 = 7 * SECSPERDAY - 1; 886 static const int32_t week0 = GPSNTP_WSHIFT + 2047; 887 static const int32_t week1 = GPSNTP_WSHIFT + 2048; 888 TCivilDate jd; 889 TGpsDatum gps; 890 l_fp fpz; 891 892 ZERO(fpz); 893 894 /* test on 2nd rollover, April 2019 895 * we set the base date properly one week *before the rollover, to 896 * check if the expansion merrily hops over the warp. 897 */ 898 basedate_set_day(2047 * 7 + NTP_TO_GPS_DAYS); 899 900 strToCal(&jd, "19-04-03T12:00:00"); 901 gps = gpscal_from_calendar(&jd, fpz); 902 TEST_ASSERT_EQUAL_MESSAGE(week0, gps.weeks, "(week test 1))"); 903 TEST_ASSERT_EQUAL_MESSAGE(wsec1, gps.wsecs, "(secs test 1)"); 904 905 strToCal(&jd, "19-04-06T23:59:59"); 906 gps = gpscal_from_calendar(&jd, fpz); 907 TEST_ASSERT_EQUAL_MESSAGE(week0, gps.weeks, "(week test 2)"); 908 TEST_ASSERT_EQUAL_MESSAGE(wsec2, gps.wsecs, "(secs test 2)"); 909 910 strToCal(&jd, "19-04-07T00:00:00"); 911 gps = gpscal_from_calendar(&jd, fpz); 912 TEST_ASSERT_EQUAL_MESSAGE(week1, gps.weeks, "(week test 3)"); 913 TEST_ASSERT_EQUAL_MESSAGE( 0 , gps.wsecs, "(secs test 3)"); 914 915 strToCal(&jd, "19-04-10T12:00:00"); 916 gps = gpscal_from_calendar(&jd, fpz); 917 TEST_ASSERT_EQUAL_MESSAGE(week1, gps.weeks, "(week test 4)"); 918 TEST_ASSERT_EQUAL_MESSAGE(wsec1, gps.wsecs, "(secs test 4)"); 919} 920 921void 922test_GpsRemapFunny(void) 923{ 924 TCivilDate di, dc, de; 925 TGpsDatum gd; 926 927 l_fp fpz; 928 929 ZERO(fpz); 930 basedate_set_day(2048 * 7 + NTP_TO_GPS_DAYS); 931 932 /* expand 2digit year to 2080, then fold back into 3rd GPS era: */ 933 strToCal(&di, "80-01-01T00:00:00"); 934 strToCal(&de, "2021-02-15T00:00:00"); 935 gd = gpscal_from_calendar(&di, fpz); 936 gpscal_to_calendar(&dc, &gd); 937 TEST_ASSERT_TRUE(IsEqualCal(&de, &dc)); 938 939 /* expand 2digit year to 2080, then fold back into 3rd GPS era: */ 940 strToCal(&di, "80-01-05T00:00:00"); 941 strToCal(&de, "2021-02-19T00:00:00"); 942 gd = gpscal_from_calendar(&di, fpz); 943 gpscal_to_calendar(&dc, &gd); 944 TEST_ASSERT_TRUE(IsEqualCal(&de, &dc)); 945 946 /* remap days before epoch into 3rd era: */ 947 strToCal(&di, "1980-01-05T00:00:00"); 948 strToCal(&de, "2038-11-20T00:00:00"); 949 gd = gpscal_from_calendar(&di, fpz); 950 gpscal_to_calendar(&dc, &gd); 951 TEST_ASSERT_TRUE(IsEqualCal(&de, &dc)); 952 953 /* remap GPS epoch: */ 954 strToCal(&di, "1980-01-06T00:00:00"); 955 strToCal(&de, "2019-04-07T00:00:00"); 956 gd = gpscal_from_calendar(&di, fpz); 957 gpscal_to_calendar(&dc, &gd); 958 TEST_ASSERT_TRUE(IsEqualCal(&de, &dc)); 959} 960 961void 962test_GpsNtpFixpoints(void) 963{ 964 basedate_set_day(NTP_TO_GPS_DAYS); 965 TGpsDatum e1gps; 966 TNtpDatum e1ntp, r1ntp; 967 l_fp lfpe , lfpr; 968 969 lfpe.l_ui = 0; 970 lfpe.l_uf = UINT32_C(0x80000000); 971 972 ZERO(e1gps); 973 e1gps.weeks = 0; 974 e1gps.wsecs = SECSPERDAY; 975 e1gps.frac = UINT32_C(0x80000000); 976 977 ZERO(e1ntp); 978 e1ntp.frac = UINT32_C(0x80000000); 979 980 r1ntp = gpsntp_from_gpscal(&e1gps); 981 TEST_ASSERT_EQUAL_MESSAGE(e1ntp.days, r1ntp.days, "gps -> ntp / days"); 982 TEST_ASSERT_EQUAL_MESSAGE(e1ntp.secs, r1ntp.secs, "gps -> ntp / secs"); 983 TEST_ASSERT_EQUAL_MESSAGE(e1ntp.frac, r1ntp.frac, "gps -> ntp / frac"); 984 985 lfpr = ntpfp_from_gpsdatum(&e1gps); 986 snprintf(mbuf, sizeof(mbuf), "gps -> l_fp: %s <=> %s", 987 lfptoa(&lfpe, 9), lfptoa(&lfpr, 9)); 988 TEST_ASSERT_TRUE_MESSAGE(L_ISEQU(&lfpe, &lfpr), mbuf); 989 990 lfpr = ntpfp_from_ntpdatum(&e1ntp); 991 snprintf(mbuf, sizeof(mbuf), "ntp -> l_fp: %s <=> %s", 992 lfptoa(&lfpe, 9), lfptoa(&lfpr, 9)); 993 TEST_ASSERT_TRUE_MESSAGE(L_ISEQU(&lfpe, &lfpr), mbuf); 994} 995 996void 997test_CalUMod7(void) 998{ 999 TEST_ASSERT_EQUAL(0, u32mod7(0)); 1000 TEST_ASSERT_EQUAL(1, u32mod7(INT32_MAX)); 1001 TEST_ASSERT_EQUAL(2, u32mod7(UINT32_C(1)+INT32_MAX)); 1002 TEST_ASSERT_EQUAL(3, u32mod7(UINT32_MAX)); 1003} 1004 1005void 1006test_CalIMod7(void) 1007{ 1008 TEST_ASSERT_EQUAL(5, i32mod7(INT32_MIN)); 1009 TEST_ASSERT_EQUAL(6, i32mod7(-1)); 1010 TEST_ASSERT_EQUAL(0, i32mod7(0)); 1011 TEST_ASSERT_EQUAL(1, i32mod7(INT32_MAX)); 1012} 1013 1014/* Century expansion tests. Reverse application of Zeller's congruence, 1015 * sort of... hence the name "Rellez", Zeller backwards. Just in case 1016 * you didn't notice ;) 1017 */ 1018 1019void 1020test_RellezCentury1_1() 1021{ 1022 /* 1st day of a century */ 1023 TEST_ASSERT_EQUAL(1901, ntpcal_expand_century( 1, 1, 1, CAL_TUESDAY )); 1024 TEST_ASSERT_EQUAL(2001, ntpcal_expand_century( 1, 1, 1, CAL_MONDAY )); 1025 TEST_ASSERT_EQUAL(2101, ntpcal_expand_century( 1, 1, 1, CAL_SATURDAY )); 1026 TEST_ASSERT_EQUAL(2201, ntpcal_expand_century( 1, 1, 1, CAL_THURSDAY )); 1027 /* bad/impossible cases: */ 1028 TEST_ASSERT_EQUAL( 0, ntpcal_expand_century( 1, 1, 1, CAL_WEDNESDAY)); 1029 TEST_ASSERT_EQUAL( 0, ntpcal_expand_century( 1, 1, 1, CAL_FRIDAY )); 1030 TEST_ASSERT_EQUAL( 0, ntpcal_expand_century( 1, 1, 1, CAL_SUNDAY )); 1031} 1032 1033void 1034test_RellezCentury3_1() 1035{ 1036 /* 1st day in March of a century (the tricky point) */ 1037 TEST_ASSERT_EQUAL(1901, ntpcal_expand_century( 1, 3, 1, CAL_FRIDAY )); 1038 TEST_ASSERT_EQUAL(2001, ntpcal_expand_century( 1, 3, 1, CAL_THURSDAY )); 1039 TEST_ASSERT_EQUAL(2101, ntpcal_expand_century( 1, 3, 1, CAL_TUESDAY )); 1040 TEST_ASSERT_EQUAL(2201, ntpcal_expand_century( 1, 3, 1, CAL_SUNDAY )); 1041 /* bad/impossible cases: */ 1042 TEST_ASSERT_EQUAL( 0, ntpcal_expand_century( 1, 3, 1, CAL_MONDAY )); 1043 TEST_ASSERT_EQUAL( 0, ntpcal_expand_century( 1, 3, 1, CAL_WEDNESDAY)); 1044 TEST_ASSERT_EQUAL( 0, ntpcal_expand_century( 1, 3, 1, CAL_SATURDAY )); 1045} 1046 1047void 1048test_RellezYearZero() 1049{ 1050 /* the infamous year zero */ 1051 TEST_ASSERT_EQUAL(1900, ntpcal_expand_century( 0, 1, 1, CAL_MONDAY )); 1052 TEST_ASSERT_EQUAL(2000, ntpcal_expand_century( 0, 1, 1, CAL_SATURDAY )); 1053 TEST_ASSERT_EQUAL(2100, ntpcal_expand_century( 0, 1, 1, CAL_FRIDAY )); 1054 TEST_ASSERT_EQUAL(2200, ntpcal_expand_century( 0, 1, 1, CAL_WEDNESDAY)); 1055 /* bad/impossible cases: */ 1056 TEST_ASSERT_EQUAL( 0, ntpcal_expand_century( 0, 1, 1, CAL_TUESDAY )); 1057 TEST_ASSERT_EQUAL( 0, ntpcal_expand_century( 0, 1, 1, CAL_THURSDAY )); 1058 TEST_ASSERT_EQUAL( 0, ntpcal_expand_century( 0, 1, 1, CAL_SUNDAY )); 1059} 1060 1061void test_RellezEra(void); 1062void test_RellezEra(void) 1063{ 1064 static const unsigned int mt[13] = { 0, 31,28,31,30,31,30,31,31,30,31,30,31 }; 1065 unsigned int yi, yo, m, d, wd; 1066 1067 /* last day before our era -- fold forward */ 1068 yi = 1899; 1069 m = 12; 1070 d = 31; 1071 wd = ntpcal_edate_to_eradays(yi-1, m-1, d-1) % 7 + 1; 1072 yo = ntpcal_expand_century((yi%100), m, d, wd); 1073 snprintf(mbuf, sizeof(mbuf), "failed, di=%04u-%02u-%02u, wd=%u", 1074 yi, m, d, wd); 1075 TEST_ASSERT_EQUAL_MESSAGE(2299, yo, mbuf); 1076 1077 /* 1st day after our era -- fold back */ 1078 yi = 2300; 1079 m = 1; 1080 d = 1; 1081 wd = ntpcal_edate_to_eradays(yi-1, m-1, d-1) % 7 + 1; 1082 yo = ntpcal_expand_century((yi%100), m, d, wd); 1083 snprintf(mbuf, sizeof(mbuf), "failed, di=%04u-%02u-%02u, wd=%u", 1084 yi, m, d, wd); 1085 TEST_ASSERT_EQUAL_MESSAGE(1900, yo, mbuf); 1086 1087 /* test every month in our 400y era */ 1088 for (yi = 1900; yi < 2300; ++yi) { 1089 for (m = 1; m < 12; ++m) { 1090 /* test first day of month */ 1091 d = 1; 1092 wd = ntpcal_edate_to_eradays(yi-1, m-1, d-1) % 7 + 1; 1093 yo = ntpcal_expand_century((yi%100), m, d, wd); 1094 snprintf(mbuf, sizeof(mbuf), "failed, di=%04u-%02u-%02u, wd=%u", 1095 yi, m, d, wd); 1096 TEST_ASSERT_EQUAL_MESSAGE(yi, yo, mbuf); 1097 1098 /* test last day of month */ 1099 d = mt[m] + (m == 2 && is_leapyear(yi)); 1100 wd = ntpcal_edate_to_eradays(yi-1, m-1, d-1) % 7 + 1; 1101 yo = ntpcal_expand_century((yi%100), m, d, wd); 1102 snprintf(mbuf, sizeof(mbuf), "failed, di=%04u-%02u-%02u, wd=%u", 1103 yi, m, d, wd); 1104 TEST_ASSERT_EQUAL_MESSAGE(yi, yo, mbuf); 1105 } 1106 } 1107} 1108 1109/* This is nearly a verbatim copy of the in-situ implementation of 1110 * Zeller's congruence in libparse/clk_rawdcf.c, so the algorithm 1111 * can be tested. 1112 */ 1113static int 1114zeller_expand( 1115 unsigned int y, 1116 unsigned int m, 1117 unsigned int d, 1118 unsigned int wd 1119 ) 1120{ 1121 unsigned int c; 1122 1123 if ((y >= 100u) || (--m >= 12u) || (--d >= 31u) || (--wd >= 7u)) 1124 return 0; 1125 1126 if ((m += 10u) >= 12u) 1127 m -= 12u; 1128 else if (--y >= 100u) 1129 y += 100u; 1130 d += y + (y >> 2) + 2u; 1131 d += (m * 83u + 16u) >> 5; 1132 1133 c = (((252u + wd - d) * 0x6db6db6eU) >> 29) & 7u; 1134 if (c > 3u) 1135 return 0; 1136 1137 if ((m > 9u) && (++y >= 100u)) { 1138 y -= 100u; 1139 c = (c + 1) & 3u; 1140 } 1141 y += (c * 100u); 1142 y += (y < 370u) ? 2000 : 1600; 1143 return (int)y; 1144} 1145 1146void test_zellerDirect(void); 1147void test_zellerDirect(void) 1148{ 1149 static const unsigned int mt[13] = { 0, 31,28,31,30,31,30,31,31,30,31,30,31 }; 1150 unsigned int yi, yo, m, d, wd; 1151 1152 /* last day before our era -- fold forward */ 1153 yi = 1969; 1154 m = 12; 1155 d = 31; 1156 wd = ntpcal_edate_to_eradays(yi-1, m-1, d-1) % 7 + 1; 1157 yo = zeller_expand((yi%100), m, d, wd); 1158 snprintf(mbuf, sizeof(mbuf), "failed, di=%04u-%02u-%02u, wd=%u", 1159 yi, m, d, wd); 1160 TEST_ASSERT_EQUAL_MESSAGE(2369, yo, mbuf); 1161 1162 /* 1st day after our era -- fold back */ 1163 yi = 2370; 1164 m = 1; 1165 d = 1; 1166 wd = ntpcal_edate_to_eradays(yi-1, m-1, d-1) % 7 + 1; 1167 yo = zeller_expand((yi%100), m, d, wd); 1168 snprintf(mbuf, sizeof(mbuf), "failed, di=%04u-%02u-%02u, wd=%u", 1169 yi, m, d, wd); 1170 TEST_ASSERT_EQUAL_MESSAGE(1970, yo, mbuf); 1171 1172 /* test every month in our 400y era */ 1173 for (yi = 1970; yi < 2370; ++yi) { 1174 for (m = 1; m < 12; ++m) { 1175 /* test first day of month */ 1176 d = 1; 1177 wd = ntpcal_edate_to_eradays(yi-1, m-1, d-1) % 7 + 1; 1178 yo = zeller_expand((yi%100), m, d, wd); 1179 snprintf(mbuf, sizeof(mbuf), "failed, di=%04u-%02u-%02u, wd=%u", 1180 yi, m, d, wd); 1181 TEST_ASSERT_EQUAL_MESSAGE(yi, yo, mbuf); 1182 1183 /* test last day of month */ 1184 d = mt[m] + (m == 2 && is_leapyear(yi)); 1185 wd = ntpcal_edate_to_eradays(yi-1, m-1, d-1) % 7 + 1; 1186 yo = zeller_expand((yi%100), m, d, wd); 1187 snprintf(mbuf, sizeof(mbuf), "failed, di=%04u-%02u-%02u, wd=%u", 1188 yi, m, d, wd); 1189 TEST_ASSERT_EQUAL_MESSAGE(yi, yo, mbuf); 1190 } 1191 } 1192} 1193 1194void test_ZellerDirectBad(void); 1195void test_ZellerDirectBad(void) 1196{ 1197 unsigned int y, n, wd; 1198 for (y = 2001; y < 2101; ++y) { 1199 wd = ntpcal_edate_to_eradays(y-1, 0, 0) % 7 + 1; 1200 /* move 4 centuries ahead */ 1201 wd = (wd + 5) % 7 + 1; 1202 for (n = 0; n < 3; ++n) { 1203 TEST_ASSERT_EQUAL(0, zeller_expand((y%100), 1, 1, wd)); 1204 wd = (wd + 4) % 7 + 1; 1205 } 1206 } 1207} 1208 1209void test_zellerModInv(void); 1210void test_zellerModInv(void) 1211{ 1212 unsigned int i, r1, r2; 1213 1214 for (i = 0; i < 2048; ++i) { 1215 r1 = (3 * i) % 7; 1216 r2 = ((i * 0x6db6db6eU) >> 29) & 7u; 1217 snprintf(mbuf, sizeof(mbuf), "i=%u", i); 1218 TEST_ASSERT_EQUAL_MESSAGE(r1, r2, mbuf); 1219 } 1220} 1221 1222 1223