Home | History | Annotate | Line # | Download | only in libntp
      1 /*	$NetBSD: calendar.c,v 1.3 2024/08/18 20:47:26 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 
     14 static char mbuf[128];
     15 
     16 static int leapdays(int year);
     17 
     18 void	setUp(void);
     19 int	isGT(int first, int second);
     20 int	leapdays(int year);
     21 char *	CalendarFromCalToString(const struct calendar *cal);
     22 char *	CalendarFromIsoToString(const struct isodate *iso);
     23 int	IsEqualCal(const struct calendar *expected, const struct calendar *actual);
     24 int	IsEqualIso(const struct isodate *expected, const struct isodate *actual);
     25 char *	DateFromCalToString(const struct calendar *cal);
     26 char *	DateFromIsoToString(const struct isodate *iso);
     27 int	IsEqualDateCal(const struct calendar *expected, const struct calendar *actual);
     28 int	IsEqualDateIso(const struct isodate *expected, const struct isodate *actual);
     29 
     30 void	test_Constants(void);
     31 void	test_DaySplitMerge(void);
     32 void	test_WeekSplitMerge(void);
     33 void	test_SplitYearDays1(void);
     34 void	test_SplitYearDays2(void);
     35 void	test_SplitEraDays(void);
     36 void	test_SplitEraWeeks(void);
     37 void	test_RataDie1(void);
     38 void	test_LeapYears1(void);
     39 void	test_LeapYears2(void);
     40 void	test_LeapYears3(void);
     41 void	test_RoundTripDate(void);
     42 void	test_RoundTripYearStart(void);
     43 void	test_RoundTripMonthStart(void);
     44 void	test_RoundTripWeekStart(void);
     45 void	test_RoundTripDayStart(void);
     46 void	test_IsoCalYearsToWeeks(void);
     47 void	test_IsoCalWeeksToYearStart(void);
     48 void	test_IsoCalWeeksToYearEnd(void);
     49 void	test_DaySecToDate(void);
     50 void	test_GpsRollOver(void);
     51 void	test_GpsRemapFunny(void);
     52 
     53 void	test_GpsNtpFixpoints(void);
     54 void	test_NtpToNtp(void);
     55 void	test_NtpToTime(void);
     56 
     57 void	test_CalUMod7(void);
     58 void	test_CalIMod7(void);
     59 void	test_RellezCentury1_1(void);
     60 void	test_RellezCentury3_1(void);
     61 void	test_RellezYearZero(void);
     62 
     63 
     64 void
     65 setUp(void)
     66 {
     67 	init_lib();
     68 
     69 	return;
     70 }
     71 
     72 
     73 /*
     74  * ---------------------------------------------------------------------
     75  * test support stuff
     76  * ---------------------------------------------------------------------
     77  */
     78 int
     79 isGT(int first, int second)
     80 {
     81 	if(first > second) {
     82 		return TRUE;
     83 	} else {
     84 		return FALSE;
     85 	}
     86 }
     87 
     88 int
     89 leapdays(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 
    100 char *
    101 CalendarFromCalToString(
    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 
    113 char *
    114 CalendarFromIsoToString(
    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 
    125 int
    126 IsEqualCal(
    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 
    151 int
    152 IsEqualIso(
    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 
    171 char *
    172 DateFromCalToString(
    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 
    184 char *
    185 DateFromIsoToString(
    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 
    196 int/*BOOL*/
    197 IsEqualDateCal(
    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 
    214 int/*BOOL*/
    215 IsEqualDateIso(
    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 
    231 static int/*BOOL*/
    232 strToCal(
    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 */
    261 static 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 */
    269 static 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 
    276 void
    277 test_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  */
    299 void
    300 test_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 
    333 void
    334 test_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 
    367 void
    368 test_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 
    386 void
    387 test_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 
    407 void
    408 test_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 
    420 void
    421 test_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 
    433 void
    434 test_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 */
    447 void
    448 test_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 */
    466 void
    467 test_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 */
    484 void
    485 test_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  */
    504 void
    505 test_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 */
    537 void
    538 test_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 */
    557 void
    558 test_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 */
    577 void
    578 test_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 */
    597 void
    598 test_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  */
    635 static int32_t
    636 refimpl_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  */
    659 void
    660 test_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 
    676 void
    677 test_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 
    696 void
    697 test_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 
    716 void
    717 test_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 
    759 void
    760 test_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 
    817 void
    818 test_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  */
    880 void
    881 test_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 
    921 void
    922 test_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 
    961 void
    962 test_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 
    996 void
    997 test_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 
   1005 void
   1006 test_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 
   1019 void
   1020 test_RellezCentury1_1(void)
   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 
   1033 void
   1034 test_RellezCentury3_1(void)
   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 
   1047 void
   1048 test_RellezYearZero(void)
   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 
   1061 void test_RellezEra(void);
   1062 void 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  */
   1113 static int
   1114 zeller_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 
   1146 void test_zellerDirect(void);
   1147 void 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 
   1194 void test_ZellerDirectBad(void);
   1195 void 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 
   1209 void test_zellerModInv(void);
   1210 void 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