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