*
* Written by Juergen Perlinger (perlinger@ntp.org) for the NTP project.
* The contents of 'html/copyright.html' apply.
+ *
+ * --------------------------------------------------------------------
+ * Some notes on the implementation:
+ *
+ * Calendar algorithms thrive on the division operation, which is one of
+ * the slowest numerical operations in any CPU. What saves us here from
+ * abysmal performance is the fact that all divisions are divisions by
+ * constant numbers, and most compilers can do this by a multiplication
+ * operation. But this might not work when using the div/ldiv/lldiv
+ * function family, because many compilers are not able to do inline
+ * expansion of the code with following optimisation for the
+ * constant-divider case.
+ *
+ * Also div/ldiv/lldiv are defined in terms of int/long/longlong, which
+ * are inherently target dependent. Nothing that could not be cured with
+ * autoconf, but still a mess...
+ *
+ * Furthermore, we need floor division in many places. C either leaves
+ * the division behaviour undefined (< C99) or demands truncation to
+ * zero (>= C99), so additional steps are required to make sure the
+ * algorithms work. The {l,ll}div function family is requested to
+ * truncate towards zero, which is also the wrong direction for our
+ * purpose.
+ *
+ * For all this, all divisions by constant are coded manually, even when
+ * there is a joined div/mod operation: The optimiser should sort that
+ * out, if possible. Most of the calculations are done with unsigned
+ * types, explicitely using two's complement arithmetics where
+ * necessary. This minimises the dependecies to compiler and target,
+ * while still giving reasonable to good performance.
+ *
+ * The implementation uses a few tricks that exploit properties of the
+ * two's complement: Floor division on negative dividents can be
+ * executed by using the one's complement of the divident. One's
+ * complement can be easily created using XOR and a mask.
+ *
+ * Finally, check for overflow conditions is minimal. There are only two
+ * calculation steps in the whole calendar that suffer from an internal
+ * overflow, and these conditions are checked: errno is set to EDOM and
+ * the results are clamped/saturated in this case. All other functions
+ * do not suffer from internal overflow and simply return the result
+ * truncated to 32 bits.
+ *
+ * This is a sacrifice made for execution speed. Since a 32-bit day
+ * counter covers +/- 5,879,610 years and the clamp limits the effective
+ * range to +/-2.9 million years, this should not pose a problem here.
+ *
*/
+
#include <config.h>
#include <sys/types.h>
#include "ntp_fp.h"
#include "ntp_unixtime.h"
+/* For now, let's take the conservative approach: if the target property
+ * macros are not defined, check a few well-known compiler/architecture
+ * settings. Default is to assume that the representation of signed
+ * integers is unknown and shift-arithmetic-right is not available.
+ */
+#ifndef TARGET_HAS_2CPL
+# if defined(__GNUC__)
+# if defined(__i386__) || defined(__x86_64__) || defined(__arm__)
+# define TARGET_HAS_2CPL 1
+# else
+# define TARGET_HAS_2CPL 0
+# endif
+# elif defined(_MSC_VER)
+# if defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM)
+# define TARGET_HAS_2CPL 1
+# else
+# define TARGET_HAS_2CPL 0
+# endif
+# endif
+#endif
+
+#ifndef TARGET_HAS_SAR
+# define TARGET_HAS_SAR 0
+#endif
+
/*
*---------------------------------------------------------------------
* replacing the 'time()' function
return (*systime_func)(NULL);
}
+/*
+ *---------------------------------------------------------------------
+ * Get sign extension mask and unsigned 2cpl rep for a signed integer
+ *---------------------------------------------------------------------
+ */
+
+static inline uint32_t
+int32_sflag(
+ const int32_t v)
+{
+# if TARGET_HAS_2CPL && TARGET_HAS_SAR && SIZEOF_INT >= 4
+
+ /* Let's assume that shift is the fastest way to get the sign
+ * extension of of a signed integer. This might not always be
+ * true, though -- On 8bit CPUs or machines without barrel
+ * shifter this will kill the performance. So we make sure
+ * we do this only if 'int' has at least 4 bytes.
+ */
+ return (uint32_t)(v >> 31);
+
+# else
+
+ /* This should be a rather generic approach for getting a sign
+ * extension mask...
+ */
+ return UINT32_C(0) - (uint32_t)(v < 0);
+
+# endif
+}
+
+static inline uint32_t
+int32_to_uint32_2cpl(
+ const int32_t v)
+{
+ uint32_t vu;
+
+# if TARGET_HAS_2CPL
+
+ /* Just copy through the 32 bits from the signed value if we're
+ * on a two's complement target.
+ */
+ vu = (uint32_t)v;
+
+# else
+
+ /* Convert from signed int to unsigned int two's complement. Do
+ * not make any assumptions about the representation of signed
+ * integers, but make sure signed integer overflow cannot happen
+ * here. A compiler on a two's complement target *might* find
+ * out that this is just a complicated cast (as above), but your
+ * mileage might vary.
+ */
+ if (v < 0)
+ vu = ~(uint32_t)(-(v + 1));
+ else
+ vu = (uint32_t)v;
+
+# endif
+
+ return vu;
+}
+
+static inline int32_t
+uint32_2cpl_to_int32(
+ const uint32_t vu)
+{
+ int32_t v;
+
+# if TARGET_HAS_2CPL
+
+ /* Just copy through the 32 bits from the unsigned value if
+ * we're on a two's complement target.
+ */
+ v = (int32_t)vu;
+
+# else
+
+ /* Convert to signed integer, making sure signed integer
+ * overflow cannot happen. Again, the optimiser might or might
+ * not find out that this is just a copy of 32 bits on a target
+ * with two's complement representation for signed integers.
+ */
+ if (vu > INT32_MAX)
+ v = -(int32_t)(~vu) - 1;
+ else
+ v = (int32_t)vu;
+
+# endif
+
+ return v;
+}
+
+/* Some of the calculations need to multiply the input by 4 before doing
+ * a division. This can cause overflow and strange results. Therefore we
+ * clamp / saturate the input operand. And since we do the calculations
+ * in unsigned int with an extra sign flag/mask, we only loose one bit
+ * of the input value range.
+ */
+static inline uint32_t
+uint32_saturate(
+ uint32_t vu,
+ uint32_t mu)
+{
+ static const uint32_t limit = UINT32_MAX/4u;
+ if ((mu ^ vu) > limit) {
+ vu = mu ^ limit;
+ errno = EDOM;
+ }
+ return vu;
+}
+
/*
*---------------------------------------------------------------------
* Convert between 'time_t' and 'vint64'
tt = *ptt;
-#if SIZEOF_TIME_T <= 4
+# if SIZEOF_TIME_T <= 4
res.D_s.hi = 0;
if (tt < 0) {
res.D_s.lo = (uint32_t)tt;
}
-#elif defined(HAVE_INT64)
+# elif defined(HAVE_INT64)
res.q_s = tt;
-#else
+# else
/*
* shifting negative signed quantities is compiler-dependent, so
* we better avoid it and do it all manually. And shifting more
res.D_s.hi = (uint32_t)(tt >> 32);
}
-#endif
+# endif
return res;
}
{
time_t res;
-#if SIZEOF_TIME_T <= 4
+# if SIZEOF_TIME_T <= 4
res = (time_t)tv->D_s.lo;
-#elif defined(HAVE_INT64)
+# elif defined(HAVE_INT64)
res = (time_t)tv->q_s;
-#else
+# else
res = ((time_t)tv->d_s.hi << 32) | tv->D_s.lo;
-#endif
+# endif
return res;
}
* problem.
*
*/
-#ifdef MKREPRO_DATE
+# ifdef MKREPRO_DATE
static const char build[] = MKREPRO_TIME "/" MKREPRO_DATE;
-#else
+# else
static const char build[] = __TIME__ "/" __DATE__;
-#endif
+# endif
static const char mlist[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
char monstr[4];
* so using 'uint16_t' is contra-indicated!
*/
-#ifdef DEBUG
+# ifdef DEBUG
static int ignore = 0;
-#endif
+# endif
ZERO(*jd);
jd->year = 1970;
jd->month = 1;
jd->monthday = 1;
-#ifdef DEBUG
+# ifdef DEBUG
/* check environment if build date should be ignored */
if (0 == ignore) {
const char * envstr;
}
if (ignore > 1)
return FALSE;
-#endif
+# endif
if (6 == sscanf(build, "%hu:%hu:%hu/%3s %hu %hu",
&hour, &minute, &second, monstr, &day, &year)) {
* (day number). This is the number of days elapsed since 0000-12-31
* in the proleptic Gregorian calendar. The begin of the Christian Era
* (0001-01-01) is RD(1).
- *
- *
- * Some notes on the implementation:
- *
- * Calendar algorithms thrive on the division operation, which is one of
- * the slowest numerical operations in any CPU. What saves us here from
- * abysmal performance is the fact that all divisions are divisions by
- * constant numbers, and most compilers can do this by a multiplication
- * operation. But this might not work when using the div/ldiv/lldiv
- * function family, because many compilers are not able to do inline
- * expansion of the code with following optimisation for the
- * constant-divider case.
- *
- * Also div/ldiv/lldiv are defined in terms of int/long/longlong, which
- * are inherently target dependent. Nothing that could not be cured with
- * autoconf, but still a mess...
- *
- * Furthermore, we need floor division while C demands truncation to
- * zero, so additional steps are required to make sure the algorithms
- * work.
- *
- * For all this, all divisions by constant are coded manually, even when
- * there is a joined div/mod operation: The optimiser should sort that
- * out, if possible.
- *
- * Finally, the functions do not check for overflow conditions. This
- * is a sacrifice made for execution speed; since a 32-bit day counter
- * covers +/- 5,879,610 years, this should not pose a problem here.
*/
-
/*
* ==================================================================
*
* Get absolute difference as unsigned quantity and
* the complement flag. This is done by always
* subtracting the smaller value from the bigger
- * one. This implementation works only on a two's
- * complement machine!
+ * one.
*/
if (value >= pivot) {
- diff = (uint32_t)value - (uint32_t)pivot;
+ diff = int32_to_uint32_2cpl(value)
+ - int32_to_uint32_2cpl(pivot);
} else {
- diff = (uint32_t)pivot - (uint32_t)value;
+ diff = int32_to_uint32_2cpl(pivot)
+ - int32_to_uint32_2cpl(value);
cpl ^= 1;
}
diff %= (uint32_t)cycle;
if (diff) {
if (cpl)
- diff = cycle - diff;
+ diff = (uint32_t)cycle - diff;
if (neg)
diff = ~diff + 1;
- pivot += diff;
+ pivot += uint32_2cpl_to_int32(diff);
}
}
return pivot;
{
vint64 res;
-#ifdef HAVE_INT64
+# if defined(HAVE_INT64)
res.q_s = (pivot != NULL)
? *pivot
ntp -= res.D_s.lo; /* cycle difference */
res.Q_s += (uint64_t)ntp; /* get expanded time */
-#else /* no 64bit scalars */
+# else /* no 64bit scalars */
time_t tmp;
ntp -= res.D_s.lo; /* cycle difference */
M_ADD(res.D_s.hi, res.D_s.lo, 0, ntp);
-#endif /* no 64bit scalars */
+# endif /* no 64bit scalars */
return res;
}
{
vint64 res;
-#ifdef HAVE_INT64
+# if defined(HAVE_INT64)
res.q_s = (pivot)
? *pivot
ntp -= res.D_s.lo; /* cycle difference */
res.Q_s += (uint64_t)ntp; /* get expanded time */
-#else /* no 64bit scalars */
+# else /* no 64bit scalars */
time_t tmp;
ntp -= res.D_s.lo; /* cycle difference */
M_ADD(res.D_s.hi, res.D_s.lo, 0, ntp);
-#endif /* no 64bit scalars */
+# endif /* no 64bit scalars */
return res;
}
)
{
ntpcal_split res;
+ uint32_t Q;
+
+# if defined(HAVE_INT64)
+
+ /* Manual floor division by SECSPERDAY. This uses the one's
+ * complement trick, too, but without an extra flag value: The
+ * flag would be 64bit, and that's a bit of overkill on a 32bit
+ * target that has to use a register pair for a 64bit number.
+ */
+ if (ts->q_s < 0)
+ Q = ~(uint32_t)(~ts->Q_s / SECSPERDAY);
+ else
+ Q = (uint32_t)(ts->Q_s / SECSPERDAY);
-#ifdef HAVE_INT64
+# else
- /* manual floor division by SECSPERDAY */
- res.hi = (int32_t)(ts->q_s / SECSPERDAY);
- res.lo = (int32_t)(ts->q_s % SECSPERDAY);
- if (res.lo < 0) {
- res.hi -= 1;
- res.lo += SECSPERDAY;
- }
+ uint32_t ah, al, sflag, A;
-#else
+ /* get operand into ah/al (either ts or ts' one's complement,
+ * for later floor division)
+ */
+ sflag = int32_sflag(ts->d_s.hi);
+ ah = sflag ^ ts->D_s.hi;
+ al = sflag ^ ts->D_s.lo;
+
+ /* Since 86400 == 128*675 we can drop the least 7 bits and
+ * divide by 675 instead of 86400. Then the maximum remainder
+ * after each devision step is 674, and we need 10 bits for
+ * that. So in the next step we can shift in 22 bits from the
+ * numerator.
+ *
+ * Therefore we load the accu with the top 13 bits (51..63) in
+ * the first shot. We don't have to remember the quotient -- it
+ * would be shifted out anyway.
+ */
+ A = ah >> 19;
+ if (A >= 675)
+ A = (A % 675u);
+
+ /* Now assemble the remainder with bits 29..50 from the
+ * numerator and divide. This creates the upper ten bits of the
+ * quotient. (Well, the top 22 bits of a 44bit result. But that
+ * will be truncated to 32 bits anyway.)
+ */
+ A = (A << 19) | (ah & 0x0007FFFFu);
+ A = (A << 3) | (al >> 29);
+ Q = A / 675u;
+ A = A % 675u;
- /*
- * since we do not have 64bit ops, we have to this by hand.
- * Luckily SECSPERDAY is 86400 is 675*128, so we do the division
- * using chained 32/16 bit divisions and shifts.
+ /* Now assemble the remainder with bits 7..28 from the numerator
+ * and do a final division step.
*/
- vint64 op;
- uint32_t q, r, a;
- int isneg;
+ A = (A << 22) | ((al >> 7) & 0x003FFFFFu);
+ Q = (Q << 22) | (A / 675u);
- memcpy(&op, ts, sizeof(op));
- /* fix sign */
- isneg = M_ISNEG(op.D_s.hi);
- if (isneg)
- M_NEG(op.D_s.hi, op.D_s.lo);
-
- /* save remainder of DIV 128, shift for divide */
- r = op.D_s.lo & 127; /* save remainder bits */
- op.D_s.lo = (op.D_s.lo >> 7) | (op.D_s.hi << 25);
- op.D_s.hi = (op.D_s.hi >> 7);
-
- /* now do a mnual division, trying to remove as many ops as
- * possible -- division is always slow! An since we do not have
- * the advantage of a specific 64/32 bit or even a specific 32/16
- * bit division op, but must use the general 32/32bit division
- * even if we *know* the divider fits into unsigned 16 bits, the
- * exra code pathes should pay off.
+ /* The last 7 bits get simply dropped, as they have no affect on
+ * the quotient when dividing by 86400.
*/
- a = op.D_s.hi;
- if (a > 675u)
- a = a % 675u;
- if (a) {
- a = (a << 16) | op.W_s.lh;
- q = a / 675u;
- a = a % 675u;
-
- a = (a << 16) | op.W_s.ll;
- q = (q << 16) | (a / 675u);
- } else {
- a = op.D_s.lo;
- q = a / 675u;
- }
- a = a % 675u;
-
- /* assemble remainder */
- r |= a << 7;
-
- /* fix sign of result */
- if (isneg) {
- if (r) {
- r = SECSPERDAY - r;
- q = ~q;
- } else
- q = ~q + 1;
- }
- res.hi = q;
- res.lo = r;
+ /* apply sign correction and calculate the true floor
+ * remainder.
+ */
+ Q ^= sflag;
+
+# endif
+
+ res.hi = uint32_2cpl_to_int32(Q);
+ res.lo = ts->D_s.lo - Q * SECSPERDAY;
-#endif
return res;
}
int32_t ts
)
{
- int32_t days = 0;
-
- /* make sure we have a positive offset into a day */
- if (ts < 0 || ts >= SECSPERDAY) {
- days = ts / SECSPERDAY;
- ts = ts % SECSPERDAY;
- if (ts < 0) {
- days -= 1;
- ts += SECSPERDAY;
- }
- }
+ /* Do 3 chained floor divisions by positive constants, using the
+ * one's complement trick and factoring out the intermediate XOR
+ * ops to reduce the number of operations.
+ */
+ uint32_t us, um, uh, ud, sflag;
- /* get secs, mins, hours */
- split[2] = (uint8_t)(ts % SECSPERMIN);
- ts /= SECSPERMIN;
- split[1] = (uint8_t)(ts % MINSPERHR);
- split[0] = (uint8_t)(ts / MINSPERHR);
+ sflag = int32_sflag(ts);
+ us = int32_to_uint32_2cpl(ts);
- return days;
+ um = (sflag ^ us) / SECSPERMIN;
+ uh = um / MINSPERHR;
+ ud = uh / HRSPERDAY;
+
+ um ^= sflag;
+ uh ^= sflag;
+ ud ^= sflag;
+
+ split[0] = (int32_t)(uh - ud * HRSPERDAY );
+ split[1] = (int32_t)(um - uh * MINSPERHR );
+ split[2] = (int32_t)(us - um * SECSPERMIN);
+
+ return uint32_2cpl_to_int32(ud);
}
/*
int *isleapyear
)
{
- ntpcal_split res;
- int32_t n400, n100, n004, n001, yday; /* calendar year cycles */
-
- /*
- * Split off calendar cycles, using floor division in the first
- * step. After that first step, simple division does it because
- * all operands are positive; alas, we have to be aware of the
- * possibe cycle overflows for 100 years and 1 year, caused by
- * the additional leap day.
+ /* Use the fast cyclesplit algorithm here, to calculate the
+ * centuries and years in a century with one division each. This
+ * reduces the number of division operations to two, but is
+ * susceptible to internal range overflow. We make sure the
+ * input operands are in the safe range; this still gives us
+ * approx +/-2.9 million years.
*/
- n400 = days / GREGORIAN_CYCLE_DAYS;
- yday = days % GREGORIAN_CYCLE_DAYS;
- if (yday < 0) {
- n400 -= 1;
- yday += GREGORIAN_CYCLE_DAYS;
- }
- n100 = yday / GREGORIAN_NORMAL_CENTURY_DAYS;
- yday = yday % GREGORIAN_NORMAL_CENTURY_DAYS;
- n004 = yday / GREGORIAN_NORMAL_LEAP_CYCLE_DAYS;
- yday = yday % GREGORIAN_NORMAL_LEAP_CYCLE_DAYS;
- n001 = yday / DAYSPERYEAR;
- yday = yday % DAYSPERYEAR;
-
- /*
- * check for leap cycle overflows and calculate the leap flag
- * if needed
+ ntpcal_split res;
+ int32_t n100, n001; /* calendar year cycles */
+ uint32_t uday, Q, sflag;
+
+ /* split off centuries first */
+ sflag = int32_sflag(days);
+ uday = uint32_saturate(int32_to_uint32_2cpl(days), sflag);
+ uday = (4u * uday) | 3u;
+ Q = sflag ^ ((sflag ^ uday) / GREGORIAN_CYCLE_DAYS);
+ uday = uday - Q * GREGORIAN_CYCLE_DAYS;
+ n100 = uint32_2cpl_to_int32(Q);
+
+ /* Split off years in century -- days >= 0 here, and we're far
+ * away from integer overflow trouble now. */
+ uday |= 3;
+ n001 = uday / GREGORIAN_NORMAL_LEAP_CYCLE_DAYS;
+ uday = uday % GREGORIAN_NORMAL_LEAP_CYCLE_DAYS;
+
+ /* Assemble the year and day in year */
+ res.hi = n100 * 100 + n001;
+ res.lo = uday / 4u;
+
+ /* Eventually set the leap year flag. Note: 0 <= n001 <= 99 and
+ * Q is still the two's complement representation of the
+ * centuries: The modulo 4 ops can be done with masking here.
+ * We also shift the year and the century by one, so the tests
+ * can be done against zero instead of 3.
*/
- if ((n001 | n100) > 3) {
- /* hit last day of leap year */
- n001 -= 1;
- yday += DAYSPERYEAR;
- if (isleapyear)
- *isleapyear = 1;
- } else if (isleapyear)
- *isleapyear = (n001 == 3) && ((n004 != 24) || (n100 == 3));
-
- /* now merge the cycles to elapsed years, using horner scheme */
- res.hi = ((4*n400 + n100)*25 + n004)*4 + n001;
- res.lo = yday;
-
+ if (isleapyear)
+ *isleapyear = !((n001+1) & 3)
+ && ((n001 != 99) || !((Q+1) & 3));
+
return res;
}
)
{
ntpcal_split split;
- int leaps;
- int retv;
+ int leapy;
+ uint ymask;
- leaps = 0;
- retv = 0;
/* Get day-of-week first. Since rd is signed, the remainder can
* be in the range [-6..+6], but the assignment to an unsigned
* variable maps the negative values to positive values >=7.
* causes the needed wrap-around into the desired value range of
* zero to six, both inclusive.
*/
- jd->weekday = rd % 7;
- if (jd->weekday >= 7) /* unsigned! */
- jd->weekday += 7;
-
- split = ntpcal_split_eradays(rd - 1, &leaps);
- retv = leaps;
- /* get year and day-of-year */
- jd->year = (uint16_t)split.hi + 1;
- if (jd->year != split.hi + 1) {
- jd->year = 0;
- retv = -1; /* bletch. overflow trouble. */
- }
+ jd->weekday = rd % DAYSPERWEEK;
+ if (jd->weekday >= DAYSPERWEEK) /* weekday is unsigned! */
+ jd->weekday += DAYSPERWEEK;
+
+ split = ntpcal_split_eradays(rd - 1, &leapy);
+ /* Get year and day-of-year, with overflow check. If any of the
+ * upper 16 bits is set after shifting to unity-based years, we
+ * will have an overflow when converting to an unsigned 16bit
+ * year. Shifting to the right is OK here, since it does not
+ * matter if the shift is logic or arithmetic.
+ */
+ split.hi += 1;
+ ymask = 0u - ((split.hi >> 16) == 0);
+ jd->year = (uint16_t)(split.hi & ymask);
jd->yearday = (uint16_t)split.lo + 1;
/* convert to month and mday */
- split = ntpcal_split_yeardays(split.lo, leaps);
+ split = ntpcal_split_yeardays(split.lo, leapy);
jd->month = (uint8_t)split.hi + 1;
jd->monthday = (uint8_t)split.lo + 1;
- return retv ? retv : leaps;
+ return ymask ? leapy : -1;
}
/*
)
{
ntpcal_split split;
- int leaps;
+ int leapy;
- leaps = 0;
/* get day-of-week first */
- utm->tm_wday = rd % 7;
+ utm->tm_wday = rd % DAYSPERWEEK;
if (utm->tm_wday < 0)
- utm->tm_wday += 7;
+ utm->tm_wday += DAYSPERWEEK;
/* get year and day-of-year */
- split = ntpcal_split_eradays(rd - 1, &leaps);
+ split = ntpcal_split_eradays(rd - 1, &leapy);
utm->tm_year = split.hi - 1899;
utm->tm_yday = split.lo; /* 0-based */
/* convert to month and mday */
- split = ntpcal_split_yeardays(split.lo, leaps);
+ split = ntpcal_split_yeardays(split.lo, leapy);
utm->tm_mon = split.hi; /* 0-based */
utm->tm_mday = split.lo + 1; /* 1-based */
- return leaps;
+ return leapy;
}
/*
{
vint64 res;
-#ifdef HAVE_INT64
+# if defined(HAVE_INT64)
res.q_s = days;
res.q_s *= SECSPERDAY;
res.q_s += secs;
-#else
+# else
uint32_t p1, p2;
int isneg;
}
M_ADD(res.D_s.hi, res.D_s.lo, p2, p1);
-#endif
+# endif
return res;
}
/*
*---------------------------------------------------------------------
- * Convert elapsed years in Era into elapsed days in Era.
- *
- * To accomodate for negative values of years, floor division would be
- * required for all division operations. This can be eased by first
- * splitting the years into full 400-year cycles and years in the
- * cycle. Only this operation must be coded as a full floor division; as
- * the years in the cycle is a non-negative number, all other divisions
- * can be regular truncated divisions.
+ * get leap years since epoch in elapsed years
*---------------------------------------------------------------------
*/
int32_t
-ntpcal_days_in_years(
+ntpcal_leapyears_in_years(
int32_t years
)
{
- int32_t cycle; /* full gregorian cycle */
-
- /* split off full calendar cycles, using floor division */
- cycle = years / 400;
- years = years % 400;
- if (years < 0) {
- cycle -= 1;
- years += 400;
- }
+ /* We use the in-out-in algorithm here, using the one's
+ * complement division trick for negative numbers. The chained
+ * division sequence by 4/25/4 gives the compiler the chance to
+ * get away with only one true division and doing shifts otherwise.
+ */
- /*
- * Calculate days in cycle. years now is a non-negative number,
- * holding the number of years in the 400-year cycle.
+ uint32_t sflag, sum, uyear;
+
+ sflag = int32_sflag(years);
+ uyear = int32_to_uint32_2cpl(years);
+ uyear ^= sflag;
+
+ sum = (uyear /= 4u); /* 4yr rule --> IN */
+ sum -= (uyear /= 25u); /* 100yr rule --> OUT */
+ sum += (uyear /= 4u); /* 400yr rule --> IN */
+
+ /* Thanks to the alternation of IN/OUT/IN we can do the sum
+ * directly and have a single one's complement operation
+ * here. (Only if the years are negative, of course.) Otherwise
+ * the one's complement would have to be done when
+ * adding/subtracting the terms.
*/
- return cycle * GREGORIAN_CYCLE_DAYS
- + years * DAYSPERYEAR /* days inregular years */
- + years / 4 /* 4 year leap rule */
- - years / 100; /* 100 year leap rule */
- /* the 400-year rule does not apply due to full-cycle split-off */
+ return uint32_2cpl_to_int32(sflag ^ sum);
+}
+
+/*
+ *---------------------------------------------------------------------
+ * Convert elapsed years in Era into elapsed days in Era.
+ *---------------------------------------------------------------------
+ */
+int32_t
+ntpcal_days_in_years(
+ int32_t years
+ )
+{
+ return years * DAYSPERYEAR + ntpcal_leapyears_in_years(years);
}
/*
{
ntpcal_split res;
- /* normalize month into range */
- res.hi = 0;
- res.lo = m;
- if (res.lo < 0 || res.lo >= 12) {
- res.hi = res.lo / 12;
- res.lo = res.lo % 12;
- if (res.lo < 0) {
- res.hi -= 1;
- res.lo += 12;
- }
- }
+ /* Add ten months and correct if needed. (It likely is...) */
+ res.lo = m + 10;
+ res.hi = (res.lo >= 12);
+ if (res.hi)
+ res.lo -= 12;
- /* add 10 month for year starting with march */
- if (res.lo < 2)
- res.lo += 10;
- else {
- res.hi += 1;
- res.lo -= 2;
+ /* if still out of range, normalise by floor division ... */
+ if (res.lo < 0 || res.lo >= 12) {
+ uint32_t mu, Q, sflag;
+ sflag = int32_sflag(res.lo);
+ mu = int32_to_uint32_2cpl(res.lo);
+ Q = sflag ^ ((sflag ^ mu) / 12u);
+ res.hi += uint32_2cpl_to_int32(Q);
+ res.lo = mu - Q * 12u;
}
-
+
/* get cummulated days in year with unshift */
res.lo = shift_month_table[res.lo] - 306;
isocal_weeks_in_years(
int32_t years
)
-{
+{
/*
* use: w = (y * 53431 + b[c]) / 1024 as interpolation
*/
- static const int32_t bctab[4] = { 449, 157, 889, 597 };
- int32_t cycle; /* full gregorian cycle */
- int32_t cents; /* full centuries */
- int32_t weeks; /* accumulated weeks */
-
- /* split off full calendar cycles, using floor division */
- cycle = years / 400;
- years = years % 400;
- if (years < 0) {
- cycle -= 1;
- years += 400;
- }
-
- /* split off full centuries */
- cents = years / 100;
- years = years % 100;
-
- /*
- * calculate elapsed weeks, taking into account that the
- * first, third and fourth century have 5218 weeks but the
- * second century falls short by one week.
+ static const uint16_t bctab[4] = { 157, 449, 597, 889 };
+
+ int32_t cs, cw;
+ uint32_t cc, ci, yu, sflag;
+
+ sflag = int32_sflag(years);
+ yu = int32_to_uint32_2cpl(years);
+
+ /* split off centuries, using floor division */
+ cc = sflag ^ ((sflag ^ yu) / 100u);
+ yu -= cc * 100u;
+
+ /* calculate century cycles shift and cycle index:
+ * Assuming a century is 5217 weeks, we have to add a cycle
+ * shift that is 3 for every 4 centuries, because 3 of the four
+ * centuries have 5218 weeks. So '(cc*3 + 1) / 4' is the actual
+ * correction, and the second century is the defective one.
+ *
+ * Needs floor division by 4, which is done with masking and
+ * shifting.
+ */
+ ci = cc * 3u + 1;
+ cs = uint32_2cpl_to_int32(sflag ^ ((sflag ^ ci) / 4u));
+ ci = ci % 4u;
+
+ /* Get weeks in century. Can use plain division here as all ops
+ * are >= 0, and let the compiler sort out the possible
+ * optimisations.
*/
- weeks = (years * 53431 + bctab[cents]) / 1024;
+ cw = (yu * 53431u + bctab[ci]) / 1024u;
- return cycle * GREGORIAN_CYCLE_WEEKS
- + cents * 5218 - (cents > 1)
- + weeks;
+ return uint32_2cpl_to_int32(cc) * 5217 + cs + cw;
}
/*
/*
* use: y = (w * 157 + b[c]) / 8192 as interpolation
*/
- static const int32_t bctab[4] = { 85, 131, 17, 62 };
+
+ static const uint16_t bctab[4] = { 85, 130, 17, 62 };
+
ntpcal_split res;
- int32_t cents;
+ int32_t cc, ci;
+ uint32_t sw, cy, Q, sflag;
- /*
- * split off 400-year cycles, using the fact that a 400-year
- * cycle has 146097 days, which is exactly 20871 weeks.
+ /* Use two fast cycle-split divisions here. This is again
+ * susceptible to internal overflow, so we check the range. This
+ * still permits more than +/-20 million years, so this is
+ * likely a pure academical problem.
+ *
+ * We want to execute '(weeks * 4 + 2) /% 20871' under floor
+ * division rules in the first step.
*/
- res.hi = weeks / GREGORIAN_CYCLE_WEEKS;
- res.lo = weeks % GREGORIAN_CYCLE_WEEKS;
- if (res.lo < 0) {
- res.hi -= 1;
- res.lo += GREGORIAN_CYCLE_WEEKS;
- }
- res.hi *= 400;
-
- /*
- * split off centuries, taking into account that the first,
- * third and fourth century have 5218 weeks but that the
- * second century falls short by one week.
+ sflag = int32_sflag(weeks);
+ sw = uint32_saturate(int32_to_uint32_2cpl(weeks), sflag);
+ sw = 4u * sw + 2;
+ Q = sflag ^ ((sflag ^ sw) / GREGORIAN_CYCLE_WEEKS);
+ sw -= Q * GREGORIAN_CYCLE_WEEKS;
+ ci = Q % 4u;
+ cc = uint32_2cpl_to_int32(Q);
+
+ /* Split off years; sw >= 0 here! The scaled weeks in the years
+ * are scaled up by 157 afterwards.
+ */
+ sw = (sw / 4u) * 157u + bctab[ci];
+ cy = sw / 8192u; /* ws >> 13 , let the compiler sort it out */
+ sw = sw % 8192u; /* ws & 8191, let the compiler sort it out */
+
+ /* assemble elapsed years and downscale the elapsed weeks in
+ * the year.
*/
- res.lo += (res.lo >= 10435);
- cents = res.lo / 5218;
- res.lo %= 5218; /* res.lo is weeks in century now */
-
- /* convert elapsed weeks in century to elapsed years and weeks */
- res.lo = res.lo * 157 + bctab[cents];
- res.hi += cents * 100 + res.lo / 8192;
- res.lo = (res.lo % 8192) / 157;
+ res.hi = 100*cc + cy;
+ res.lo = sw / 157u;
return res;
}
{
ntpcal_split ds;
int32_t ts[3];
+ uint32_t uw, ud, sflag;
/*
* Split NTP time into days and seconds, shift days into CE
id->minute = (uint8_t)ts[1];
id->second = (uint8_t)ts[2];
- /* split date part */
- ds.lo = ds.hi + DAY_NTP_STARTS - 1; /* elapsed era days */
- ds.hi = ds.lo / 7; /* elapsed era weeks */
- ds.lo = ds.lo % 7; /* elapsed week days */
- if (ds.lo < 0) { /* floor division! */
- ds.hi -= 1;
- ds.lo += 7;
- }
+ /* split days into days and weeks, using floor division in unsigned */
+ ds.hi += DAY_NTP_STARTS - 1; /* shift from NTP to RDN */
+ sflag = int32_sflag(ds.hi);
+ ud = int32_to_uint32_2cpl(ds.hi);
+ uw = sflag ^ ((sflag ^ ud) / DAYSPERWEEK);
+ ud -= uw * DAYSPERWEEK;
+ ds.hi = uint32_2cpl_to_int32(uw);
+ ds.lo = ud;
+
id->weekday = (uint8_t)ds.lo + 1; /* weekday result */
+ /* get year and week in year */
ds = isocal_split_eraweeks(ds.hi); /* elapsed years&week*/
id->year = (uint16_t)ds.hi + 1; /* shift to current */
id->week = (uint8_t )ds.lo + 1;
static int leapdays(int year);
-char * CalendarFromCalToString(const struct calendar cal);
-char * CalendarFromIsoToString(const struct isodate iso);
-
-// technically, booleans
-int IsEqualCal(const struct calendar expected, const struct calendar actual);
-int IsEqualIso(const struct isodate expected, const struct isodate actual);
-
-char * DateFromCalToStringCal(const struct calendar cal);
-char * DateFromIsoToStringIso(const struct isodate iso);
-
-// technically, booleans
-int sEqualDateCal(const struct calendar expected, const struct calendar actual);
-int IsEqualDateIso(const struct isodate expected, const struct isodate actual);
-
-
int isGT(int first, int second);
int leapdays(int year);
-char * CalendarFromCalToString(const struct calendar cal);
-char * CalendarFromIsoToString(const struct isodate iso);
-int IsEqualCal(const struct calendar expected, const struct calendar actual);
-int IsEqualIso(const struct isodate expected, const struct isodate actual);
-char * DateFromCalToString(const struct calendar cal);
-char * DateFromIsoToString(const struct isodate iso);
-int IsEqualDateCal(const struct calendar expected, const struct calendar actual);
-int IsEqualDateIso(const struct isodate expected, const struct isodate actual);
+char * CalendarFromCalToString(const struct calendar *cal);
+char * CalendarFromIsoToString(const struct isodate *iso);
+int IsEqualCal(const struct calendar *expected, const struct calendar *actual);
+int IsEqualIso(const struct isodate *expected, const struct isodate *actual);
+char * DateFromCalToString(const struct calendar *cal);
+char * DateFromIsoToString(const struct isodate *iso);
+int IsEqualDateCal(const struct calendar *expected, const struct calendar *actual);
+int IsEqualDateIso(const struct isodate *expected, const struct isodate *actual);
void test_DaySplitMerge(void);
void test_SplitYearDays1(void);
void test_SplitYearDays2(void);
void test_RoundTripMonthStart(void);
void test_RoundTripWeekStart(void);
void test_RoundTripDayStart(void);
-
-
+void test_IsoCalYearsToWeeks(void);
+void test_IsoCalWeeksToYearStart(void);
+void test_IsoCalWeeksToYearEnd(void);
+void test_DaySecToDate(void);
// ---------------------------------------------------------------------
// test support stuff
}
char *
-CalendarFromCalToString(const struct calendar cal) {
- char * str = malloc (sizeof (char) * 100);
-
- char buffer[100] ="";
- snprintf(buffer, 100, "%u", cal.year);
- strcat(str, buffer);
- strcat(str, "-");
- snprintf(buffer, 100, "%u", (u_int)cal.month);
- strcat(str, buffer);
- strcat(str, "-");
- snprintf(buffer, 100, "%u", (u_int)cal.monthday);
- strcat(str, buffer);
- strcat(str, " (");
- snprintf(buffer, 100, "%u", cal.yearday);
- strcat(str, buffer);
- strcat(str, ") ");
- snprintf(buffer, 100, "%u", (u_int)cal.hour);
- strcat(str, buffer);
- strcat(str, ":");
- snprintf(buffer, 100, "%u", (u_int)cal.minute);
- strcat(str, buffer);
- strcat(str, ":");
- snprintf(buffer, 100, "%u", (u_int)cal.second);
- strcat(str, buffer);
-
+CalendarFromCalToString(
+ const struct calendar *cal)
+{
+ char * str = malloc(sizeof (char) * 100);
+ snprintf(str, 100, "%u-%02u-%02u (%u) %02u:%02u:%02u",
+ cal->year, (u_int)cal->month, (u_int)cal->monthday,
+ cal->yearday,
+ (u_int)cal->hour, (u_int)cal->minute, (u_int)cal->second);
+ str[99] = '\0'; /* paranoia rulez! */
return str;
}
char *
-CalendarFromIsoToString(const struct isodate iso) {
-
+CalendarFromIsoToString(
+ const struct isodate *iso)
+{
char * str = malloc (sizeof (char) * 100);
-
- char buffer[100] ="";
- snprintf(buffer, 100, "%u", iso.year);
- strcat(str, buffer);
- strcat(str, "-");
- snprintf(buffer, 100, "%u", (u_int)iso.week);
- strcat(str, buffer);
- strcat(str, "-");
- snprintf(buffer, 100, "%u", (u_int)iso.weekday);
- strcat(str, buffer);
- snprintf(buffer, 100, "%u", (u_int)iso.hour);
- strcat(str, buffer);
- strcat(str, ":");
- snprintf(buffer, 100, "%u", (u_int)iso.minute);
- strcat(str, buffer);
- strcat(str, ":");
- snprintf(buffer, 100, "%u", (u_int)iso.second);
- strcat(str, buffer);
-
+ snprintf(str, 100, "%u-W%02u-%02u %02u:%02u:%02u",
+ iso->year, (u_int)iso->week, (u_int)iso->weekday,
+ (u_int)iso->hour, (u_int)iso->minute, (u_int)iso->second);
+ str[99] = '\0'; /* paranoia rulez! */
return str;
}
int
-IsEqualCal(const struct calendar expected, const struct calendar actual) {
- if (expected.year == actual.year &&
- (!expected.yearday || expected.yearday == actual.yearday) &&
- expected.month == actual.month &&
- expected.monthday == actual.monthday &&
- expected.hour == actual.hour &&
- expected.minute == actual.minute &&
- expected.second == actual.second) {
+IsEqualCal(
+ const struct calendar *expected,
+ const struct calendar *actual)
+{
+ if (expected->year == actual->year &&
+ (!expected->yearday || expected->yearday == actual->yearday) &&
+ expected->month == actual->month &&
+ expected->monthday == actual->monthday &&
+ expected->hour == actual->hour &&
+ expected->minute == actual->minute &&
+ expected->second == actual->second) {
return TRUE;
} else {
- printf("expected: %s but was %s", CalendarFromCalToString(expected) , CalendarFromCalToString(actual));
+ printf("expected: %s but was %s",
+ CalendarFromCalToString(expected),
+ CalendarFromCalToString(actual));
return FALSE;
}
}
int
-IsEqualIso(const struct isodate expected, const struct isodate actual) {
- if (expected.year == actual.year &&
- expected.week == actual.week &&
- expected.weekday == actual.weekday &&
- expected.hour == actual.hour &&
- expected.minute == actual.minute &&
- expected.second == actual.second) {
+IsEqualIso(
+ const struct isodate *expected,
+ const struct isodate *actual)
+{
+ if (expected->year == actual->year &&
+ expected->week == actual->week &&
+ expected->weekday == actual->weekday &&
+ expected->hour == actual->hour &&
+ expected->minute == actual->minute &&
+ expected->second == actual->second) {
return TRUE;
} else {
- printf("expected: %s but was %s", CalendarFromIsoToString(expected) , CalendarFromIsoToString(actual));
+ printf("expected: %s but was %s",
+ CalendarFromIsoToString(expected),
+ CalendarFromIsoToString(actual));
return FALSE;
}
}
char *
-DateFromCalToString(const struct calendar cal) {
+DateFromCalToString(
+ const struct calendar *cal)
+{
char * str = malloc (sizeof (char) * 100);
-
- char buffer[100] ="";
- snprintf(buffer, 100, "%u", cal.year);
- strcat(str, buffer);
- strcat(str, "-");
- snprintf(buffer, 100, "%u", (u_int)cal.month);
- strcat(str, buffer);
- strcat(str, "-");
- snprintf(buffer, 100, "%u", (u_int)cal.monthday);
- strcat(str, buffer);
- strcat(str, " (");
- snprintf(buffer, 100, "%u", cal.yearday);
- strcat(str, buffer);
- strcat(str, ")");
-
+ snprintf(str, 100, "%u-%02u-%02u (%u)",
+ cal->year, (u_int)cal->month, (u_int)cal->monthday,
+ cal->yearday);
+ str[99] = '\0'; /* paranoia rulez! */
return str;
}
char *
-DateFromIsoToString(const struct isodate iso) {
+DateFromIsoToString(
+ const struct isodate *iso)
+{
char * str = malloc (sizeof (char) * 100);
-
- char buffer[100] ="";
- snprintf(buffer, 100, "%u", iso.year);
- strcat(str, buffer);
- strcat(str, "-");
- snprintf(buffer, 100, "%u", (u_int)iso.week);
- strcat(str, buffer);
- strcat(str, "-");
- snprintf(buffer, 100, "%u", (u_int)iso.weekday);
- strcat(str, buffer);
-
+ snprintf(str, 100, "%u-W%02u-%02u",
+ iso->year, (u_int)iso->week, (u_int)iso->weekday);
+ str[99] = '\0'; /* paranoia rulez! */
return str;
}
// boolean
int
-IsEqualDateCal(const struct calendar expected, const struct calendar actual) {
- if (expected.year == actual.year &&
- (!expected.yearday || expected.yearday == actual.yearday) &&
- expected.month == actual.month &&
- expected.monthday == actual.monthday) {
+IsEqualDateCal(
+ const struct calendar *expected,
+ const struct calendar *actual)
+{
+ if (expected->year == actual->year &&
+ (!expected->yearday || expected->yearday == actual->yearday) &&
+ expected->month == actual->month &&
+ expected->monthday == actual->monthday) {
return TRUE;
} else {
- printf("expected: %s but was %s", DateFromCalToString(expected) ,DateFromCalToString(actual));
+ printf("expected: %s but was %s",
+ DateFromCalToString(expected),
+ DateFromCalToString(actual));
return FALSE;
}
}
// boolean
int
-IsEqualDateIso(const struct isodate expected, const struct isodate actual) {
- if (expected.year == actual.year &&
- expected.week == actual.week &&
- expected.weekday == actual.weekday) {
+IsEqualDateIso(
+ const struct isodate *expected,
+ const struct isodate *actual)
+{
+ if (expected->year == actual->year &&
+ expected->week == actual->week &&
+ expected->weekday == actual->weekday) {
return TRUE;
} else {
- printf("expected: %s but was %s", DateFromIsoToString(expected) ,DateFromIsoToString(actual));
+ printf("expected: %s but was %s",
+ DateFromIsoToString(expected),
+ DateFromIsoToString(actual));
return FALSE;
}
}
struct calendar actual;
ntpcal_rd_to_date(&actual, testDate);
- TEST_ASSERT_TRUE(IsEqualDateCal(expected, actual));
+ TEST_ASSERT_TRUE(IsEqualDateCal(&expected, &actual));
}
// check last day of february for first 10000 years
ntpcal_rd_to_date(&dateOut, ntpcal_date_to_rd(&dateIn));
- TEST_ASSERT_TRUE(IsEqualDateCal(dateIn, dateOut));
+ TEST_ASSERT_TRUE(IsEqualDateCal(&dateIn, &dateOut));
}
}
dateIn.yearday = 60 + leapdays(dateIn.year);
ntpcal_rd_to_date(&dateOut, ntpcal_date_to_rd(&dateIn));
- TEST_ASSERT_TRUE(IsEqualDateCal(dateIn, dateOut));
+ TEST_ASSERT_TRUE(IsEqualDateCal(&dateIn, &dateOut));
}
}
TEST_ASSERT_EQUAL(expRdn, truRdn);
ntpcal_rd_to_date(&truDate, truRdn);
- TEST_ASSERT_TRUE(IsEqualDateCal(expDate, truDate));
+ TEST_ASSERT_TRUE(IsEqualDateCal(&expDate, &truDate));
}
}
}
expds = ntpcal_date_to_ntp(&date);
TEST_ASSERT_EQUAL(expds, truds);
}
-}
+}
+
+/* ---------------------------------------------------------------------
+ * ISO8601 week calendar internals
+ *
+ * The ISO8601 week calendar implementation is simple in the terms of
+ * the math involved, but the implementation of the calculations must
+ * take care of a few things like overflow, floor division, and sign
+ * corrections.
+ *
+ * Most of the functions are straight forward, but converting from years
+ * to weeks and from weeks to years warrants some extra tests. These use
+ * an independent reference implementation of the conversion from years
+ * to weeks.
+ * ---------------------------------------------------------------------
+ */
+
+/* helper / reference implementation for the first week of year in the
+ * ISO8601 week calendar. This is based on the reference definition of
+ * the ISO week calendar start: The Monday closest to January,1st of the
+ * corresponding year in the Gregorian calendar.
+ */
+static int32_t
+refimpl_WeeksInIsoYears(
+ int32_t years)
+{
+ int32_t days, weeks;
+ days = ntpcal_weekday_close(
+ ntpcal_days_in_years(years) + 1,
+ CAL_MONDAY) - 1;
+ /* the weekday functions operate on RDN, while we want elapsed
+ * units here -- we have to add / sub 1 in the midlle / at the
+ * end of the operation that gets us the first day of the ISO
+ * week calendar day.
+ */
+ weeks = days / 7;
+ days = days % 7;
+ TEST_ASSERT_EQUAL(0, days); /* paranoia check... */
+ return weeks;
+}
+
+/* The next tests loop over 5000yrs, but should still be very fast. If
+ * they are not, the calendar needs a better implementation...
+ */
+void test_IsoCalYearsToWeeks(void) {
+ int32_t years;
+ int32_t wref, wcal;
+ for (years = -1000; years < 4000; ++years) {
+ /* get number of weeks before years (reference) */
+ wref = refimpl_WeeksInIsoYears(years);
+ /* get number of weeks before years (object-under-test) */
+ wcal = isocal_weeks_in_years(years);
+ TEST_ASSERT_EQUAL(wref, wcal);
+ }
+}
+
+void test_IsoCalWeeksToYearStart(void) {
+ int32_t years;
+ int32_t wref;
+ ntpcal_split ysplit;
+ for (years = -1000; years < 4000; ++years) {
+ /* get number of weeks before years (reference) */
+ wref = refimpl_WeeksInIsoYears(years);
+ /* reverse split */
+ ysplit = isocal_split_eraweeks(wref);
+ /* check invariants: same year, week 0 */
+ TEST_ASSERT_EQUAL(years, ysplit.hi);
+ TEST_ASSERT_EQUAL(0, ysplit.lo);
+ }
+}
+
+void test_IsoCalWeeksToYearEnd(void) {
+ int32_t years;
+ int32_t wref;
+ ntpcal_split ysplit;
+ for (years = -1000; years < 4000; ++years) {
+ /* get last week of previous year */
+ wref = refimpl_WeeksInIsoYears(years) - 1;
+ /* reverse split */
+ ysplit = isocal_split_eraweeks(wref);
+ /* check invariants: previous year, week 51 or 52 */
+ TEST_ASSERT_EQUAL(years-1, ysplit.hi);
+ TEST_ASSERT(ysplit.lo == 51 || ysplit.lo == 52);
+ }
+}
+
+void test_DaySecToDate(void) {
+ struct calendar cal;
+ int32_t days;
+
+ days = ntpcal_daysec_to_date(&cal, -86400);
+ TEST_ASSERT_MESSAGE((days==-1 && cal.hour==0 && cal.minute==0 && cal.second==0),
+ "failed for -86400");
+
+ days = ntpcal_daysec_to_date(&cal, -86399);
+ TEST_ASSERT_MESSAGE((days==-1 && cal.hour==0 && cal.minute==0 && cal.second==1),
+ "failed for -86399");
+
+ days = ntpcal_daysec_to_date(&cal, -1);
+ TEST_ASSERT_MESSAGE((days==-1 && cal.hour==23 && cal.minute==59 && cal.second==59),
+ "failed for -1");
+
+ days = ntpcal_daysec_to_date(&cal, 0);
+ TEST_ASSERT_MESSAGE((days==0 && cal.hour==0 && cal.minute==0 && cal.second==0),
+ "failed for 0");
+
+ days = ntpcal_daysec_to_date(&cal, 1);
+ TEST_ASSERT_MESSAGE((days==0 && cal.hour==0 && cal.minute==0 && cal.second==1),
+ "failed for 1");
+
+ days = ntpcal_daysec_to_date(&cal, 86399);
+ TEST_ASSERT_MESSAGE((days==0 && cal.hour==23 && cal.minute==59 && cal.second==59),
+ "failed for 86399");
+
+ days = ntpcal_daysec_to_date(&cal, 86400);
+ TEST_ASSERT_MESSAGE((days==1 && cal.hour==0 && cal.minute==0 && cal.second==0),
+ "failed for 86400");
+}