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