1/*
2 * caltontp - convert a date to an NTP time
3 */
4#include <sys/types.h>
5
6#include "ntp_types.h"
7#include "ntp_calendar.h"
8#include "ntp_stdlib.h"
9#include "ntp_assert.h"
10
11/*
12 * Juergen Perlinger, 2008-11-12
13 * Add support for full calendar calculatios. If the day-of-year is provided
14 * (that is, not zero) it will be used instead of month and day-of-month;
15 * otherwise a full turn through the calendar calculations will be taken.
16 *
17 * I know that Harlan Stenn likes to see assertions in production code, and I
18 * agree there, but it would be a tricky thing here. The algorithm is quite
19 * capable of producing sensible answers even to seemingly weird inputs: the
20 * date <any year here>-03-00, the 0.th March of the year, will be automtically
21 * treated as the last day of February, no matter whether the year is a leap
22 * year or not. So adding constraints is merely for the benefit of the callers,
23 * because the only thing we can check for consistency is our input, produced
24 * by somebody else.
25 *
26 * BTW: A total roundtrip using 'caljulian' would be a quite shaky thing:
27 * Because of the truncation of the NTP time stamp to 32 bits and the epoch
28 * unfolding around the current time done by 'caljulian' the roundtrip does
29 * *not* necessarily reproduce the input, especially if the time spec is more
30 * than 68 years off from the current time...
31 */
32u_long
33caltontp(
34	const struct calendar *jt
35	)
36{
37	ntp_u_int32_t days;	/* full days in NTP epoch */
38	ntp_u_int32_t years;	/* complete ACE years before date */
39	ntp_u_int32_t month;	/* adjusted month for calendar */
40
41	NTP_INSIST(jt != NULL);
42
43	NTP_REQUIRE(jt->month <= 13);	/* permit month 0..13! */
44	NTP_REQUIRE(jt->monthday <= 32);
45	NTP_REQUIRE(jt->yearday <= 366);
46	NTP_REQUIRE(jt->hour <= 24);
47	NTP_REQUIRE(jt->minute <= MINSPERHR);
48	NTP_REQUIRE(jt->second <= SECSPERMIN);
49
50	/*
51	 * First convert the date to fully elapsed days since NTP epoch. The
52	 * expressions used here give us initially days since 0001-01-01, the
53	 * beginning of the christian era in the proleptic gregorian calendar;
54	 * they are rebased on-the-fly into days since beginning of the NTP
55	 * epoch, 1900-01-01.
56	 */
57	if (jt->yearday) {
58		/*
59		 * Assume that the day-of-year contains a useable value and
60		 * avoid all calculations involving month and day-of-month.
61		 */
62		years = jt->year - 1;
63		days  = years * DAYSPERYEAR	/* days in previous years */
64		      + years / 4		/* plus prior years's leap days */
65		      - years / 100		/* minus leapless century years */
66		      + years / 400		/* plus leapful Gregorian yrs */
67		      + jt->yearday		/* days this year */
68		      - DAY_NTP_STARTS;		/* rebase to NTP epoch */
69	} else {
70		/*
71		 * The following code is according to the excellent book
72		 * 'Calendrical Calculations' by Nachum Dershowitz and Edward
73		 * Reingold. It does a full calendar evaluation, using one of
74		 * the alternate algorithms: Shift to a hypothetical year
75		 * starting on the previous march,1st; merge years, month and
76		 * days; undo the the 9 month shift (which is 306 days). The
77		 * advantage is that we do NOT need to now whether a year is a
78		 * leap year or not, because the leap day is the LAST day of
79		 * the year.
80		 */
81		month  = (ntp_u_int32_t)jt->month + 9;
82		years  = jt->year - 1 + month / 12;
83		month %= 12;
84		days   = years * DAYSPERYEAR	/* days in previous years */
85		       + years / 4		/* plus prior years's leap days */
86		       - years / 100		/* minus leapless century years */
87		       + years / 400		/* plus leapful Gregorian yrs */
88		       + (month * 153 + 2) / 5	/* plus days before month */
89		       + jt->monthday		/* plus day-of-month */
90		       - 306			/* minus 9 months */
91		       - DAY_NTP_STARTS;	/* rebase to NTP epoch */
92	}
93
94	/*
95	 * Do the obvious: Merge everything together, making sure integer
96	 * promotion doesn't play dirty tricks on us; there is probably some
97	 * redundancy in the casts, but this drives it home with force. All
98	 * arithmetic is done modulo 2**32, because the result is truncated
99	 * anyway.
100	 */
101	return               days       * SECSPERDAY
102	    + (ntp_u_int32_t)jt->hour   * MINSPERHR*SECSPERMIN
103	    + (ntp_u_int32_t)jt->minute * SECSPERMIN
104	    + (ntp_u_int32_t)jt->second;
105}
106