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