summaryrefslogtreecommitdiffstats
path: root/libntp/caltontp.c
diff options
context:
space:
mode:
Diffstat (limited to 'libntp/caltontp.c')
-rw-r--r--libntp/caltontp.c113
1 files changed, 88 insertions, 25 deletions
diff --git a/libntp/caltontp.c b/libntp/caltontp.c
index 9ec1064..9c41368 100644
--- a/libntp/caltontp.c
+++ b/libntp/caltontp.c
@@ -6,37 +6,100 @@
#include "ntp_types.h"
#include "ntp_calendar.h"
#include "ntp_stdlib.h"
+#include "ntp_assert.h"
+/*
+ * Juergen Perlinger, 2008-11-12
+ * Add support for full calendar calculatios. If the day-of-year is provided
+ * (that is, not zero) it will be used instead of month and day-of-month;
+ * otherwise a full turn through the calendar calculations will be taken.
+ *
+ * I know that Harlan Stenn likes to see assertions in production code, and I
+ * agree there, but it would be a tricky thing here. The algorithm is quite
+ * capable of producing sensible answers even to seemingly weird inputs: the
+ * date <any year here>-03-00, the 0.th March of the year, will be automtically
+ * treated as the last day of February, no matter whether the year is a leap
+ * year or not. So adding constraints is merely for the benefit of the callers,
+ * because the only thing we can check for consistency is our input, produced
+ * by somebody else.
+ *
+ * BTW: A total roundtrip using 'caljulian' would be a quite shaky thing:
+ * Because of the truncation of the NTP time stamp to 32 bits and the epoch
+ * unfolding around the current time done by 'caljulian' the roundtrip does
+ * *not* necessarily reproduce the input, especially if the time spec is more
+ * than 68 years off from the current time...
+ */
u_long
caltontp(
- register const struct calendar *jt
+ const struct calendar *jt
)
{
- u_long ace_days; /* absolute Christian Era days */
- u_long ntp_days;
- int prior_years;
- u_long ntp_time;
-
- /*
- * First convert today's date to absolute days past 12/1/1 BC
- */
- prior_years = jt->year-1;
- ace_days = jt->yearday /* days this year */
- +(DAYSPERYEAR*prior_years) /* plus days in previous years */
- +(prior_years/4) /* plus prior years's leap days */
- -(prior_years/100) /* minus leapless century years */
- +(prior_years/400); /* plus leapful Gregorian yrs */
+ ntp_u_int32_t days; /* full days in NTP epoch */
+ ntp_u_int32_t years; /* complete ACE years before date */
+ ntp_u_int32_t month; /* adjusted month for calendar */
+
+ NTP_INSIST(jt != NULL);
- /*
- * Subtract out 1/1/1900, the beginning of the NTP epoch
- */
- ntp_days = ace_days - DAY_NTP_STARTS;
+ NTP_REQUIRE(jt->month <= 13); /* permit month 0..13! */
+ NTP_REQUIRE(jt->monthday <= 32);
+ NTP_REQUIRE(jt->yearday <= 366);
+ NTP_REQUIRE(jt->hour <= 24);
+ NTP_REQUIRE(jt->minute <= MINSPERHR);
+ NTP_REQUIRE(jt->second <= SECSPERMIN);
- /*
- * Do the obvious:
- */
- ntp_time =
- ntp_days*SECSPERDAY+SECSPERMIN*(MINSPERHR*jt->hour + jt->minute);
+ /*
+ * First convert the date to fully elapsed days since NTP epoch. The
+ * expressions used here give us initially days since 0001-01-01, the
+ * beginning of the christian era in the proleptic gregorian calendar;
+ * they are rebased on-the-fly into days since beginning of the NTP
+ * epoch, 1900-01-01.
+ */
+ if (jt->yearday) {
+ /*
+ * Assume that the day-of-year contains a useable value and
+ * avoid all calculations involving month and day-of-month.
+ */
+ years = jt->year - 1;
+ days = years * DAYSPERYEAR /* days in previous years */
+ + years / 4 /* plus prior years's leap days */
+ - years / 100 /* minus leapless century years */
+ + years / 400 /* plus leapful Gregorian yrs */
+ + jt->yearday /* days this year */
+ - DAY_NTP_STARTS; /* rebase to NTP epoch */
+ } else {
+ /*
+ * The following code is according to the excellent book
+ * 'Calendrical Calculations' by Nachum Dershowitz and Edward
+ * Reingold. It does a full calendar evaluation, using one of
+ * the alternate algorithms: Shift to a hypothetical year
+ * starting on the previous march,1st; merge years, month and
+ * days; undo the the 9 month shift (which is 306 days). The
+ * advantage is that we do NOT need to now whether a year is a
+ * leap year or not, because the leap day is the LAST day of
+ * the year.
+ */
+ month = (ntp_u_int32_t)jt->month + 9;
+ years = jt->year - 1 + month / 12;
+ month %= 12;
+ days = years * DAYSPERYEAR /* days in previous years */
+ + years / 4 /* plus prior years's leap days */
+ - years / 100 /* minus leapless century years */
+ + years / 400 /* plus leapful Gregorian yrs */
+ + (month * 153 + 2) / 5 /* plus days before month */
+ + jt->monthday /* plus day-of-month */
+ - 306 /* minus 9 months */
+ - DAY_NTP_STARTS; /* rebase to NTP epoch */
+ }
- return ntp_time;
+ /*
+ * Do the obvious: Merge everything together, making sure integer
+ * promotion doesn't play dirty tricks on us; there is probably some
+ * redundancy in the casts, but this drives it home with force. All
+ * arithmetic is done modulo 2**32, because the result is truncated
+ * anyway.
+ */
+ return days * SECSPERDAY
+ + (ntp_u_int32_t)jt->hour * MINSPERHR*SECSPERMIN
+ + (ntp_u_int32_t)jt->minute * SECSPERMIN
+ + (ntp_u_int32_t)jt->second;
}
OpenPOWER on IntegriCloud