xref: /php-src/ext/calendar/gregor.c (revision 80894d87)
1 /* $selId: gregor.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  *     SdnToGregorian(
14  *         long int  sdn,
15  *         int      *pYear,
16  *         int      *pMonth,
17  *         int      *pDay);
18  *
19  * Convert a SDN to a Gregorian calendar date.  If the input SDN is less
20  * than 1, the three output values will all be set to zero, otherwise
21  * *pYear will be >= -4714 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  *     long int
25  *     GregorianToSdn(
26  *         int inputYear,
27  *         int inputMonth,
28  *         int inputDay);
29  *
30  * Convert a Gregorian 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  *     char *MonthNameShort[13];
38  *
39  * Convert a Gregorian month number (1 to 12) to the abbreviated (three
40  * character) name of the Gregorian month (null terminated).  An index of
41  * zero will return a zero length string.
42  *
43  *     char *MonthNameLong[13];
44  *
45  * Convert a Gregorian month number (1 to 12) to the name of the Gregorian
46  * month (null terminated).  An index of zero will return a zero length
47  * string.
48  *
49  * VALID RANGE
50  *
51  *     4714 B.C. to at least 10000 A.D.
52  *
53  *     Although this software can handle dates all the way back to 4714
54  *     B.C., such use may not be meaningful.  The Gregorian calendar was
55  *     not instituted until October 15, 1582 (or October 5, 1582 in the
56  *     Julian calendar).  Some countries did not accept it until much
57  *     later.  For example, Britain converted in 1752, The USSR in 1918 and
58  *     Greece in 1923.  Most European countries used the Julian calendar
59  *     prior to the Gregorian.
60  *
61  * CALENDAR OVERVIEW
62  *
63  *     The Gregorian calendar is a modified version of the Julian calendar.
64  *     The only difference being the specification of leap years.  The
65  *     Julian calendar specifies that every year that is a multiple of 4
66  *     will be a leap year.  This leads to a year that is 365.25 days long,
67  *     but the current accepted value for the tropical year is 365.242199
68  *     days.
69  *
70  *     To correct this error in the length of the year and to bring the
71  *     vernal equinox back to March 21, Pope Gregory XIII issued a papal
72  *     bull declaring that Thursday October 4, 1582 would be followed by
73  *     Friday October 15, 1582 and that centennial years would only be a
74  *     leap year if they were a multiple of 400.  This shortened the year
75  *     by 3 days per 400 years, giving a year of 365.2425 days.
76  *
77  *     Another recently proposed change in the leap year rule is to make
78  *     years that are multiples of 4000 not a leap year, but this has never
79  *     been officially accepted and this rule is not implemented in these
80  *     algorithms.
81  *
82  * ALGORITHMS
83  *
84  *     The calculations are based on three different cycles: a 400 year
85  *     cycle of leap years, a 4 year cycle of leap years and a 5 month
86  *     cycle of month lengths.
87  *
88  *     The 5 month cycle is used to account for the varying lengths of
89  *     months.  You will notice that the lengths alternate between 30
90  *     and 31 days, except for three anomalies: both July and August
91  *     have 31 days, both December and January have 31, and February
92  *     is less than 30.  Starting with March, the lengths are in a
93  *     cycle of 5 months (31, 30, 31, 30, 31):
94  *
95  *         Mar   31 days  \
96  *         Apr   30 days   |
97  *         May   31 days    > First cycle
98  *         Jun   30 days   |
99  *         Jul   31 days  /
100  *
101  *         Aug   31 days  \
102  *         Sep   30 days   |
103  *         Oct   31 days    > Second cycle
104  *         Nov   30 days   |
105  *         Dec   31 days  /
106  *
107  *         Jan   31 days  \
108  *         Feb 28/9 days   |
109  *                          > Third cycle (incomplete)
110  *
111  *     For this reason the calculations (internally) assume that the
112  *     year starts with March 1.
113  *
114  * TESTING
115  *
116  *     This algorithm has been tested from the year 4714 B.C. to 10000
117  *     A.D.  The source code of the verification program is included in
118  *     this package.
119  *
120  * REFERENCES
121  *
122  *     Conversions Between Calendar Date and Julian Day Number by Robert J.
123  *     Tantzen, Communications of the Association for Computing Machinery
124  *     August 1963.  (Also published in Collected Algorithms from CACM,
125  *     algorithm number 199).
126  *
127  **************************************************************************/
128 
129 #include "sdncal.h"
130 #include <limits.h>
131 
132 #define GREGOR_SDN_OFFSET         32045
133 #define DAYS_PER_5_MONTHS  153
134 #define DAYS_PER_4_YEARS   1461
135 #define DAYS_PER_400_YEARS 146097
136 
SdnToGregorian(zend_long sdn,int * pYear,int * pMonth,int * pDay)137 void SdnToGregorian(
138 					   zend_long sdn,
139 					   int *pYear,
140 					   int *pMonth,
141 					   int *pDay)
142 {
143 	int century;
144 	int year;
145 	int month;
146 	int day;
147 	zend_long temp;
148 	int dayOfYear;
149 
150 	if (sdn <= 0 ||
151 			sdn > (ZEND_LONG_MAX - 4 * GREGOR_SDN_OFFSET) / 4) {
152 		goto fail;
153 	}
154 	temp = (sdn + GREGOR_SDN_OFFSET) * 4 - 1;
155 
156 	if (temp < 0 || (temp / DAYS_PER_400_YEARS) > INT_MAX) {
157 		goto fail;
158 	}
159 
160 	/* Calculate the century (year/100). */
161 	century = temp / DAYS_PER_400_YEARS;
162 
163 	/* Calculate the year and day of year (1 <= dayOfYear <= 366). */
164 	temp = ((temp % DAYS_PER_400_YEARS) / 4) * 4 + 3;
165 
166 	if (century > ((INT_MAX / 100) - (temp / DAYS_PER_4_YEARS))) {
167 		goto fail;
168 	}
169 
170 	year = (century * 100) + (temp / DAYS_PER_4_YEARS);
171 	dayOfYear = (temp % DAYS_PER_4_YEARS) / 4 + 1;
172 
173 	/* Calculate the month and day of month. */
174 	temp = dayOfYear * 5 - 3;
175 	month = temp / DAYS_PER_5_MONTHS;
176 	day = (temp % DAYS_PER_5_MONTHS) / 5 + 1;
177 
178 	/* Convert to the normal beginning of the year. */
179 	if (month < 10) {
180 		month += 3;
181 	} else {
182 		year += 1;
183 		month -= 9;
184 	}
185 
186 	/* Adjust to the B.C./A.D. type numbering. */
187 	year -= 4800;
188 	if (year <= 0)
189 		year--;
190 
191 	*pYear = year;
192 	*pMonth = month;
193 	*pDay = day;
194 	return;
195 
196 fail:
197 	*pYear = 0;
198 	*pMonth = 0;
199 	*pDay = 0;
200 }
201 
GregorianToSdn(int inputYear,int inputMonth,int inputDay)202 zend_long GregorianToSdn(
203 						   int inputYear,
204 						   int inputMonth,
205 						   int inputDay)
206 {
207 	zend_long year;
208 	int month;
209 
210 	/* check for invalid dates */
211 	if (inputYear == 0 || inputYear < -4714 ||
212 		inputMonth <= 0 || inputMonth > 12 ||
213 		inputDay <= 0 || inputDay > 31) {
214 		return (0);
215 	}
216 	/* check for dates before SDN 1 (Nov 25, 4714 B.C.) */
217 	if (inputYear == -4714) {
218 		if (inputMonth < 11) {
219 			return (0);
220 		}
221 		if (inputMonth == 11 && inputDay < 25) {
222 			return (0);
223 		}
224 	}
225 	/* Make year always a positive number. */
226 	if (inputYear < 0) {
227 		year = inputYear + 4801;
228 	} else {
229 		year = inputYear + 4800;
230 	}
231 
232 	/* Adjust the start of the year. */
233 	if (inputMonth > 2) {
234 		month = inputMonth - 3;
235 	} else {
236 		month = inputMonth + 9;
237 		year--;
238 	}
239 
240 	return (((year / 100) * DAYS_PER_400_YEARS) / 4
241 			+ ((year % 100) * DAYS_PER_4_YEARS) / 4
242 			+ (month * DAYS_PER_5_MONTHS + 2) / 5
243 			+ inputDay
244 			- GREGOR_SDN_OFFSET);
245 }
246 
247 const char * const MonthNameShort[13] =
248 {
249 	"",
250 	"Jan",
251 	"Feb",
252 	"Mar",
253 	"Apr",
254 	"May",
255 	"Jun",
256 	"Jul",
257 	"Aug",
258 	"Sep",
259 	"Oct",
260 	"Nov",
261 	"Dec"
262 };
263 
264 const char * const MonthNameLong[13] =
265 {
266 	"",
267 	"January",
268 	"February",
269 	"March",
270 	"April",
271 	"May",
272 	"June",
273 	"July",
274 	"August",
275 	"September",
276 	"October",
277 	"November",
278 	"December"
279 };
280