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