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 * Reference:
37 * Calendrical Calculations, The Ultimate Edition (4th Edition)
38 * by Edward M. Reingold and Nachum Dershowitz
39 * 2018, Cambridge University Press
40 */
41
42 #include <stdbool.h>
43 #include <stdio.h>
44
45 #include "calendar.h"
46 #include "basics.h"
47 #include "dates.h"
48 #include "gregorian.h"
49 #include "julian.h"
50 #include "utils.h"
51
52 /*
53 * Fixed date of the start of the Julian calendar.
54 * Ref: Sec.(3.1), Eq.(3.2)
55 */
56 static const int epoch = -1; /* Gregorian: 0, December, 30 */
57
58 /*
59 * Return true if $year is a leap year on the Julian calendar,
60 * otherwise return false.
61 * Ref: Sec.(3.1), Eq.(3.1)
62 */
63 bool
julian_leap_year(int year)64 julian_leap_year(int year)
65 {
66 int i = (year > 0) ? 0 : 3;
67 return (mod(year, 4) == i);
68 }
69
70 /*
71 * Calculate the fixed date (RD) equivalent to the Julian date $date.
72 * Ref: Sec.(3.1), Eq.(3.3)
73 */
74 int
fixed_from_julian(const struct date * date)75 fixed_from_julian(const struct date *date)
76 {
77 int y = (date->year >= 0) ? date->year : (date->year + 1);
78 int rd = ((epoch - 1) + 365 * (y - 1) +
79 div_floor(y - 1, 4) +
80 div_floor(date->month * 367 - 362, 12));
81 /* correct for the assumption that February always has 30 days */
82 if (date->month <= 2)
83 return rd + date->day;
84 else if (julian_leap_year(date->year))
85 return rd + date->day - 1;
86 else
87 return rd + date->day - 2;
88 }
89
90 /*
91 * Calculate the Julian date (year, month, day) corresponding to the
92 * fixed date $rd.
93 * Ref: Sec.(3.1), Eq.(3.4)
94 */
95 void
julian_from_fixed(int rd,struct date * date)96 julian_from_fixed(int rd, struct date *date)
97 {
98 int correction, pdays;
99
100 date->year = div_floor(4 * (rd - epoch) + 1464, 1461);
101 if (date->year <= 0)
102 date->year--;
103
104 struct date d = { date->year, 3, 1 };
105 if (rd < fixed_from_julian(&d))
106 correction = 0;
107 else if (julian_leap_year(date->year))
108 correction = 1;
109 else
110 correction = 2;
111
112 d.month = 1;
113 pdays = rd - fixed_from_julian(&d);
114 date->month = div_floor(12 * (pdays + correction) + 373, 367);
115
116 d.month = date->month;
117 date->day = rd - fixed_from_julian(&d) + 1;
118 }
119
120 /**************************************************************************/
121
122 /*
123 * Format the given fixed date $rd to '<month>/<day>' string in $buf.
124 * Return the formatted string length.
125 */
126 int
julian_format_date(char * buf,size_t size,int rd)127 julian_format_date(char *buf, size_t size, int rd)
128 {
129 static const char *month_names[] = {
130 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
131 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec",
132 };
133 struct date jdate;
134
135 julian_from_fixed(rd, &jdate);
136 return snprintf(buf, size, "%s/%02d",
137 month_names[jdate.month - 1], jdate.day);
138 }
139
140 /*
141 * Calculate the Julian year corresponding to the fixed date $rd.
142 */
143 static int
julian_year_from_fixed(int rd)144 julian_year_from_fixed(int rd)
145 {
146 struct date jdate;
147 julian_from_fixed(rd, &jdate);
148 return jdate.year;
149 }
150
151 /*
152 * Find days of the specified Julian year ($year), month ($month) and
153 * day ($day).
154 * If year $year < 0, then year is ignored.
155 */
156 int
julian_find_days_ymd(int year,int month,int day,struct cal_day ** dayp,char ** edp __unused)157 julian_find_days_ymd(int year, int month, int day, struct cal_day **dayp,
158 char **edp __unused)
159 {
160 struct cal_day *dp;
161 struct date date;
162 int rd, year1, year2;
163 int count = 0;
164
165 year1 = julian_year_from_fixed(Options.day_begin);
166 year2 = julian_year_from_fixed(Options.day_end);
167 for (int y = year1; y <= year2; y++) {
168 if (year >= 0 && year != y)
169 continue;
170 date_set(&date, y, month, day);
171 rd = fixed_from_julian(&date);
172 if ((dp = find_rd(rd, 0)) != NULL) {
173 if (count >= CAL_MAX_REPEAT) {
174 warnx("%s: too many repeats", __func__);
175 return count;
176 }
177 dayp[count++] = dp;
178 }
179 }
180
181 return count;
182 }
183
184 /*
185 * Find days of the specified Julian day of month ($dom) of all months.
186 */
187 int
julian_find_days_dom(int dom,struct cal_day ** dayp,char ** edp __unused)188 julian_find_days_dom(int dom, struct cal_day **dayp, char **edp __unused)
189 {
190 struct cal_day *dp;
191 struct date date;
192 int year1, year2;
193 int rd_begin, rd_end;
194 int count = 0;
195
196 year1 = julian_year_from_fixed(Options.day_begin);
197 year2 = julian_year_from_fixed(Options.day_end);
198 for (int y = year1; y <= year2; y++) {
199 date_set(&date, y, 1, 1);
200 rd_begin = fixed_from_julian(&date);
201 date.year++;
202 rd_end = fixed_from_julian(&date);
203 if (rd_end > Options.day_end)
204 rd_end = Options.day_end;
205
206 for (int m = 1, rd = rd_begin; rd <= rd_end; m++) {
207 date_set(&date, y, m, dom);
208 rd = fixed_from_julian(&date);
209 if ((dp = find_rd(rd, 0)) != NULL) {
210 if (count >= CAL_MAX_REPEAT) {
211 warnx("%s: too many repeats",
212 __func__);
213 return count;
214 }
215 dayp[count++] = dp;
216 }
217 }
218 }
219
220 return count;
221 }
222
223 /*
224 * Find days of all days of the specified Julian month ($month).
225 */
226 int
julian_find_days_month(int month,struct cal_day ** dayp,char ** edp __unused)227 julian_find_days_month(int month, struct cal_day **dayp, char **edp __unused)
228 {
229 struct cal_day *dp;
230 struct date date;
231 int year1, year2;
232 int rd_begin, rd_end;
233 int count = 0;
234
235 year1 = julian_year_from_fixed(Options.day_begin);
236 year2 = julian_year_from_fixed(Options.day_end);
237 for (int y = year1; y <= year2; y++) {
238 date_set(&date, y, month, 1);
239 rd_begin = fixed_from_julian(&date);
240 date.month++;
241 if (date.month > 12)
242 date_set(&date, y+1, 1, 1);
243 rd_end = fixed_from_julian(&date);
244 if (rd_end > Options.day_end)
245 rd_end = Options.day_end;
246
247 for (int rd = rd_begin; rd <= rd_end; rd++) {
248 if ((dp = find_rd(rd, 0)) != NULL) {
249 if (count >= CAL_MAX_REPEAT) {
250 warnx("%s: too many repeats",
251 __func__);
252 return count;
253 }
254 dayp[count++] = dp;
255 }
256 }
257 }
258
259 return count;
260 }
261
262
263 /*
264 * Print the Julian calendar of the given date $rd.
265 */
266 void
show_julian_calendar(int rd)267 show_julian_calendar(int rd)
268 {
269 struct date gdate, jdate;
270 bool leap;
271
272 gregorian_from_fixed(rd, &gdate);
273 julian_from_fixed(rd, &jdate);
274 leap = julian_leap_year(jdate.year);
275
276 printf("Gregorian date: %d-%02d-%02d\n",
277 gdate.year, gdate.month, gdate.day);
278 printf("Julian date: %d-%02d-%02d\n",
279 jdate.year, jdate.month, jdate.day);
280 printf("Leap year: %s\n", leap ? "yes" : "no");
281 }
282