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