xref: /freebsd/lib/libcalendar/calendar.c (revision 1d386b48a555f61cb7325543adbbb5c3f3407a66)
1306a501fSWolfgang Helbig /*-
2*4d846d26SWarner Losh  * SPDX-License-Identifier: BSD-2-Clause
35e53a4f9SPedro F. Giffuni  *
4306a501fSWolfgang Helbig  * Copyright (c) 1997 Wolfgang Helbig
5306a501fSWolfgang Helbig  * All rights reserved.
6306a501fSWolfgang Helbig  *
7306a501fSWolfgang Helbig  * Redistribution and use in source and binary forms, with or without
8306a501fSWolfgang Helbig  * modification, are permitted provided that the following conditions
9306a501fSWolfgang Helbig  * are met:
10306a501fSWolfgang Helbig  * 1. Redistributions of source code must retain the above copyright
11306a501fSWolfgang Helbig  *    notice, this list of conditions and the following disclaimer.
12306a501fSWolfgang Helbig  * 2. Redistributions in binary form must reproduce the above copyright
13306a501fSWolfgang Helbig  *    notice, this list of conditions and the following disclaimer in the
14306a501fSWolfgang Helbig  *    documentation and/or other materials provided with the distribution.
15306a501fSWolfgang Helbig  *
16306a501fSWolfgang Helbig  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
17306a501fSWolfgang Helbig  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18306a501fSWolfgang Helbig  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
19306a501fSWolfgang Helbig  * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
20306a501fSWolfgang Helbig  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
21306a501fSWolfgang Helbig  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
22306a501fSWolfgang Helbig  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
23306a501fSWolfgang Helbig  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
24306a501fSWolfgang Helbig  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
25306a501fSWolfgang Helbig  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
26306a501fSWolfgang Helbig  * SUCH DAMAGE.
27306a501fSWolfgang Helbig  */
28306a501fSWolfgang Helbig 
29e9dcdd2fSMatthew Dillon #include <sys/cdefs.h>
30306a501fSWolfgang Helbig #include "calendar.h"
31306a501fSWolfgang Helbig 
32306a501fSWolfgang Helbig #ifndef NULL
33306a501fSWolfgang Helbig #define NULL 0
34306a501fSWolfgang Helbig #endif
35306a501fSWolfgang Helbig 
36306a501fSWolfgang Helbig /*
37306a501fSWolfgang Helbig  * For each month tabulate the number of days elapsed in a year before the
38306a501fSWolfgang Helbig  * month. This assumes the internal date representation, where a year
39306a501fSWolfgang Helbig  * starts on March 1st. So we don't need a special table for leap years.
40306a501fSWolfgang Helbig  * But we do need a special table for the year 1582, since 10 days are
41306a501fSWolfgang Helbig  * deleted in October. This is month1s for the switch from Julian to
42306a501fSWolfgang Helbig  * Gregorian calendar.
43306a501fSWolfgang Helbig  */
44306a501fSWolfgang Helbig static int const month1[] =
45306a501fSWolfgang Helbig     {0, 31, 61, 92, 122, 153, 184, 214, 245, 275, 306, 337};
46306a501fSWolfgang Helbig    /*  M   A   M   J    J    A    S    O    N    D    J */
47306a501fSWolfgang Helbig static int const month1s[]=
48306a501fSWolfgang Helbig     {0, 31, 61, 92, 122, 153, 184, 214, 235, 265, 296, 327};
49306a501fSWolfgang Helbig 
50fa183770SWolfgang Helbig typedef struct date date;
51fa183770SWolfgang Helbig 
52306a501fSWolfgang Helbig /* The last day of Julian calendar, in internal and ndays representation */
53306a501fSWolfgang Helbig static int nswitch;	/* The last day of Julian calendar */
54306a501fSWolfgang Helbig static date jiswitch = {1582, 7, 3};
55306a501fSWolfgang Helbig 
56306a501fSWolfgang Helbig static date	*date2idt(date *idt, date *dt);
57306a501fSWolfgang Helbig static date	*idt2date(date *dt, date *idt);
58306a501fSWolfgang Helbig static int	 ndaysji(date *idt);
59306a501fSWolfgang Helbig static int	 ndaysgi(date *idt);
60306a501fSWolfgang Helbig static int	 firstweek(int year);
61306a501fSWolfgang Helbig 
62306a501fSWolfgang Helbig /*
63306a501fSWolfgang Helbig  * Compute the Julian date from the number of days elapsed since
64306a501fSWolfgang Helbig  * March 1st of year zero.
65306a501fSWolfgang Helbig  */
66306a501fSWolfgang Helbig date *
jdate(int ndays,date * dt)67306a501fSWolfgang Helbig jdate(int ndays, date *dt)
68306a501fSWolfgang Helbig {
69306a501fSWolfgang Helbig 	date    idt;		/* Internal date representation */
70306a501fSWolfgang Helbig 	int     r;		/* hold the rest of days */
71306a501fSWolfgang Helbig 
72306a501fSWolfgang Helbig 	/*
73306a501fSWolfgang Helbig 	 * Compute the year by starting with an approximation not smaller
74306a501fSWolfgang Helbig 	 * than the answer and using linear search for the greatest
75306a501fSWolfgang Helbig 	 * year which does not begin after ndays.
76306a501fSWolfgang Helbig 	 */
77306a501fSWolfgang Helbig 	idt.y = ndays / 365;
78306a501fSWolfgang Helbig 	idt.m = 0;
79306a501fSWolfgang Helbig 	idt.d = 0;
80306a501fSWolfgang Helbig 	while ((r = ndaysji(&idt)) > ndays)
81306a501fSWolfgang Helbig 		idt.y--;
82306a501fSWolfgang Helbig 
83306a501fSWolfgang Helbig 	/*
84306a501fSWolfgang Helbig 	 * Set r to the days left in the year and compute the month by
85306a501fSWolfgang Helbig 	 * linear search as the largest month that does not begin after r
86306a501fSWolfgang Helbig 	 * days.
87306a501fSWolfgang Helbig 	 */
88306a501fSWolfgang Helbig 	r = ndays - r;
89306a501fSWolfgang Helbig 	for (idt.m = 11; month1[idt.m] > r; idt.m--)
90306a501fSWolfgang Helbig 		;
91306a501fSWolfgang Helbig 
92306a501fSWolfgang Helbig 	/* Compute the days left in the month */
93306a501fSWolfgang Helbig 	idt.d = r - month1[idt.m];
94306a501fSWolfgang Helbig 
95306a501fSWolfgang Helbig 	/* return external representation of the date */
96306a501fSWolfgang Helbig 	return (idt2date(dt, &idt));
97306a501fSWolfgang Helbig }
98306a501fSWolfgang Helbig 
99306a501fSWolfgang Helbig /*
100306a501fSWolfgang Helbig  * Return the number of days since March 1st of the year zero.
101306a501fSWolfgang Helbig  * The date is given according to Julian calendar.
102306a501fSWolfgang Helbig  */
103306a501fSWolfgang Helbig int
ndaysj(date * dt)104306a501fSWolfgang Helbig ndaysj(date *dt)
105306a501fSWolfgang Helbig {
106306a501fSWolfgang Helbig 	date    idt;		/* Internal date representation */
107306a501fSWolfgang Helbig 
108306a501fSWolfgang Helbig 	if (date2idt(&idt, dt) == NULL)
109306a501fSWolfgang Helbig 		return (-1);
110306a501fSWolfgang Helbig 	else
111306a501fSWolfgang Helbig 		return (ndaysji(&idt));
112306a501fSWolfgang Helbig }
113306a501fSWolfgang Helbig 
114306a501fSWolfgang Helbig /*
115306a501fSWolfgang Helbig  * Same as above, where the Julian date is given in internal notation.
116306a501fSWolfgang Helbig  * This formula shows the beauty of this notation.
117306a501fSWolfgang Helbig  */
118306a501fSWolfgang Helbig static int
ndaysji(date * idt)119306a501fSWolfgang Helbig ndaysji(date * idt)
120306a501fSWolfgang Helbig {
121306a501fSWolfgang Helbig 
122306a501fSWolfgang Helbig 	return (idt->d + month1[idt->m] + idt->y * 365 + idt->y / 4);
123306a501fSWolfgang Helbig }
124306a501fSWolfgang Helbig 
125306a501fSWolfgang Helbig /*
126306a501fSWolfgang Helbig  * Compute the date according to the Gregorian calendar from the number of
127306a501fSWolfgang Helbig  * days since March 1st, year zero. The date computed will be Julian if it
128306a501fSWolfgang Helbig  * is older than 1582-10-05. This is the reverse of the function ndaysg().
129306a501fSWolfgang Helbig  */
130306a501fSWolfgang Helbig date   *
gdate(int ndays,date * dt)131306a501fSWolfgang Helbig gdate(int ndays, date *dt)
132306a501fSWolfgang Helbig {
133306a501fSWolfgang Helbig 	int const *montht;	/* month-table */
134306a501fSWolfgang Helbig 	date    idt;		/* for internal date representation */
135306a501fSWolfgang Helbig 	int     r;		/* holds the rest of days */
136306a501fSWolfgang Helbig 
137306a501fSWolfgang Helbig 	/*
138306a501fSWolfgang Helbig 	 * Compute the year by starting with an approximation not smaller
139306a501fSWolfgang Helbig 	 * than the answer and search linearly for the greatest year not
140306a501fSWolfgang Helbig 	 * starting after ndays.
141306a501fSWolfgang Helbig 	 */
142306a501fSWolfgang Helbig 	idt.y = ndays / 365;
143306a501fSWolfgang Helbig 	idt.m = 0;
144306a501fSWolfgang Helbig 	idt.d = 0;
145306a501fSWolfgang Helbig 	while ((r = ndaysgi(&idt)) > ndays)
146306a501fSWolfgang Helbig 		idt.y--;
147306a501fSWolfgang Helbig 
148306a501fSWolfgang Helbig 	/*
149306a501fSWolfgang Helbig 	 * Set ndays to the number of days left and compute by linear
150306a501fSWolfgang Helbig 	 * search the greatest month which does not start after ndays. We
151306a501fSWolfgang Helbig 	 * use the table month1 which provides for each month the number
152306a501fSWolfgang Helbig 	 * of days that elapsed in the year before that month. Here the
153306a501fSWolfgang Helbig 	 * year 1582 is special, as 10 days are left out in October to
154306a501fSWolfgang Helbig 	 * resynchronize the calendar with the earth's orbit. October 4th
155306a501fSWolfgang Helbig 	 * 1582 is followed by October 15th 1582. We use the "switch"
156306a501fSWolfgang Helbig 	 * table month1s for this year.
157306a501fSWolfgang Helbig 	 */
158306a501fSWolfgang Helbig 	ndays = ndays - r;
159306a501fSWolfgang Helbig 	if (idt.y == 1582)
160306a501fSWolfgang Helbig 		montht = month1s;
161306a501fSWolfgang Helbig 	else
162306a501fSWolfgang Helbig 		montht = month1;
163306a501fSWolfgang Helbig 
164306a501fSWolfgang Helbig 	for (idt.m = 11; montht[idt.m] > ndays; idt.m--)
165306a501fSWolfgang Helbig 		;
166306a501fSWolfgang Helbig 
167306a501fSWolfgang Helbig 	idt.d = ndays - montht[idt.m]; /* the rest is the day in month */
168306a501fSWolfgang Helbig 
169306a501fSWolfgang Helbig 	/* Advance ten days deleted from October if after switch in Oct 1582 */
170306a501fSWolfgang Helbig 	if (idt.y == jiswitch.y && idt.m == jiswitch.m && jiswitch.d < idt.d)
171306a501fSWolfgang Helbig 		idt.d += 10;
172306a501fSWolfgang Helbig 
173306a501fSWolfgang Helbig 	/* return external representation of found date */
174306a501fSWolfgang Helbig 	return (idt2date(dt, &idt));
175306a501fSWolfgang Helbig }
176306a501fSWolfgang Helbig 
177306a501fSWolfgang Helbig /*
178306a501fSWolfgang Helbig  * Return the number of days since March 1st of the year zero. The date is
179306a501fSWolfgang Helbig  * assumed Gregorian if younger than 1582-10-04 and Julian otherwise. This
180306a501fSWolfgang Helbig  * is the reverse of gdate.
181306a501fSWolfgang Helbig  */
182306a501fSWolfgang Helbig int
ndaysg(date * dt)183306a501fSWolfgang Helbig ndaysg(date *dt)
184306a501fSWolfgang Helbig {
185306a501fSWolfgang Helbig 	date    idt;		/* Internal date representation */
186306a501fSWolfgang Helbig 
187306a501fSWolfgang Helbig 	if (date2idt(&idt, dt) == NULL)
188306a501fSWolfgang Helbig 		return (-1);
189306a501fSWolfgang Helbig 	return (ndaysgi(&idt));
190306a501fSWolfgang Helbig }
191306a501fSWolfgang Helbig 
192306a501fSWolfgang Helbig /*
193306a501fSWolfgang Helbig  * Same as above, but with the Gregorian date given in internal
194306a501fSWolfgang Helbig  * representation.
195306a501fSWolfgang Helbig  */
196306a501fSWolfgang Helbig static int
ndaysgi(date * idt)197306a501fSWolfgang Helbig ndaysgi(date *idt)
198306a501fSWolfgang Helbig {
199306a501fSWolfgang Helbig 	int     nd;		/* Number of days--return value */
200306a501fSWolfgang Helbig 
201306a501fSWolfgang Helbig 	/* Cache nswitch if not already done */
202306a501fSWolfgang Helbig 	if (nswitch == 0)
203306a501fSWolfgang Helbig 		nswitch = ndaysji(&jiswitch);
204306a501fSWolfgang Helbig 
205306a501fSWolfgang Helbig 	/*
206306a501fSWolfgang Helbig 	 * Assume Julian calendar and adapt to Gregorian if necessary, i. e.
207306a501fSWolfgang Helbig 	 * younger than nswitch. Gregori deleted
208306a501fSWolfgang Helbig 	 * the ten days from Oct 5th to Oct 14th 1582.
209306a501fSWolfgang Helbig 	 * Thereafter years which are multiples of 100 and not multiples
210306a501fSWolfgang Helbig 	 * of 400 were not leap years anymore.
211306a501fSWolfgang Helbig 	 * This makes the average length of a year
212306a501fSWolfgang Helbig 	 * 365d +.25d - .01d + .0025d = 365.2425d. But the tropical
213306a501fSWolfgang Helbig 	 * year measures 365.2422d. So in 10000/3 years we are
214306a501fSWolfgang Helbig 	 * again one day ahead of the earth. Sigh :-)
215306a501fSWolfgang Helbig 	 * (d is the average length of a day and tropical year is the
216306a501fSWolfgang Helbig 	 * time from one spring point to the next.)
217306a501fSWolfgang Helbig 	 */
218306a501fSWolfgang Helbig 	if ((nd = ndaysji(idt)) == -1)
219306a501fSWolfgang Helbig 		return (-1);
220306a501fSWolfgang Helbig 	if (idt->y >= 1600)
221306a501fSWolfgang Helbig 		nd = (nd - 10 - (idt->y - 1600) / 100 + (idt->y - 1600) / 400);
222306a501fSWolfgang Helbig 	else if (nd > nswitch)
223306a501fSWolfgang Helbig 		nd -= 10;
224306a501fSWolfgang Helbig 	return (nd);
225306a501fSWolfgang Helbig }
226306a501fSWolfgang Helbig 
227306a501fSWolfgang Helbig /*
228306a501fSWolfgang Helbig  * Compute the week number from the number of days since March 1st year 0.
229306a501fSWolfgang Helbig  * The weeks are numbered per year starting with 1. If the first
230306a501fSWolfgang Helbig  * week of a year includes at least four days of that year it is week 1,
231306a501fSWolfgang Helbig  * otherwise it gets the number of the last week of the previous year.
232306a501fSWolfgang Helbig  * The variable y will be filled with the year that contains the greater
233306a501fSWolfgang Helbig  * part of the week.
234306a501fSWolfgang Helbig  */
235306a501fSWolfgang Helbig int
week(int nd,int * y)236306a501fSWolfgang Helbig week(int nd, int *y)
237306a501fSWolfgang Helbig {
238306a501fSWolfgang Helbig 	date    dt;
239306a501fSWolfgang Helbig 	int     fw;		/* 1st day of week 1 of previous, this and
240306a501fSWolfgang Helbig 				 * next year */
241306a501fSWolfgang Helbig 	gdate(nd, &dt);
242306a501fSWolfgang Helbig 	for (*y = dt.y + 1; nd < (fw = firstweek(*y)); (*y)--)
243306a501fSWolfgang Helbig 		;
244306a501fSWolfgang Helbig 	return ((nd - fw) / 7 + 1);
245306a501fSWolfgang Helbig }
246306a501fSWolfgang Helbig 
247306a501fSWolfgang Helbig /* return the first day of week 1 of year y */
248306a501fSWolfgang Helbig static int
firstweek(int y)249306a501fSWolfgang Helbig firstweek(int y)
250306a501fSWolfgang Helbig {
251306a501fSWolfgang Helbig 	date idt;
252306a501fSWolfgang Helbig 	int nd, wd;
253306a501fSWolfgang Helbig 
254306a501fSWolfgang Helbig 	idt.y = y - 1;   /* internal representation of y-1-1 */
255306a501fSWolfgang Helbig 	idt.m = 10;
256306a501fSWolfgang Helbig 	idt.d = 0;
257306a501fSWolfgang Helbig 
258306a501fSWolfgang Helbig 	nd = ndaysgi(&idt);
259306a501fSWolfgang Helbig 	/*
260306a501fSWolfgang Helbig 	 * If more than 3 days of this week are in the preceding year, the
261306a501fSWolfgang Helbig 	 * next week is week 1 (and the next monday is the answer),
262306a501fSWolfgang Helbig 	 * otherwise this week is week 1 and the last monday is the
263306a501fSWolfgang Helbig 	 * answer.
264306a501fSWolfgang Helbig 	 */
265306a501fSWolfgang Helbig 	if ((wd = weekday(nd)) > 3)
266306a501fSWolfgang Helbig 		return (nd - wd + 7);
267306a501fSWolfgang Helbig 	else
268306a501fSWolfgang Helbig 		return (nd - wd);
269306a501fSWolfgang Helbig }
270306a501fSWolfgang Helbig 
271306a501fSWolfgang Helbig /* return the weekday (Mo = 0 .. Su = 6) */
272306a501fSWolfgang Helbig int
weekday(int nd)273306a501fSWolfgang Helbig weekday(int nd)
274306a501fSWolfgang Helbig {
275306a501fSWolfgang Helbig 	date dmondaygi = {1997, 8, 16}; /* Internal repr. of 1997-11-17 */
276306a501fSWolfgang Helbig 	static int nmonday;             /* ... which is a monday        */
277306a501fSWolfgang Helbig 
278306a501fSWolfgang Helbig 	/* Cache the daynumber of one monday */
279306a501fSWolfgang Helbig 	if (nmonday == 0)
280306a501fSWolfgang Helbig 		nmonday = ndaysgi(&dmondaygi);
281306a501fSWolfgang Helbig 
282306a501fSWolfgang Helbig 	/* return (nd - nmonday) modulo 7 which is the weekday */
283306a501fSWolfgang Helbig 	nd = (nd - nmonday) % 7;
284306a501fSWolfgang Helbig 	if (nd < 0)
285306a501fSWolfgang Helbig 		return (nd + 7);
286306a501fSWolfgang Helbig 	else
287306a501fSWolfgang Helbig 		return (nd);
288306a501fSWolfgang Helbig }
289306a501fSWolfgang Helbig 
290306a501fSWolfgang Helbig /*
291306a501fSWolfgang Helbig  * Convert a date to internal date representation: The year starts on
292306a501fSWolfgang Helbig  * March 1st, month and day numbering start at zero. E. g. March 1st of
293306a501fSWolfgang Helbig  * year zero is written as y=0, m=0, d=0.
294306a501fSWolfgang Helbig  */
295306a501fSWolfgang Helbig static date *
date2idt(date * idt,date * dt)296306a501fSWolfgang Helbig date2idt(date *idt, date *dt)
297306a501fSWolfgang Helbig {
298306a501fSWolfgang Helbig 
299306a501fSWolfgang Helbig 	idt->d = dt->d - 1;
300306a501fSWolfgang Helbig 	if (dt->m > 2) {
301306a501fSWolfgang Helbig 		idt->m = dt->m - 3;
302306a501fSWolfgang Helbig 		idt->y = dt->y;
303306a501fSWolfgang Helbig 	} else {
304306a501fSWolfgang Helbig 		idt->m = dt->m + 9;
305306a501fSWolfgang Helbig 		idt->y = dt->y - 1;
306306a501fSWolfgang Helbig 	}
307306a501fSWolfgang Helbig 	if (idt->m < 0 || idt->m > 11 || idt->y < 0)
308306a501fSWolfgang Helbig 		return (NULL);
309306a501fSWolfgang Helbig 	else
310306a501fSWolfgang Helbig 		return idt;
311306a501fSWolfgang Helbig }
312306a501fSWolfgang Helbig 
313306a501fSWolfgang Helbig /* Reverse of date2idt */
314306a501fSWolfgang Helbig static date *
idt2date(date * dt,date * idt)315306a501fSWolfgang Helbig idt2date(date *dt, date *idt)
316306a501fSWolfgang Helbig {
317306a501fSWolfgang Helbig 
318306a501fSWolfgang Helbig 	dt->d = idt->d + 1;
319306a501fSWolfgang Helbig 	if (idt->m < 10) {
320306a501fSWolfgang Helbig 		dt->m = idt->m + 3;
321306a501fSWolfgang Helbig 		dt->y = idt->y;
322306a501fSWolfgang Helbig 	} else {
323306a501fSWolfgang Helbig 		dt->m = idt->m - 9;
324306a501fSWolfgang Helbig 		dt->y = idt->y + 1;
325306a501fSWolfgang Helbig 	}
326306a501fSWolfgang Helbig 	if (dt->m < 1)
327306a501fSWolfgang Helbig 		return (NULL);
328306a501fSWolfgang Helbig 	else
329306a501fSWolfgang Helbig 		return (dt);
330306a501fSWolfgang Helbig }
331