xref: /dragonfly/usr.bin/calendar/days.c (revision e6d22e9b)
1 /*-
2  * SPDX-License-Identifier: BSD-3-Clause
3  *
4  * Copyright (c) 2020 The DragonFly Project.  All rights reserved.
5  *
6  * This code is derived from software contributed to The DragonFly Project
7  * by Aaron LI <aly@aaronly.me>
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  *
13  * 1. Redistributions of source code must retain the above copyright
14  *    notice, this list of conditions and the following disclaimer.
15  * 2. Redistributions in binary form must reproduce the above copyright
16  *    notice, this list of conditions and the following disclaimer in
17  *    the documentation and/or other materials provided with the
18  *    distribution.
19  * 3. Neither the name of The DragonFly Project nor the names of its
20  *    contributors may be used to endorse or promote products derived
21  *    from this software without specific, prior written permission.
22  *
23  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
27  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
29  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
31  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
32  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
33  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34  * SUCH DAMAGE.
35  */
36 
37 #include <assert.h>
38 #include <err.h>
39 #include <math.h>
40 #include <stddef.h>
41 
42 #include "calendar.h"
43 #include "basics.h"
44 #include "chinese.h"
45 #include "dates.h"
46 #include "days.h"
47 #include "ecclesiastical.h"
48 #include "gregorian.h"
49 #include "moon.h"
50 #include "nnames.h"
51 #include "parsedata.h"
52 #include "sun.h"
53 #include "utils.h"
54 
55 static int	find_days_yearly(int sday_id, int offset,
56 				 struct cal_day **dayp, char **edp);
57 static int	find_days_moon(int sday_id, int offset,
58 			       struct cal_day **dayp, char **edp);
59 
60 static int	find_days_easter(int, struct cal_day **, char **);
61 static int	find_days_paskha(int, struct cal_day **, char **);
62 static int	find_days_advent(int, struct cal_day **, char **);
63 static int	find_days_cny(int, struct cal_day **, char **);
64 static int	find_days_cqingming(int, struct cal_day **, char **);
65 static int	find_days_cjieqi(int, struct cal_day **, char **);
66 static int	find_days_marequinox(int, struct cal_day **, char **);
67 static int	find_days_sepequinox(int, struct cal_day **, char **);
68 static int	find_days_junsolstice(int, struct cal_day **, char **);
69 static int	find_days_decsolstice(int, struct cal_day **, char **);
70 static int	find_days_newmoon(int, struct cal_day **, char **);
71 static int	find_days_fullmoon(int, struct cal_day **, char **);
72 
73 #define SPECIALDAY_INIT0 \
74 	{ SD_NONE, NULL, 0, NULL, 0, NULL }
75 #define SPECIALDAY_INIT(id, name, func) \
76 	{ (id), name, sizeof(name)-1, NULL, 0, func }
77 struct specialday specialdays[] = {
78 	SPECIALDAY_INIT(SD_EASTER, "Easter", &find_days_easter),
79 	SPECIALDAY_INIT(SD_PASKHA, "Paskha", &find_days_paskha),
80 	SPECIALDAY_INIT(SD_ADVENT, "Advent", &find_days_advent),
81 	SPECIALDAY_INIT(SD_CNY, "ChineseNewYear", &find_days_cny),
82 	SPECIALDAY_INIT(SD_CQINGMING, "ChineseQingming", &find_days_cqingming),
83 	SPECIALDAY_INIT(SD_CJIEQI, "ChineseJieqi", &find_days_cjieqi),
84 	SPECIALDAY_INIT(SD_MAREQUINOX, "MarEquinox", &find_days_marequinox),
85 	SPECIALDAY_INIT(SD_SEPEQUINOX, "SepEquinox", &find_days_sepequinox),
86 	SPECIALDAY_INIT(SD_JUNSOLSTICE, "JunSolstice", &find_days_junsolstice),
87 	SPECIALDAY_INIT(SD_DECSOLSTICE, "DecSolstice", &find_days_decsolstice),
88 	SPECIALDAY_INIT(SD_NEWMOON, "NewMoon", &find_days_newmoon),
89 	SPECIALDAY_INIT(SD_FULLMOON, "FullMoon", &find_days_fullmoon),
90 	SPECIALDAY_INIT0,
91 };
92 
93 
94 static int
95 find_days_easter(int offset, struct cal_day **dayp, char **edp)
96 {
97 	return find_days_yearly(SD_EASTER, offset, dayp, edp);
98 }
99 
100 static int
101 find_days_paskha(int offset, struct cal_day **dayp, char **edp)
102 {
103 	return find_days_yearly(SD_PASKHA, offset, dayp, edp);
104 }
105 
106 static int
107 find_days_advent(int offset, struct cal_day **dayp, char **edp)
108 {
109 	return find_days_yearly(SD_ADVENT, offset, dayp, edp);
110 }
111 
112 static int
113 find_days_cny(int offset, struct cal_day **dayp, char **edp)
114 {
115 	return find_days_yearly(SD_CNY, offset, dayp, edp);
116 }
117 
118 static int
119 find_days_cqingming(int offset, struct cal_day **dayp, char **edp)
120 {
121 	return find_days_yearly(SD_CQINGMING, offset, dayp, edp);
122 }
123 
124 static int
125 find_days_marequinox(int offset, struct cal_day **dayp, char **edp)
126 {
127 	return find_days_yearly(SD_MAREQUINOX, offset, dayp, edp);
128 }
129 
130 static int
131 find_days_sepequinox(int offset, struct cal_day **dayp, char **edp)
132 {
133 	return find_days_yearly(SD_SEPEQUINOX, offset, dayp, edp);
134 }
135 
136 static int
137 find_days_junsolstice(int offset, struct cal_day **dayp, char **edp)
138 {
139 	return find_days_yearly(SD_JUNSOLSTICE, offset, dayp, edp);
140 }
141 
142 static int
143 find_days_decsolstice(int offset, struct cal_day **dayp, char **edp)
144 {
145 	return find_days_yearly(SD_DECSOLSTICE, offset, dayp, edp);
146 }
147 
148 /*
149  * Find days of the yearly special day specified by $sday_id.
150  */
151 static int
152 find_days_yearly(int sday_id, int offset, struct cal_day **dayp, char **edp)
153 {
154 	struct cal_day *dp;
155 	struct date date;
156 	double t, longitude;
157 	char buf[32];
158 	int rd, approx, month;
159 	int year1, year2;
160 	int count = 0;
161 
162 	year1 = gregorian_year_from_fixed(Options.day_begin);
163 	year2 = gregorian_year_from_fixed(Options.day_end);
164 	for (int y = year1; y <= year2; y++) {
165 		t = NAN;
166 
167 		switch (sday_id) {
168 		case SD_EASTER:
169 			rd = easter(y);
170 			break;
171 		case SD_PASKHA:
172 			rd = orthodox_easter(y);
173 			break;
174 		case SD_ADVENT:
175 			rd = advent(y);
176 			break;
177 		case SD_CNY:
178 			rd = chinese_new_year(y);
179 			break;
180 		case SD_CQINGMING:
181 			rd = chinese_qingming(y);
182 			break;
183 		case SD_MAREQUINOX:
184 		case SD_JUNSOLSTICE:
185 		case SD_SEPEQUINOX:
186 		case SD_DECSOLSTICE:
187 			if (sday_id == SD_MAREQUINOX) {
188 				month = 3;
189 				longitude = 0.0;
190 			} else if (sday_id == SD_JUNSOLSTICE) {
191 				month = 6;
192 				longitude = 90.0;
193 			} else if (sday_id == SD_SEPEQUINOX) {
194 				month = 9;
195 				longitude = 180.0;
196 			} else {
197 				month = 12;
198 				longitude = 270.0;
199 			}
200 			date_set(&date, y, month, 1);
201 			approx = fixed_from_gregorian(&date);
202 			t = solar_longitude_atafter(longitude, approx);
203 			t += Options.location->zone;  /* to standard time */
204 			rd = floor(t);
205 			break;
206 		default:
207 			errx(1, "%s: unknown special day: %d",
208 			     __func__, sday_id);
209 		}
210 
211 		if ((dp = find_rd(rd, offset)) != NULL) {
212 			if (count >= CAL_MAX_REPEAT) {
213 				warnx("%s: too many repeats", __func__);
214 				return count;
215 			}
216 			if (!isnan(t)) {
217 				format_time(buf, sizeof(buf), t);
218 				edp[count] = xstrdup(buf);
219 			}
220 			dayp[count++] = dp;
221 		}
222 	}
223 
224 	return count;
225 }
226 
227 /*
228  * Find days of the 24 Chinese Jiéqì (节气)
229  */
230 static int
231 find_days_cjieqi(int offset, struct cal_day **dayp, char **edp)
232 {
233 	const struct chinese_jieqi *jq;
234 	struct cal_day *dp;
235 	struct date date;
236 	char buf[32];
237 	int year1, year2;
238 	int rd, rd_begin, rd_end;
239 	int count = 0;
240 
241 	year1 = gregorian_year_from_fixed(Options.day_begin);
242 	year2 = gregorian_year_from_fixed(Options.day_end);
243 	for (int y = year1; y <= year2; y++) {
244 		date_set(&date, y, 1, 1);
245 		rd_begin = fixed_from_gregorian(&date);
246 		date.year++;
247 		rd_end = fixed_from_gregorian(&date);
248 		if (rd_end > Options.day_end)
249 			rd_end = Options.day_end;
250 
251 		for (rd = rd_begin; rd <= rd_end; rd += 14) {
252 			rd = chinese_jieqi_onafter(rd, C_JIEQI_ALL, &jq);
253 			if (rd > rd_end)
254 				break;
255 
256 			if ((dp = find_rd(rd, offset)) != NULL) {
257 				if (count >= CAL_MAX_REPEAT) {
258 					warnx("%s: too many repeats",
259 					      __func__);
260 					return count;
261 				}
262 				snprintf(buf, sizeof(buf), "%s, %s",
263 					 jq->name, jq->zhname);
264 				edp[count] = xstrdup(buf);
265 				dayp[count++] = dp;
266 			}
267 		}
268 	}
269 
270 	return count;
271 }
272 
273 static int
274 find_days_newmoon(int offset, struct cal_day **dayp, char **edp)
275 {
276 	return find_days_moon(SD_NEWMOON, offset, dayp, edp);
277 }
278 
279 static int
280 find_days_fullmoon(int offset, struct cal_day **dayp, char **edp)
281 {
282 	return find_days_moon(SD_FULLMOON, offset, dayp, edp);
283 }
284 
285 /*
286  * Find days of the moon events specified by $sday_id.
287  */
288 static int
289 find_days_moon(int sday_id, int offset, struct cal_day **dayp, char **edp)
290 {
291 	struct cal_day *dp;
292 	struct date date;
293 	double t, t_begin, t_end;
294 	char buf[32];
295 	int year1, year2;
296 	int count = 0;
297 
298 	year1 = gregorian_year_from_fixed(Options.day_begin);
299 	year2 = gregorian_year_from_fixed(Options.day_end);
300 	for (int y = year1; y <= year2; y++) {
301 		date_set(&date, y, 1, 1);
302 		t_begin = fixed_from_gregorian(&date) - Options.location->zone;
303 		date.year++;
304 		t_end = fixed_from_gregorian(&date) - Options.location->zone;
305 		if (t_end > Options.day_end + 1 - Options.location->zone)
306 			t_end = Options.day_end + 1 - Options.location->zone;
307 				/* NOTE: '+1' to include the ending day */
308 
309 		for (t = t_begin; t <= t_end; ) {
310 			switch (sday_id) {
311 			case SD_NEWMOON:
312 				t = new_moon_atafter(t);
313 				break;
314 			case SD_FULLMOON:
315 				t = lunar_phase_atafter(180, t);
316 				break;
317 			default:
318 				errx(1, "%s: unknown moon event: %d",
319 				     __func__, sday_id);
320 			}
321 
322 			if (t > t_end)
323 				break;
324 
325 			t += Options.location->zone;  /* to standard time */
326 			if ((dp = find_rd(floor(t), offset)) != NULL) {
327 				if (count >= CAL_MAX_REPEAT) {
328 					warnx("%s: too many repeats",
329 					      __func__);
330 					return count;
331 				}
332 				format_time(buf, sizeof(buf), t);
333 				edp[count] = xstrdup(buf);
334 				dayp[count++] = dp;
335 			}
336 		}
337 	}
338 
339 	return count;
340 }
341 
342 /**************************************************************************/
343 
344 /*
345  * Find days of the specified year ($year), month ($month) and day ($day).
346  * If year $year < 0, then year is ignored.
347  */
348 int
349 find_days_ymd(int year, int month, int day,
350 	      struct cal_day **dayp, char **edp __unused)
351 {
352 	struct cal_day *dp = NULL;
353 	int count = 0;
354 
355 	while ((dp = loop_dates(dp)) != NULL) {
356 		if (year >= 0 && year != dp->year)
357 			continue;
358 		if ((dp->month == month && dp->day == day) ||
359 		    /* day of zero means the last day of previous month */
360 		    (day == 0 && dp->last_dom && month == dp->month % 12 + 1)) {
361 			if (count >= CAL_MAX_REPEAT) {
362 				warnx("%s: too many repeats", __func__);
363 				return count;
364 			}
365 			dayp[count++] = dp;
366 		}
367 	}
368 
369 	return count;
370 }
371 
372 /*
373  * Find days of the specified day of month ($dom) of all months.
374  */
375 int
376 find_days_dom(int dom, struct cal_day **dayp, char **edp __unused)
377 {
378 	struct cal_day *dp = NULL;
379 	int count = 0;
380 
381 	while ((dp = loop_dates(dp)) != NULL) {
382 		if (dp->day == dom ||
383 		    /* day of zero means the last day of previous month */
384 		    (dom == 0 && dp->last_dom)) {
385 			if (count >= CAL_MAX_REPEAT) {
386 				warnx("%s: too many repeats", __func__);
387 				return count;
388 			}
389 			dayp[count++] = dp;
390 		}
391 	}
392 
393 	return count;
394 }
395 
396 /*
397  * Find days of all days of the specified month ($month).
398  */
399 int
400 find_days_month(int month, struct cal_day **dayp, char **edp __unused)
401 {
402 	struct cal_day *dp = NULL;
403 	int count = 0;
404 
405 	while ((dp = loop_dates(dp)) != NULL) {
406 		if (dp->month == month) {
407 			if (count >= CAL_MAX_REPEAT) {
408 				warnx("%s: too many repeats", __func__);
409 				return count;
410 			}
411 			dayp[count++] = dp;
412 		}
413 	}
414 
415 	return count;
416 }
417 
418 /*
419  * If $index == 0, find days of every day-of-week ($dow) of the specified month
420  * ($month).  Otherwise, find days of the indexed day-of-week of the month.
421  * If month $month < 0, then find days in every month.
422  */
423 int
424 find_days_mdow(int month, int dow, int index,
425 	       struct cal_day **dayp, char **edp __unused)
426 {
427 	struct cal_day *dp = NULL;
428 	int count = 0;
429 
430 	while ((dp = loop_dates(dp)) != NULL) {
431 		if (month >= 0 && month != dp->month)
432 			continue;
433 		if (dp->dow[0] == dow) {
434 			if (index != 0 &&
435 			    (index != dp->dow[1] && index != dp->dow[2])) {
436 				/* Not the indexed day-of-week of month */
437 				continue;
438 			}
439 			if (count >= CAL_MAX_REPEAT) {
440 				warnx("%s: too many repeats", __func__);
441 				return count;
442 			}
443 			dayp[count++] = dp;
444 		}
445 	}
446 
447 	return count;
448 }
449