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