xref: /dflybsd-src/usr.bin/calendar/julian.c (revision d19ef5a274debcb71f2e8cd8dce8b954dc73944b)
1*d19ef5a2SAaron LI /*-
2*d19ef5a2SAaron LI  * SPDX-License-Identifier: BSD-3-Clause
3*d19ef5a2SAaron LI  *
4*d19ef5a2SAaron LI  * Copyright (c) 2020 The DragonFly Project.  All rights reserved.
5*d19ef5a2SAaron LI  *
6*d19ef5a2SAaron LI  * This code is derived from software contributed to The DragonFly Project
7*d19ef5a2SAaron LI  * by Aaron LI <aly@aaronly.me>
8*d19ef5a2SAaron LI  *
9*d19ef5a2SAaron LI  * Redistribution and use in source and binary forms, with or without
10*d19ef5a2SAaron LI  * modification, are permitted provided that the following conditions
11*d19ef5a2SAaron LI  * are met:
12*d19ef5a2SAaron LI  *
13*d19ef5a2SAaron LI  * 1. Redistributions of source code must retain the above copyright
14*d19ef5a2SAaron LI  *    notice, this list of conditions and the following disclaimer.
15*d19ef5a2SAaron LI  * 2. Redistributions in binary form must reproduce the above copyright
16*d19ef5a2SAaron LI  *    notice, this list of conditions and the following disclaimer in
17*d19ef5a2SAaron LI  *    the documentation and/or other materials provided with the
18*d19ef5a2SAaron LI  *    distribution.
19*d19ef5a2SAaron LI  * 3. Neither the name of The DragonFly Project nor the names of its
20*d19ef5a2SAaron LI  *    contributors may be used to endorse or promote products derived
21*d19ef5a2SAaron LI  *    from this software without specific, prior written permission.
22*d19ef5a2SAaron LI  *
23*d19ef5a2SAaron LI  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24*d19ef5a2SAaron LI  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25*d19ef5a2SAaron LI  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26*d19ef5a2SAaron LI  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
27*d19ef5a2SAaron LI  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28*d19ef5a2SAaron LI  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
29*d19ef5a2SAaron LI  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30*d19ef5a2SAaron LI  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
31*d19ef5a2SAaron LI  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
32*d19ef5a2SAaron LI  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
33*d19ef5a2SAaron LI  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
34*d19ef5a2SAaron LI  * SUCH DAMAGE.
35*d19ef5a2SAaron LI  *
36*d19ef5a2SAaron LI  * Reference:
37*d19ef5a2SAaron LI  * Calendrical Calculations, The Ultimate Edition (4th Edition)
38*d19ef5a2SAaron LI  * by Edward M. Reingold and Nachum Dershowitz
39*d19ef5a2SAaron LI  * 2018, Cambridge University Press
40*d19ef5a2SAaron LI  */
41*d19ef5a2SAaron LI 
42*d19ef5a2SAaron LI #include <stdbool.h>
43*d19ef5a2SAaron LI #include <stdio.h>
44*d19ef5a2SAaron LI 
45*d19ef5a2SAaron LI #include "calendar.h"
46*d19ef5a2SAaron LI #include "basics.h"
47*d19ef5a2SAaron LI #include "dates.h"
48*d19ef5a2SAaron LI #include "gregorian.h"
49*d19ef5a2SAaron LI #include "julian.h"
50*d19ef5a2SAaron LI #include "utils.h"
51*d19ef5a2SAaron LI 
52*d19ef5a2SAaron LI /*
53*d19ef5a2SAaron LI  * Fixed date of the start of the Julian calendar.
54*d19ef5a2SAaron LI  * Ref: Sec.(3.1), Eq.(3.2)
55*d19ef5a2SAaron LI  */
56*d19ef5a2SAaron LI static const int epoch = -1;  /* Gregorian: 0, December, 30 */
57*d19ef5a2SAaron LI 
58*d19ef5a2SAaron LI /*
59*d19ef5a2SAaron LI  * Return true if $year is a leap year on the Julian calendar,
60*d19ef5a2SAaron LI  * otherwise return false.
61*d19ef5a2SAaron LI  * Ref: Sec.(3.1), Eq.(3.1)
62*d19ef5a2SAaron LI  */
63*d19ef5a2SAaron LI bool
julian_leap_year(int year)64*d19ef5a2SAaron LI julian_leap_year(int year)
65*d19ef5a2SAaron LI {
66*d19ef5a2SAaron LI 	int i = (year > 0) ? 0 : 3;
67*d19ef5a2SAaron LI 	return (mod(year, 4) == i);
68*d19ef5a2SAaron LI }
69*d19ef5a2SAaron LI 
70*d19ef5a2SAaron LI /*
71*d19ef5a2SAaron LI  * Calculate the fixed date (RD) equivalent to the Julian date $date.
72*d19ef5a2SAaron LI  * Ref: Sec.(3.1), Eq.(3.3)
73*d19ef5a2SAaron LI  */
74*d19ef5a2SAaron LI int
fixed_from_julian(const struct date * date)75*d19ef5a2SAaron LI fixed_from_julian(const struct date *date)
76*d19ef5a2SAaron LI {
77*d19ef5a2SAaron LI 	int y = (date->year >= 0) ? date->year : (date->year + 1);
78*d19ef5a2SAaron LI 	int rd = ((epoch - 1) + 365 * (y - 1) +
79*d19ef5a2SAaron LI 		  div_floor(y - 1, 4) +
80*d19ef5a2SAaron LI 		  div_floor(date->month * 367 - 362, 12));
81*d19ef5a2SAaron LI 	/* correct for the assumption that February always has 30 days */
82*d19ef5a2SAaron LI 	if (date->month <= 2)
83*d19ef5a2SAaron LI 		return rd + date->day;
84*d19ef5a2SAaron LI 	else if (julian_leap_year(date->year))
85*d19ef5a2SAaron LI 		return rd + date->day - 1;
86*d19ef5a2SAaron LI 	else
87*d19ef5a2SAaron LI 		return rd + date->day - 2;
88*d19ef5a2SAaron LI }
89*d19ef5a2SAaron LI 
90*d19ef5a2SAaron LI /*
91*d19ef5a2SAaron LI  * Calculate the Julian date (year, month, day) corresponding to the
92*d19ef5a2SAaron LI  * fixed date $rd.
93*d19ef5a2SAaron LI  * Ref: Sec.(3.1), Eq.(3.4)
94*d19ef5a2SAaron LI  */
95*d19ef5a2SAaron LI void
julian_from_fixed(int rd,struct date * date)96*d19ef5a2SAaron LI julian_from_fixed(int rd, struct date *date)
97*d19ef5a2SAaron LI {
98*d19ef5a2SAaron LI 	int correction, pdays;
99*d19ef5a2SAaron LI 
100*d19ef5a2SAaron LI 	date->year = div_floor(4 * (rd - epoch) + 1464, 1461);
101*d19ef5a2SAaron LI 	if (date->year <= 0)
102*d19ef5a2SAaron LI 		date->year--;
103*d19ef5a2SAaron LI 
104*d19ef5a2SAaron LI 	struct date d = { date->year, 3, 1 };
105*d19ef5a2SAaron LI 	if (rd < fixed_from_julian(&d))
106*d19ef5a2SAaron LI 		correction = 0;
107*d19ef5a2SAaron LI 	else if (julian_leap_year(date->year))
108*d19ef5a2SAaron LI 		correction = 1;
109*d19ef5a2SAaron LI 	else
110*d19ef5a2SAaron LI 		correction = 2;
111*d19ef5a2SAaron LI 
112*d19ef5a2SAaron LI 	d.month = 1;
113*d19ef5a2SAaron LI 	pdays = rd - fixed_from_julian(&d);
114*d19ef5a2SAaron LI 	date->month = div_floor(12 * (pdays + correction) + 373, 367);
115*d19ef5a2SAaron LI 
116*d19ef5a2SAaron LI 	d.month = date->month;
117*d19ef5a2SAaron LI 	date->day = rd - fixed_from_julian(&d) + 1;
118*d19ef5a2SAaron LI }
119*d19ef5a2SAaron LI 
120*d19ef5a2SAaron LI /**************************************************************************/
121*d19ef5a2SAaron LI 
122*d19ef5a2SAaron LI /*
123*d19ef5a2SAaron LI  * Format the given fixed date $rd to '<month>/<day>' string in $buf.
124*d19ef5a2SAaron LI  * Return the formatted string length.
125*d19ef5a2SAaron LI  */
126*d19ef5a2SAaron LI int
julian_format_date(char * buf,size_t size,int rd)127*d19ef5a2SAaron LI julian_format_date(char *buf, size_t size, int rd)
128*d19ef5a2SAaron LI {
129*d19ef5a2SAaron LI 	static const char *month_names[] = {
130*d19ef5a2SAaron LI 		"Jan", "Feb", "Mar", "Apr", "May", "Jun",
131*d19ef5a2SAaron LI 		"Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
132*d19ef5a2SAaron LI 	};
133*d19ef5a2SAaron LI 	struct date jdate;
134*d19ef5a2SAaron LI 
135*d19ef5a2SAaron LI 	julian_from_fixed(rd, &jdate);
136*d19ef5a2SAaron LI 	return snprintf(buf, size, "%s/%02d",
137*d19ef5a2SAaron LI 			month_names[jdate.month - 1], jdate.day);
138*d19ef5a2SAaron LI }
139*d19ef5a2SAaron LI 
140*d19ef5a2SAaron LI /*
141*d19ef5a2SAaron LI  * Calculate the Julian year corresponding to the fixed date $rd.
142*d19ef5a2SAaron LI  */
143*d19ef5a2SAaron LI static int
julian_year_from_fixed(int rd)144*d19ef5a2SAaron LI julian_year_from_fixed(int rd)
145*d19ef5a2SAaron LI {
146*d19ef5a2SAaron LI 	struct date jdate;
147*d19ef5a2SAaron LI 	julian_from_fixed(rd, &jdate);
148*d19ef5a2SAaron LI 	return jdate.year;
149*d19ef5a2SAaron LI }
150*d19ef5a2SAaron LI 
151*d19ef5a2SAaron LI /*
152*d19ef5a2SAaron LI  * Find days of the specified Julian year ($year), month ($month) and
153*d19ef5a2SAaron LI  * day ($day).
154*d19ef5a2SAaron LI  * If year $year < 0, then year is ignored.
155*d19ef5a2SAaron LI  */
156*d19ef5a2SAaron LI int
julian_find_days_ymd(int year,int month,int day,struct cal_day ** dayp,char ** edp __unused)157*d19ef5a2SAaron LI julian_find_days_ymd(int year, int month, int day, struct cal_day **dayp,
158*d19ef5a2SAaron LI 		     char **edp __unused)
159*d19ef5a2SAaron LI {
160*d19ef5a2SAaron LI 	struct cal_day *dp;
161*d19ef5a2SAaron LI 	struct date date;
162*d19ef5a2SAaron LI 	int rd, year1, year2;
163*d19ef5a2SAaron LI 	int count = 0;
164*d19ef5a2SAaron LI 
165*d19ef5a2SAaron LI 	year1 = julian_year_from_fixed(Options.day_begin);
166*d19ef5a2SAaron LI 	year2 = julian_year_from_fixed(Options.day_end);
167*d19ef5a2SAaron LI 	for (int y = year1; y <= year2; y++) {
168*d19ef5a2SAaron LI 		if (year >= 0 && year != y)
169*d19ef5a2SAaron LI 			continue;
170*d19ef5a2SAaron LI 		date_set(&date, y, month, day);
171*d19ef5a2SAaron LI 		rd = fixed_from_julian(&date);
172*d19ef5a2SAaron LI 		if ((dp = find_rd(rd, 0)) != NULL) {
173*d19ef5a2SAaron LI 			if (count >= CAL_MAX_REPEAT) {
174*d19ef5a2SAaron LI 				warnx("%s: too many repeats", __func__);
175*d19ef5a2SAaron LI 				return count;
176*d19ef5a2SAaron LI 			}
177*d19ef5a2SAaron LI 			dayp[count++] = dp;
178*d19ef5a2SAaron LI 		}
179*d19ef5a2SAaron LI 	}
180*d19ef5a2SAaron LI 
181*d19ef5a2SAaron LI 	return count;
182*d19ef5a2SAaron LI }
183*d19ef5a2SAaron LI 
184*d19ef5a2SAaron LI /*
185*d19ef5a2SAaron LI  * Find days of the specified Julian day of month ($dom) of all months.
186*d19ef5a2SAaron LI  */
187*d19ef5a2SAaron LI int
julian_find_days_dom(int dom,struct cal_day ** dayp,char ** edp __unused)188*d19ef5a2SAaron LI julian_find_days_dom(int dom, struct cal_day **dayp, char **edp __unused)
189*d19ef5a2SAaron LI {
190*d19ef5a2SAaron LI 	struct cal_day *dp;
191*d19ef5a2SAaron LI 	struct date date;
192*d19ef5a2SAaron LI 	int year1, year2;
193*d19ef5a2SAaron LI 	int rd_begin, rd_end;
194*d19ef5a2SAaron LI 	int count = 0;
195*d19ef5a2SAaron LI 
196*d19ef5a2SAaron LI 	year1 = julian_year_from_fixed(Options.day_begin);
197*d19ef5a2SAaron LI 	year2 = julian_year_from_fixed(Options.day_end);
198*d19ef5a2SAaron LI 	for (int y = year1; y <= year2; y++) {
199*d19ef5a2SAaron LI 		date_set(&date, y, 1, 1);
200*d19ef5a2SAaron LI 		rd_begin = fixed_from_julian(&date);
201*d19ef5a2SAaron LI 		date.year++;
202*d19ef5a2SAaron LI 		rd_end = fixed_from_julian(&date);
203*d19ef5a2SAaron LI 		if (rd_end > Options.day_end)
204*d19ef5a2SAaron LI 			rd_end = Options.day_end;
205*d19ef5a2SAaron LI 
206*d19ef5a2SAaron LI 		for (int m = 1, rd = rd_begin; rd <= rd_end; m++) {
207*d19ef5a2SAaron LI 			date_set(&date, y, m, dom);
208*d19ef5a2SAaron LI 			rd = fixed_from_julian(&date);
209*d19ef5a2SAaron LI 			if ((dp = find_rd(rd, 0)) != NULL) {
210*d19ef5a2SAaron LI 				if (count >= CAL_MAX_REPEAT) {
211*d19ef5a2SAaron LI 					warnx("%s: too many repeats",
212*d19ef5a2SAaron LI 					      __func__);
213*d19ef5a2SAaron LI 					return count;
214*d19ef5a2SAaron LI 				}
215*d19ef5a2SAaron LI 				dayp[count++] = dp;
216*d19ef5a2SAaron LI 			}
217*d19ef5a2SAaron LI 		}
218*d19ef5a2SAaron LI 	}
219*d19ef5a2SAaron LI 
220*d19ef5a2SAaron LI 	return count;
221*d19ef5a2SAaron LI }
222*d19ef5a2SAaron LI 
223*d19ef5a2SAaron LI /*
224*d19ef5a2SAaron LI  * Find days of all days of the specified Julian month ($month).
225*d19ef5a2SAaron LI  */
226*d19ef5a2SAaron LI int
julian_find_days_month(int month,struct cal_day ** dayp,char ** edp __unused)227*d19ef5a2SAaron LI julian_find_days_month(int month, struct cal_day **dayp, char **edp __unused)
228*d19ef5a2SAaron LI {
229*d19ef5a2SAaron LI 	struct cal_day *dp;
230*d19ef5a2SAaron LI 	struct date date;
231*d19ef5a2SAaron LI 	int year1, year2;
232*d19ef5a2SAaron LI 	int rd_begin, rd_end;
233*d19ef5a2SAaron LI 	int count = 0;
234*d19ef5a2SAaron LI 
235*d19ef5a2SAaron LI 	year1 = julian_year_from_fixed(Options.day_begin);
236*d19ef5a2SAaron LI 	year2 = julian_year_from_fixed(Options.day_end);
237*d19ef5a2SAaron LI 	for (int y = year1; y <= year2; y++) {
238*d19ef5a2SAaron LI 		date_set(&date, y, month, 1);
239*d19ef5a2SAaron LI 		rd_begin = fixed_from_julian(&date);
240*d19ef5a2SAaron LI 		date.month++;
241*d19ef5a2SAaron LI 		if (date.month > 12)
242*d19ef5a2SAaron LI 			date_set(&date, y+1, 1, 1);
243*d19ef5a2SAaron LI 		rd_end = fixed_from_julian(&date);
244*d19ef5a2SAaron LI 		if (rd_end > Options.day_end)
245*d19ef5a2SAaron LI 			rd_end = Options.day_end;
246*d19ef5a2SAaron LI 
247*d19ef5a2SAaron LI 		for (int rd = rd_begin; rd <= rd_end; rd++) {
248*d19ef5a2SAaron LI 			if ((dp = find_rd(rd, 0)) != NULL) {
249*d19ef5a2SAaron LI 				if (count >= CAL_MAX_REPEAT) {
250*d19ef5a2SAaron LI 					warnx("%s: too many repeats",
251*d19ef5a2SAaron LI 					      __func__);
252*d19ef5a2SAaron LI 					return count;
253*d19ef5a2SAaron LI 				}
254*d19ef5a2SAaron LI 				dayp[count++] = dp;
255*d19ef5a2SAaron LI 			}
256*d19ef5a2SAaron LI 		}
257*d19ef5a2SAaron LI 	}
258*d19ef5a2SAaron LI 
259*d19ef5a2SAaron LI 	return count;
260*d19ef5a2SAaron LI }
261*d19ef5a2SAaron LI 
262*d19ef5a2SAaron LI 
263*d19ef5a2SAaron LI /*
264*d19ef5a2SAaron LI  * Print the Julian calendar of the given date $rd.
265*d19ef5a2SAaron LI  */
266*d19ef5a2SAaron LI void
show_julian_calendar(int rd)267*d19ef5a2SAaron LI show_julian_calendar(int rd)
268*d19ef5a2SAaron LI {
269*d19ef5a2SAaron LI 	struct date gdate, jdate;
270*d19ef5a2SAaron LI 	bool leap;
271*d19ef5a2SAaron LI 
272*d19ef5a2SAaron LI 	gregorian_from_fixed(rd, &gdate);
273*d19ef5a2SAaron LI 	julian_from_fixed(rd, &jdate);
274*d19ef5a2SAaron LI 	leap = julian_leap_year(jdate.year);
275*d19ef5a2SAaron LI 
276*d19ef5a2SAaron LI 	printf("Gregorian date: %d-%02d-%02d\n",
277*d19ef5a2SAaron LI 	       gdate.year, gdate.month, gdate.day);
278*d19ef5a2SAaron LI 	printf("Julian date: %d-%02d-%02d\n",
279*d19ef5a2SAaron LI 	       jdate.year, jdate.month, jdate.day);
280*d19ef5a2SAaron LI 	printf("Leap year: %s\n", leap ? "yes" : "no");
281*d19ef5a2SAaron LI }
282