xref: /php-src/ext/calendar/julian.c (revision 92ac598a)
1 /* $selId: julian.c,v 2.0 1995/10/24 01:13:06 lees Exp $
2  * Copyright 1993-1995, Scott E. Lee, all rights reserved.
3  * Permission granted to use, copy, modify, distribute and sell so long as
4  * the above copyright and this permission statement are retained in all
5  * copies.  THERE IS NO WARRANTY - USE AT YOUR OWN RISK.
6  */
7 
8 /**************************************************************************
9  *
10  * These are the externally visible components of this file:
11  *
12  *     void
13  *     SdnToJulian(
14  *         zend_long  sdn,
15  *         int      *pYear,
16  *         int      *pMonth,
17  *         int      *pDay);
18  *
19  * Convert a SDN to a Julian calendar date.  If the input SDN is less than
20  * 1, the three output values will all be set to zero, otherwise *pYear
21  * will be >= -4713 and != 0; *pMonth will be in the range 1 to 12
22  * inclusive; *pDay will be in the range 1 to 31 inclusive.
23  *
24  *     zend_long
25  *     JulianToSdn(
26  *         int inputYear,
27  *         int inputMonth,
28  *         int inputDay);
29  *
30  * Convert a Julian calendar date to a SDN.  Zero is returned when the
31  * input date is detected as invalid or out of the supported range.  The
32  * return value will be > 0 for all valid, supported dates, but there are
33  * some invalid dates that will return a positive value.  To verify that a
34  * date is valid, convert it to SDN and then back and compare with the
35  * original.
36  *
37  * VALID RANGE
38  *
39  *     4713 B.C. to at least 10000 A.D.
40  *
41  *     Although this software can handle dates all the way back to 4713
42  *     B.C., such use may not be meaningful.  The calendar was created in
43  *     46 B.C., but the details did not stabilize until at least 8 A.D.,
44  *     and perhaps as late at the 4th century.  Also, the beginning of a
45  *     year varied from one culture to another - not all accepted January
46  *     as the first month.
47  *
48  * CALENDAR OVERVIEW
49  *
50  *     Julius Caesar created the calendar in 46 B.C. as a modified form of
51  *     the old Roman republican calendar which was based on lunar cycles.
52  *     The new Julian calendar set fixed lengths for the months, abandoning
53  *     the lunar cycle.  It also specified that there would be exactly 12
54  *     months per year and 365.25 days per year with every 4th year being a
55  *     leap year.
56  *
57  *     Note that the current accepted value for the tropical year is
58  *     365.242199 days, not 365.25.  This lead to an 11 day shift in the
59  *     calendar with respect to the seasons by the 16th century when the
60  *     Gregorian calendar was created to replace the Julian calendar.
61  *
62  *     The difference between the Julian and today's Gregorian calendar is
63  *     that the Gregorian does not make centennial years leap years unless
64  *     they are a multiple of 400, which leads to a year of 365.2425 days.
65  *     In other words, in the Gregorian calendar, 1700, 1800 and 1900 are
66  *     not leap years, but 2000 is.  All centennial years are leap years in
67  *     the Julian calendar.
68  *
69  *     The details are unknown, but the lengths of the months were adjusted
70  *     until they finally stablized in 8 A.D. with their current lengths:
71  *
72  *         January          31
73  *         February         28/29
74  *         March            31
75  *         April            30
76  *         May              31
77  *         June             30
78  *         Quintilis/July   31
79  *         Sextilis/August  31
80  *         September        30
81  *         October          31
82  *         November         30
83  *         December         31
84  *
85  *     In the early days of the calendar, the days of the month were not
86  *     numbered as we do today.  The numbers ran backwards (decreasing) and
87  *     were counted from the Ides (15th of the month - which in the old
88  *     Roman republican lunar calendar would have been the full moon) or
89  *     from the Nonae (9th day before the Ides) or from the beginning of
90  *     the next month.
91  *
92  *     In the early years, the beginning of the year varied, sometimes
93  *     based on the ascension of rulers.  It was not always the first of
94  *     January.
95  *
96  *     Also, today's epoch, 1 A.D. or the birth of Jesus Christ, did not
97  *     come into use until several centuries later when Christianity became
98  *     a dominant religion.
99  *
100  * ALGORITHMS
101  *
102  *     The calculations are based on two different cycles: a 4 year cycle
103  *     of leap years and a 5 month cycle of month lengths.
104  *
105  *     The 5 month cycle is used to account for the varying lengths of
106  *     months.  You will notice that the lengths alternate between 30 and
107  *     31 days, except for three anomalies: both July and August have 31
108  *     days, both December and January have 31, and February is less than
109  *     30.  Starting with March, the lengths are in a cycle of 5 months
110  *     (31, 30, 31, 30, 31):
111  *
112  *         Mar   31 days  \
113  *         Apr   30 days   |
114  *         May   31 days    > First cycle
115  *         Jun   30 days   |
116  *         Jul   31 days  /
117  *
118  *         Aug   31 days  \
119  *         Sep   30 days   |
120  *         Oct   31 days    > Second cycle
121  *         Nov   30 days   |
122  *         Dec   31 days  /
123  *
124  *         Jan   31 days  \
125  *         Feb 28/9 days   |
126  *                          > Third cycle (incomplete)
127  *
128  *     For this reason the calculations (internally) assume that the year
129  *     starts with March 1.
130  *
131  * TESTING
132  *
133  *     This algorithm has been tested from the year 4713 B.C. to 10000 A.D.
134  *     The source code of the verification program is included in this
135  *     package.
136  *
137  * REFERENCES
138  *
139  *     Conversions Between Calendar Date and Julian Day Number by Robert J.
140  *     Tantzen, Communications of the Association for Computing Machinery
141  *     August 1963.  (Also published in Collected Algorithms from CACM,
142  *     algorithm number 199).  [Note: the published algorithm is for the
143  *     Gregorian calendar, but was adjusted to use the Julian calendar's
144  *     simpler leap year rule.]
145  *
146  **************************************************************************/
147 
148 #include "sdncal.h"
149 #include <limits.h>
150 
151 #define JULIAN_SDN_OFFSET         32083
152 #define DAYS_PER_5_MONTHS  153
153 #define DAYS_PER_4_YEARS   1461
154 
SdnToJulian(zend_long sdn,int * pYear,int * pMonth,int * pDay)155 void SdnToJulian(
156 					zend_long sdn,
157 					int *pYear,
158 					int *pMonth,
159 					int *pDay)
160 {
161 	int year;
162 	int month;
163 	int day;
164 	zend_long temp;
165 	int dayOfYear;
166 
167 	if (sdn <= 0) {
168 		goto fail;
169 	}
170 	/* Check for overflow */
171 	if (sdn > (LONG_MAX - JULIAN_SDN_OFFSET * 4 + 1) / 4 || sdn < LONG_MIN / 4) {
172 		goto fail;
173 	}
174 	temp = sdn * 4 + (JULIAN_SDN_OFFSET * 4 - 1);
175 
176 	/* Calculate the year and day of year (1 <= dayOfYear <= 366). */
177 	{
178 		long yearl = temp / DAYS_PER_4_YEARS;
179 		if (yearl > INT_MAX || yearl < INT_MIN) {
180 			goto fail;
181 		}
182 		year = (int) yearl;
183 	}
184 	dayOfYear = (temp % DAYS_PER_4_YEARS) / 4 + 1;
185 
186 	/* Calculate the month and day of month. */
187 	temp = dayOfYear * 5 - 3;
188 	month = temp / DAYS_PER_5_MONTHS;
189 	day = (temp % DAYS_PER_5_MONTHS) / 5 + 1;
190 
191 	/* Convert to the normal beginning of the year. */
192 	if (month < 10) {
193 		month += 3;
194 	} else {
195 		year += 1;
196 		month -= 9;
197 	}
198 
199 	/* Adjust to the B.C./A.D. type numbering. */
200 	year -= 4800;
201 	if (year <= 0)
202 		year--;
203 
204 	*pYear = year;
205 	*pMonth = month;
206 	*pDay = day;
207 	return;
208 
209 fail:
210 	*pYear = 0;
211 	*pMonth = 0;
212 	*pDay = 0;
213 }
214 
JulianToSdn(int inputYear,int inputMonth,int inputDay)215 zend_long JulianToSdn(
216 						int inputYear,
217 						int inputMonth,
218 						int inputDay)
219 {
220 	zend_long year;
221 	int month;
222 
223 	/* check for invalid dates */
224 	if (inputYear == 0 || inputYear < -4713 ||
225 		inputMonth <= 0 || inputMonth > 12 ||
226 		inputDay <= 0 || inputDay > 31) {
227 		return (0);
228 	}
229 	/* check for dates before SDN 1 (Jan 2, 4713 B.C.) */
230 	if (inputYear == -4713) {
231 		if (inputMonth == 1 && inputDay == 1) {
232 			return (0);
233 		}
234 	}
235 	/* Make year always a positive number. */
236 	if (inputYear < 0) {
237 		year = inputYear + 4801;
238 	} else {
239 		year = inputYear + 4800;
240 	}
241 
242 	/* Adjust the start of the year. */
243 	if (inputMonth > 2) {
244 		month = inputMonth - 3;
245 	} else {
246 		month = inputMonth + 9;
247 		year--;
248 	}
249 
250 	return ((year * DAYS_PER_4_YEARS) / 4
251 			+ (month * DAYS_PER_5_MONTHS + 2) / 5
252 			+ inputDay
253 			- JULIAN_SDN_OFFSET);
254 }
255