xref: /dragonfly/usr.bin/calendar/julian.c (revision 479ab7f0)
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
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
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
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
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
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
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
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
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
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