xref: /dragonfly/lib/libcalendar/calendar.c (revision 4699ce0091cc8dca577f692bff8d79cbac437276)
1 /*-
2  * Copyright (c) 1997 Wolfgang Helbig
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer.
10  * 2. Redistributions in binary form must reproduce the above copyright
11  *    notice, this list of conditions and the following disclaimer in the
12  *    documentation and/or other materials provided with the distribution.
13  *
14  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
15  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
18  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
19  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
20  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
21  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
22  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
23  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
24  * SUCH DAMAGE.
25  *
26  * $FreeBSD: src/lib/libcalendar/calendar.c,v 1.3 1999/08/28 00:04:03 peter Exp $
27  */
28 
29 #include <sys/param.h>
30 #include "calendar.h"
31 
32 /*
33  * For each month tabulate the number of days elapsed in a year before the
34  * month. This assumes the internal date representation, where a year
35  * starts on March 1st. So we don't need a special table for leap years.
36  * But we do need a special table for the year 1582, since 10 days are
37  * deleted in October. This is month1s for the switch from Julian to
38  * Gregorian calendar.
39  */
40 static int const month1[] =
41     {0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337};
42    /*  M   A   M   J    J    A    S    O    N    D    J */
43 static int const month1s[]=
44     {0, 31, 61, 92, 122, 153, 184, 214, 235, 265, 296, 327};
45 
46 typedef struct date date;
47 
48 /* The last day of Julian calendar, in internal and ndays representation */
49 static int nswitch; /* The last day of Julian calendar */
50 static date jiswitch = {1582, 7, 3};
51 
52 static date         *date2idt(date *idt, date *dt);
53 static date         *idt2date(date *dt, date *idt);
54 static int           ndaysji(date *idt);
55 static int           ndaysgi(date *idt);
56 static int           firstweek(int year);
57 
58 /*
59  * Compute the Julian date from the number of days elapsed since
60  * March 1st of year zero.
61  */
62 date *
jdate(int ndays,date * dt)63 jdate(int ndays, date *dt)
64 {
65           date    idt;                  /* Internal date representation */
66           int     r;                    /* hold the rest of days */
67 
68           /*
69            * Compute the year by starting with an approximation not smaller
70            * than the answer and using linear search for the greatest
71            * year which does not begin after ndays.
72            */
73           idt.y = ndays / 365;
74           idt.m = 0;
75           idt.d = 0;
76           while ((r = ndaysji(&idt)) > ndays)
77                     idt.y--;
78 
79           /*
80            * Set r to the days left in the year and compute the month by
81            * linear search as the largest month that does not begin after r
82            * days.
83            */
84           r = ndays - r;
85           for (idt.m = 11; month1[idt.m] > r; idt.m--)
86                     ;
87 
88           /* Compute the days left in the month */
89           idt.d = r - month1[idt.m];
90 
91           /* return external representation of the date */
92           return (idt2date(dt, &idt));
93 }
94 
95 /*
96  * Return the number of days since March 1st of the year zero.
97  * The date is given according to Julian calendar.
98  */
99 int
ndaysj(date * dt)100 ndaysj(date *dt)
101 {
102           date    idt;                  /* Internal date representation */
103 
104           if (date2idt(&idt, dt) == NULL)
105                     return (-1);
106           else
107                     return (ndaysji(&idt));
108 }
109 
110 /*
111  * Same as above, where the Julian date is given in internal notation.
112  * This formula shows the beauty of this notation.
113  */
114 static int
ndaysji(date * idt)115 ndaysji(date * idt)
116 {
117 
118           return (idt->d + month1[idt->m] + idt->y * 365 + idt->y / 4);
119 }
120 
121 /*
122  * Compute the date according to the Gregorian calendar from the number of
123  * days since March 1st, year zero. The date computed will be Julian if it
124  * is older than 1582-10-05. This is the reverse of the function ndaysg().
125  */
126 date   *
gdate(int ndays,date * dt)127 gdate(int ndays, date *dt)
128 {
129           int const *montht;  /* month-table */
130           date    idt;                  /* for internal date representation */
131           int     r;                    /* holds the rest of days */
132 
133           /*
134            * Compute the year by starting with an approximation not smaller
135            * than the answer and search linearly for the greatest year not
136            * starting after ndays.
137            */
138           idt.y = ndays / 365;
139           idt.m = 0;
140           idt.d = 0;
141           while ((r = ndaysgi(&idt)) > ndays)
142                     idt.y--;
143 
144           /*
145            * Set ndays to the number of days left and compute by linear
146            * search the greatest month which does not start after ndays. We
147            * use the table month1 which provides for each month the number
148            * of days that elapsed in the year before that month. Here the
149            * year 1582 is special, as 10 days are left out in October to
150            * resynchronize the calendar with the earth's orbit. October 4th
151            * 1582 is followed by October 15th 1582. We use the "switch"
152            * table month1s for this year.
153            */
154           ndays = ndays - r;
155           if (idt.y == 1582)
156                     montht = month1s;
157           else
158                     montht = month1;
159 
160           for (idt.m = 11; montht[idt.m] > ndays; idt.m--)
161                     ;
162 
163           idt.d = ndays - montht[idt.m]; /* the rest is the day in month */
164 
165           /* Advance ten days deleted from October if after switch in Oct 1582 */
166           if (idt.y == jiswitch.y && idt.m == jiswitch.m && jiswitch.d < idt.d)
167                     idt.d += 10;
168 
169           /* return external representation of found date */
170           return (idt2date(dt, &idt));
171 }
172 
173 /*
174  * Return the number of days since March 1st of the year zero. The date is
175  * assumed Gregorian if younger than 1582-10-04 and Julian otherwise. This
176  * is the reverse of gdate.
177  */
178 int
ndaysg(date * dt)179 ndaysg(date *dt)
180 {
181           date    idt;                  /* Internal date representation */
182 
183           if (date2idt(&idt, dt) == NULL)
184                     return (-1);
185           return (ndaysgi(&idt));
186 }
187 
188 /*
189  * Same as above, but with the Gregorian date given in internal
190  * representation.
191  */
192 static int
ndaysgi(date * idt)193 ndaysgi(date *idt)
194 {
195           int     nd;                   /* Number of days--return value */
196 
197           /* Cache nswitch if not already done */
198           if (nswitch == 0)
199                     nswitch = ndaysji(&jiswitch);
200 
201           /*
202            * Assume Julian calendar and adapt to Gregorian if necessary, i. e.
203            * younger than nswitch. Gregori deleted
204            * the ten days from Oct 5th to Oct 14th 1582.
205            * Thereafter years which are multiples of 100 and not multiples
206            * of 400 were not leap years anymore.
207            * This makes the average length of a year
208            * 365d +.25d - .01d + .0025d = 365.2425d. But the tropical
209            * year measures 365.2422d. So in 10000/3 years we are
210            * again one day ahead of the earth. Sigh :-)
211            * (d is the average length of a day and tropical year is the
212            * time from one spring point to the next.)
213            */
214           if ((nd = ndaysji(idt)) == -1)
215                     return (-1);
216           if (idt->y >= 1600)
217                     nd = (nd - 10 - (idt->y - 1600) / 100 + (idt->y - 1600) / 400);
218           else if (nd > nswitch)
219                     nd -= 10;
220           return (nd);
221 }
222 
223 /*
224  * Compute the week number from the number of days since March 1st year 0.
225  * The weeks are numbered per year starting with 1. If the first
226  * week of a year includes at least four days of that year it is week 1,
227  * otherwise it gets the number of the last week of the previous year.
228  * The variable y will be filled with the year that contains the greater
229  * part of the week.
230  */
231 int
week(int nd,int * y)232 week(int nd, int *y)
233 {
234           date    dt;
235           int     fw;                   /* 1st day of week 1 of previous, this and
236                                          * next year */
237           gdate(nd, &dt);
238           for (*y = dt.y + 1; nd < (fw = firstweek(*y)); (*y)--)
239                     ;
240           return ((nd - fw) / 7 + 1);
241 }
242 
243 /* return the first day of week 1 of year y */
244 static int
firstweek(int y)245 firstweek(int y)
246 {
247           date idt;
248           int nd, wd;
249 
250           idt.y = y - 1;   /* internal representation of y-1-1 */
251           idt.m = 10;
252           idt.d = 0;
253 
254           nd = ndaysgi(&idt);
255           /*
256            * If more than 3 days of this week are in the preceding year, the
257            * next week is week 1 (and the next monday is the answer),
258            * otherwise this week is week 1 and the last monday is the
259            * answer.
260            */
261           if ((wd = weekday(nd)) > 3)
262                     return (nd - wd + 7);
263           else
264                     return (nd - wd);
265 }
266 
267 /* return the weekday (Mo = 0 .. Su = 6) */
268 int
weekday(int nd)269 weekday(int nd)
270 {
271           date dmondaygi = {1997, 8, 16}; /* Internal repr. of 1997-11-17 */
272           static int nmonday;             /* ... which is a monday        */
273 
274           /* Cache the daynumber of one monday */
275           if (nmonday == 0)
276                     nmonday = ndaysgi(&dmondaygi);
277 
278           /* return (nd - nmonday) modulo 7 which is the weekday */
279           nd = (nd - nmonday) % 7;
280           if (nd < 0)
281                     return (nd + 7);
282           else
283                     return (nd);
284 }
285 
286 /*
287  * Convert a date to internal date representation: The year starts on
288  * March 1st, month and day numbering start at zero. E. g. March 1st of
289  * year zero is written as y=0, m=0, d=0.
290  */
291 static date *
date2idt(date * idt,date * dt)292 date2idt(date *idt, date *dt)
293 {
294 
295           idt->d = dt->d - 1;
296           if (dt->m > 2) {
297                     idt->m = dt->m - 3;
298                     idt->y = dt->y;
299           } else {
300                     idt->m = dt->m + 9;
301                     idt->y = dt->y - 1;
302           }
303           if (idt->m < 0 || idt->m > 11 || idt->y < 0)
304                     return (NULL);
305           else
306                     return idt;
307 }
308 
309 /* Reverse of date2idt */
310 static date *
idt2date(date * dt,date * idt)311 idt2date(date *dt, date *idt)
312 {
313 
314           dt->d = idt->d + 1;
315           if (idt->m < 10) {
316                     dt->m = idt->m + 3;
317                     dt->y = idt->y;
318           } else {
319                     dt->m = idt->m - 9;
320                     dt->y = idt->y + 1;
321           }
322           if (dt->m < 1)
323                     return (NULL);
324           else
325                     return (dt);
326 }
327