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 37 #include <assert.h> 38 #include <err.h> 39 #include <math.h> 40 #include <stddef.h> 41 42 #include "calendar.h" 43 #include "basics.h" 44 #include "chinese.h" 45 #include "dates.h" 46 #include "days.h" 47 #include "ecclesiastical.h" 48 #include "gregorian.h" 49 #include "moon.h" 50 #include "nnames.h" 51 #include "parsedata.h" 52 #include "sun.h" 53 #include "utils.h" 54 55 static int find_days_yearly(int sday_id, int offset, 56 struct cal_day **dayp, char **edp); 57 static int find_days_moon(int sday_id, int offset, 58 struct cal_day **dayp, char **edp); 59 60 static int find_days_easter(int, struct cal_day **, char **); 61 static int find_days_paskha(int, struct cal_day **, char **); 62 static int find_days_advent(int, struct cal_day **, char **); 63 static int find_days_cny(int, struct cal_day **, char **); 64 static int find_days_cqingming(int, struct cal_day **, char **); 65 static int find_days_cjieqi(int, struct cal_day **, char **); 66 static int find_days_marequinox(int, struct cal_day **, char **); 67 static int find_days_sepequinox(int, struct cal_day **, char **); 68 static int find_days_junsolstice(int, struct cal_day **, char **); 69 static int find_days_decsolstice(int, struct cal_day **, char **); 70 static int find_days_newmoon(int, struct cal_day **, char **); 71 static int find_days_fullmoon(int, struct cal_day **, char **); 72 73 #define SPECIALDAY_INIT0 \ 74 { SD_NONE, NULL, 0, NULL, 0, NULL } 75 #define SPECIALDAY_INIT(id, name, func) \ 76 { (id), name, sizeof(name)-1, NULL, 0, func } 77 struct specialday specialdays[] = { 78 SPECIALDAY_INIT(SD_EASTER, "Easter", &find_days_easter), 79 SPECIALDAY_INIT(SD_PASKHA, "Paskha", &find_days_paskha), 80 SPECIALDAY_INIT(SD_ADVENT, "Advent", &find_days_advent), 81 SPECIALDAY_INIT(SD_CNY, "ChineseNewYear", &find_days_cny), 82 SPECIALDAY_INIT(SD_CQINGMING, "ChineseQingming", &find_days_cqingming), 83 SPECIALDAY_INIT(SD_CJIEQI, "ChineseJieqi", &find_days_cjieqi), 84 SPECIALDAY_INIT(SD_MAREQUINOX, "MarEquinox", &find_days_marequinox), 85 SPECIALDAY_INIT(SD_SEPEQUINOX, "SepEquinox", &find_days_sepequinox), 86 SPECIALDAY_INIT(SD_JUNSOLSTICE, "JunSolstice", &find_days_junsolstice), 87 SPECIALDAY_INIT(SD_DECSOLSTICE, "DecSolstice", &find_days_decsolstice), 88 SPECIALDAY_INIT(SD_NEWMOON, "NewMoon", &find_days_newmoon), 89 SPECIALDAY_INIT(SD_FULLMOON, "FullMoon", &find_days_fullmoon), 90 SPECIALDAY_INIT0, 91 }; 92 93 94 static int 95 find_days_easter(int offset, struct cal_day **dayp, char **edp) 96 { 97 return find_days_yearly(SD_EASTER, offset, dayp, edp); 98 } 99 100 static int 101 find_days_paskha(int offset, struct cal_day **dayp, char **edp) 102 { 103 return find_days_yearly(SD_PASKHA, offset, dayp, edp); 104 } 105 106 static int 107 find_days_advent(int offset, struct cal_day **dayp, char **edp) 108 { 109 return find_days_yearly(SD_ADVENT, offset, dayp, edp); 110 } 111 112 static int 113 find_days_cny(int offset, struct cal_day **dayp, char **edp) 114 { 115 return find_days_yearly(SD_CNY, offset, dayp, edp); 116 } 117 118 static int 119 find_days_cqingming(int offset, struct cal_day **dayp, char **edp) 120 { 121 return find_days_yearly(SD_CQINGMING, offset, dayp, edp); 122 } 123 124 static int 125 find_days_marequinox(int offset, struct cal_day **dayp, char **edp) 126 { 127 return find_days_yearly(SD_MAREQUINOX, offset, dayp, edp); 128 } 129 130 static int 131 find_days_sepequinox(int offset, struct cal_day **dayp, char **edp) 132 { 133 return find_days_yearly(SD_SEPEQUINOX, offset, dayp, edp); 134 } 135 136 static int 137 find_days_junsolstice(int offset, struct cal_day **dayp, char **edp) 138 { 139 return find_days_yearly(SD_JUNSOLSTICE, offset, dayp, edp); 140 } 141 142 static int 143 find_days_decsolstice(int offset, struct cal_day **dayp, char **edp) 144 { 145 return find_days_yearly(SD_DECSOLSTICE, offset, dayp, edp); 146 } 147 148 /* 149 * Find days of the yearly special day specified by $sday_id. 150 */ 151 static int 152 find_days_yearly(int sday_id, int offset, struct cal_day **dayp, char **edp) 153 { 154 struct cal_day *dp; 155 struct date date; 156 double t, longitude; 157 char buf[32]; 158 int rd, approx, month; 159 int year1, year2; 160 int count = 0; 161 162 year1 = gregorian_year_from_fixed(Options.day_begin); 163 year2 = gregorian_year_from_fixed(Options.day_end); 164 for (int y = year1; y <= year2; y++) { 165 t = NAN; 166 167 switch (sday_id) { 168 case SD_EASTER: 169 rd = easter(y); 170 break; 171 case SD_PASKHA: 172 rd = orthodox_easter(y); 173 break; 174 case SD_ADVENT: 175 rd = advent(y); 176 break; 177 case SD_CNY: 178 rd = chinese_new_year(y); 179 break; 180 case SD_CQINGMING: 181 rd = chinese_qingming(y); 182 break; 183 case SD_MAREQUINOX: 184 case SD_JUNSOLSTICE: 185 case SD_SEPEQUINOX: 186 case SD_DECSOLSTICE: 187 if (sday_id == SD_MAREQUINOX) { 188 month = 3; 189 longitude = 0.0; 190 } else if (sday_id == SD_JUNSOLSTICE) { 191 month = 6; 192 longitude = 90.0; 193 } else if (sday_id == SD_SEPEQUINOX) { 194 month = 9; 195 longitude = 180.0; 196 } else { 197 month = 12; 198 longitude = 270.0; 199 } 200 date_set(&date, y, month, 1); 201 approx = fixed_from_gregorian(&date); 202 t = solar_longitude_atafter(longitude, approx); 203 t += Options.location->zone; /* to standard time */ 204 rd = floor(t); 205 break; 206 default: 207 errx(1, "%s: unknown special day: %d", 208 __func__, sday_id); 209 } 210 211 if ((dp = find_rd(rd, offset)) != NULL) { 212 if (count >= CAL_MAX_REPEAT) { 213 warnx("%s: too many repeats", __func__); 214 return count; 215 } 216 if (!isnan(t)) { 217 format_time(buf, sizeof(buf), t); 218 edp[count] = xstrdup(buf); 219 } 220 dayp[count++] = dp; 221 } 222 } 223 224 return count; 225 } 226 227 /* 228 * Find days of the 24 Chinese Jiéqì (节气) 229 */ 230 static int 231 find_days_cjieqi(int offset, struct cal_day **dayp, char **edp) 232 { 233 const struct chinese_jieqi *jq; 234 struct cal_day *dp; 235 struct date date; 236 char buf[32]; 237 int year1, year2; 238 int rd, rd_begin, rd_end; 239 int count = 0; 240 241 year1 = gregorian_year_from_fixed(Options.day_begin); 242 year2 = gregorian_year_from_fixed(Options.day_end); 243 for (int y = year1; y <= year2; y++) { 244 date_set(&date, y, 1, 1); 245 rd_begin = fixed_from_gregorian(&date); 246 date.year++; 247 rd_end = fixed_from_gregorian(&date); 248 if (rd_end > Options.day_end) 249 rd_end = Options.day_end; 250 251 for (rd = rd_begin; rd <= rd_end; rd += 14) { 252 rd = chinese_jieqi_onafter(rd, C_JIEQI_ALL, &jq); 253 if (rd > rd_end) 254 break; 255 256 if ((dp = find_rd(rd, offset)) != NULL) { 257 if (count >= CAL_MAX_REPEAT) { 258 warnx("%s: too many repeats", 259 __func__); 260 return count; 261 } 262 snprintf(buf, sizeof(buf), "%s, %s", 263 jq->name, jq->zhname); 264 edp[count] = xstrdup(buf); 265 dayp[count++] = dp; 266 } 267 } 268 } 269 270 return count; 271 } 272 273 static int 274 find_days_newmoon(int offset, struct cal_day **dayp, char **edp) 275 { 276 return find_days_moon(SD_NEWMOON, offset, dayp, edp); 277 } 278 279 static int 280 find_days_fullmoon(int offset, struct cal_day **dayp, char **edp) 281 { 282 return find_days_moon(SD_FULLMOON, offset, dayp, edp); 283 } 284 285 /* 286 * Find days of the moon events specified by $sday_id. 287 */ 288 static int 289 find_days_moon(int sday_id, int offset, struct cal_day **dayp, char **edp) 290 { 291 struct cal_day *dp; 292 struct date date; 293 double t, t_begin, t_end; 294 char buf[32]; 295 int year1, year2; 296 int count = 0; 297 298 year1 = gregorian_year_from_fixed(Options.day_begin); 299 year2 = gregorian_year_from_fixed(Options.day_end); 300 for (int y = year1; y <= year2; y++) { 301 date_set(&date, y, 1, 1); 302 t_begin = fixed_from_gregorian(&date) - Options.location->zone; 303 date.year++; 304 t_end = fixed_from_gregorian(&date) - Options.location->zone; 305 if (t_end > Options.day_end + 1 - Options.location->zone) 306 t_end = Options.day_end + 1 - Options.location->zone; 307 /* NOTE: '+1' to include the ending day */ 308 309 for (t = t_begin; t <= t_end; ) { 310 switch (sday_id) { 311 case SD_NEWMOON: 312 t = new_moon_atafter(t); 313 break; 314 case SD_FULLMOON: 315 t = lunar_phase_atafter(180, t); 316 break; 317 default: 318 errx(1, "%s: unknown moon event: %d", 319 __func__, sday_id); 320 } 321 322 if (t > t_end) 323 break; 324 325 t += Options.location->zone; /* to standard time */ 326 if ((dp = find_rd(floor(t), offset)) != NULL) { 327 if (count >= CAL_MAX_REPEAT) { 328 warnx("%s: too many repeats", 329 __func__); 330 return count; 331 } 332 format_time(buf, sizeof(buf), t); 333 edp[count] = xstrdup(buf); 334 dayp[count++] = dp; 335 } 336 } 337 } 338 339 return count; 340 } 341 342 /**************************************************************************/ 343 344 /* 345 * Find days of the specified year ($year), month ($month) and day ($day). 346 * If year $year < 0, then year is ignored. 347 */ 348 int 349 find_days_ymd(int year, int month, int day, 350 struct cal_day **dayp, char **edp __unused) 351 { 352 struct cal_day *dp = NULL; 353 int count = 0; 354 355 while ((dp = loop_dates(dp)) != NULL) { 356 if (year >= 0 && year != dp->year) 357 continue; 358 if ((dp->month == month && dp->day == day) || 359 /* day of zero means the last day of previous month */ 360 (day == 0 && dp->last_dom && month == dp->month % 12 + 1)) { 361 if (count >= CAL_MAX_REPEAT) { 362 warnx("%s: too many repeats", __func__); 363 return count; 364 } 365 dayp[count++] = dp; 366 } 367 } 368 369 return count; 370 } 371 372 /* 373 * Find days of the specified day of month ($dom) of all months. 374 */ 375 int 376 find_days_dom(int dom, struct cal_day **dayp, char **edp __unused) 377 { 378 struct cal_day *dp = NULL; 379 int count = 0; 380 381 while ((dp = loop_dates(dp)) != NULL) { 382 if (dp->day == dom || 383 /* day of zero means the last day of previous month */ 384 (dom == 0 && dp->last_dom)) { 385 if (count >= CAL_MAX_REPEAT) { 386 warnx("%s: too many repeats", __func__); 387 return count; 388 } 389 dayp[count++] = dp; 390 } 391 } 392 393 return count; 394 } 395 396 /* 397 * Find days of all days of the specified month ($month). 398 */ 399 int 400 find_days_month(int month, struct cal_day **dayp, char **edp __unused) 401 { 402 struct cal_day *dp = NULL; 403 int count = 0; 404 405 while ((dp = loop_dates(dp)) != NULL) { 406 if (dp->month == month) { 407 if (count >= CAL_MAX_REPEAT) { 408 warnx("%s: too many repeats", __func__); 409 return count; 410 } 411 dayp[count++] = dp; 412 } 413 } 414 415 return count; 416 } 417 418 /* 419 * If $index == 0, find days of every day-of-week ($dow) of the specified month 420 * ($month). Otherwise, find days of the indexed day-of-week of the month. 421 * If month $month < 0, then find days in every month. 422 */ 423 int 424 find_days_mdow(int month, int dow, int index, 425 struct cal_day **dayp, char **edp __unused) 426 { 427 struct cal_day *dp = NULL; 428 int count = 0; 429 430 while ((dp = loop_dates(dp)) != NULL) { 431 if (month >= 0 && month != dp->month) 432 continue; 433 if (dp->dow[0] == dow) { 434 if (index != 0 && 435 (index != dp->dow[1] && index != dp->dow[2])) { 436 /* Not the indexed day-of-week of month */ 437 continue; 438 } 439 if (count >= CAL_MAX_REPEAT) { 440 warnx("%s: too many repeats", __func__); 441 return count; 442 } 443 dayp[count++] = dp; 444 } 445 } 446 447 return count; 448 } 449