1 /* $OpenBSD: day.c,v 1.34 2016/09/14 15:09:46 millert Exp $ */ 2 3 /* 4 * Copyright (c) 1989, 1993, 1994 5 * The Regents of the University of California. All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 1. Redistributions of source code must retain the above copyright 11 * notice, this list of conditions and the following disclaimer. 12 * 2. Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 3. Neither the name of the University nor the names of its contributors 16 * may be used to endorse or promote products derived from this software 17 * without specific prior written permission. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND 20 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE 23 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 24 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 25 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 26 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 27 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 28 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 29 * SUCH DAMAGE. 30 */ 31 32 #include <sys/types.h> 33 #include <sys/uio.h> 34 35 #include <ctype.h> 36 #include <err.h> 37 #include <locale.h> 38 #include <stdio.h> 39 #include <stdlib.h> 40 #include <string.h> 41 #include <time.h> 42 43 #include "pathnames.h" 44 #include "calendar.h" 45 46 extern struct iovec header[]; 47 48 #define WEEKLY 1 49 #define MONTHLY 2 50 #define YEARLY 3 51 52 struct tm *tp; 53 int *cumdays, offset; 54 char dayname[10]; 55 enum calendars calendar; 56 u_long julian; 57 58 59 /* 1-based month, 0-based days, cumulative */ 60 int daytab[][14] = { 61 { 0, -1, 30, 58, 89, 119, 150, 180, 211, 242, 272, 303, 333, 364 }, 62 { 0, -1, 30, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365 }, 63 }; 64 65 static char *days[] = { 66 "sun", "mon", "tue", "wed", "thu", "fri", "sat", NULL, 67 }; 68 69 static char *months[] = { 70 "jan", "feb", "mar", "apr", "may", "jun", 71 "jul", "aug", "sep", "oct", "nov", "dec", NULL, 72 }; 73 74 static struct fixs fndays[8]; /* full national days names */ 75 static struct fixs ndays[8]; /* short national days names */ 76 77 static struct fixs fnmonths[13]; /* full national months names */ 78 static struct fixs nmonths[13]; /* short national month names */ 79 80 void 81 fill_print_date(struct match *m, struct tm *tm) 82 { 83 if (strftime(m->print_date, sizeof(m->print_date), 84 daynames ? "%a %b %d" : "%b %d", tm) == 0) 85 m->print_date[sizeof(m->print_date) - 1] = '\0'; 86 } 87 88 void 89 setnnames(void) 90 { 91 char buf[80]; 92 int i, l; 93 struct tm tm; 94 95 for (i = 0; i < 7; i++) { 96 tm.tm_wday = i; 97 l = strftime(buf, sizeof(buf), "%a", &tm); 98 for (; l > 0 && isspace((unsigned char)buf[l - 1]); l--) 99 ; 100 buf[l] = '\0'; 101 free(ndays[i].name); 102 if ((ndays[i].name = strdup(buf)) == NULL) 103 err(1, NULL); 104 ndays[i].len = strlen(buf); 105 106 l = strftime(buf, sizeof(buf), "%A", &tm); 107 for (; l > 0 && isspace((unsigned char)buf[l - 1]); l--) 108 ; 109 buf[l] = '\0'; 110 free(fndays[i].name); 111 if ((fndays[i].name = strdup(buf)) == NULL) 112 err(1, NULL); 113 fndays[i].len = strlen(buf); 114 } 115 116 for (i = 0; i < 12; i++) { 117 tm.tm_mon = i; 118 l = strftime(buf, sizeof(buf), "%b", &tm); 119 for (; l > 0 && isspace((unsigned char)buf[l - 1]); l--) 120 ; 121 buf[l] = '\0'; 122 free(nmonths[i].name); 123 if ((nmonths[i].name = strdup(buf)) == NULL) 124 err(1, NULL); 125 nmonths[i].len = strlen(buf); 126 127 l = strftime(buf, sizeof(buf), "%B", &tm); 128 for (; l > 0 && isspace((unsigned char)buf[l - 1]); l--) 129 ; 130 buf[l] = '\0'; 131 free(fnmonths[i].name); 132 if ((fnmonths[i].name = strdup(buf)) == NULL) 133 err(1, NULL); 134 fnmonths[i].len = strlen(buf); 135 } 136 /* Hardwired special events */ 137 spev[0].name = strdup(PESACH); 138 spev[0].nlen = PESACHLEN; 139 spev[0].getev = pesach; 140 spev[1].name = strdup(EASTER); 141 spev[1].nlen = EASTERNAMELEN; 142 spev[1].getev = easter; 143 spev[2].name = strdup(PASKHA); 144 spev[2].nlen = PASKHALEN; 145 spev[2].getev = paskha; 146 for (i = 0; i < NUMEV; i++) { 147 if (spev[i].name == NULL) 148 err(1, NULL); 149 spev[i].uname = NULL; 150 } 151 } 152 153 void 154 settime(time_t *now) 155 { 156 tp = localtime(now); 157 tp->tm_sec = 0; 158 tp->tm_min = 0; 159 /* Avoid getting caught by a timezone shift; set time to noon */ 160 tp->tm_isdst = 0; 161 tp->tm_hour = 12; 162 *now = mktime(tp); 163 if (isleap(tp->tm_year + 1900)) 164 cumdays = daytab[1]; 165 else 166 cumdays = daytab[0]; 167 /* Friday displays Monday's events */ 168 offset = tp->tm_wday == 5 ? 3 : 1; 169 if (f_SetdayAfter) 170 offset = 0; /* Except not when range is set explicitly */ 171 header[5].iov_base = dayname; 172 173 (void) setlocale(LC_TIME, "C"); 174 header[5].iov_len = strftime(dayname, sizeof(dayname), "%A", tp); 175 (void) setlocale(LC_TIME, ""); 176 177 setnnames(); 178 } 179 180 /* convert [Year][Month]Day into unix time (since 1970) 181 * Year: two or four digits, Month: two digits, Day: two digits 182 */ 183 time_t 184 Mktime(char *date) 185 { 186 time_t t; 187 int len; 188 struct tm tm; 189 190 (void)time(&t); 191 tp = localtime(&t); 192 193 len = strlen(date); 194 if (len < 2) 195 return((time_t)-1); 196 bzero(&tm, sizeof tm); 197 tm.tm_sec = 0; 198 tm.tm_min = 0; 199 /* Avoid getting caught by a timezone shift; set time to noon */ 200 tm.tm_isdst = 0; 201 tm.tm_hour = 12; 202 tm.tm_wday = 0; 203 tm.tm_mday = tp->tm_mday; 204 tm.tm_mon = tp->tm_mon; 205 tm.tm_year = tp->tm_year; 206 207 /* Day */ 208 tm.tm_mday = atoi(date + len - 2); 209 210 /* Month */ 211 if (len >= 4) { 212 *(date + len - 2) = '\0'; 213 tm.tm_mon = atoi(date + len - 4) - 1; 214 } 215 216 /* Year */ 217 if (len >= 6) { 218 *(date + len - 4) = '\0'; 219 tm.tm_year = atoi(date); 220 221 if (tm.tm_year < 69) /* Y2K */ 222 tm.tm_year += 100; 223 else if (tm.tm_year > 1900) 224 tm.tm_year -= 1900; 225 } 226 227 #if DEBUG 228 printf("Mktime: %d %lld %d %s\n", (int)mktime(&tm), (long long)t, len, 229 asctime(&tm)); 230 #endif 231 return(mktime(&tm)); 232 } 233 234 static void 235 adjust_calendar(int *day, int *month) 236 { 237 switch (calendar) { 238 case GREGORIAN: 239 break; 240 241 case JULIAN: 242 *day += julian; 243 if (*day > (cumdays[*month + 1] - cumdays[*month])) { 244 *day -= (cumdays[*month + 1] - cumdays[*month]); 245 if (++*month > 12) 246 *month = 1; 247 } 248 break; 249 case LUNAR: 250 break; 251 } 252 } 253 254 /* 255 * Possible date formats include any combination of: 256 * 3-charmonth (January, Jan, Jan) 257 * 3-charweekday (Friday, Monday, mon.) 258 * numeric month or day (1, 2, 04) 259 * 260 * Any character except \t or '*' may separate them, or they may not be 261 * separated. Any line following a line that is matched, that starts 262 * with \t, is shown along with the matched line. 263 */ 264 struct match * 265 isnow(char *endp, int bodun) 266 { 267 int day = 0, flags = 0, month = 0, v1, v2, i; 268 int monthp, dayp, varp = 0; 269 struct match *matches = NULL, *tmp, *tmp2; 270 int interval = YEARLY; /* how frequently the event repeats. */ 271 int vwd = 0; /* Variable weekday */ 272 time_t tdiff, ttmp; 273 struct tm tmtmp; 274 275 /* 276 * CONVENTION 277 * 278 * Month: 1-12 279 * Monthname: Jan .. Dec 280 * Day: 1-31 281 * Weekday: Mon-Sun 282 * 283 */ 284 285 /* read first field */ 286 /* didn't recognize anything, skip it */ 287 if (!(v1 = getfield(endp, &endp, &flags))) 288 return (NULL); 289 290 /* adjust bodun rate */ 291 if (bodun && !bodun_always) 292 bodun = !arc4random_uniform(3); 293 294 /* Easter or Easter depending days */ 295 if (flags & F_SPECIAL) 296 vwd = v1; 297 298 /* 299 * 1. {Weekday,Day} XYZ ... 300 * 301 * where Day is > 12 302 */ 303 else if (flags & F_ISDAY || v1 > 12) { 304 305 /* found a day; day: 13-31 or weekday: 1-7 */ 306 day = v1; 307 308 /* {Day,Weekday} {Month,Monthname} ... */ 309 /* if no recognizable month, assume just a day alone -- this is 310 * very unlikely and can only happen after the first 12 days. 311 * --find month or use current month */ 312 if (!(month = getfield(endp, &endp, &flags))) { 313 month = tp->tm_mon + 1; 314 /* F_ISDAY is set only if a weekday was spelled out */ 315 /* F_ISDAY must be set if 0 < day < 8 */ 316 if ((day <= 7) && (day >= 1)) 317 interval = WEEKLY; 318 else 319 interval = MONTHLY; 320 } else if ((day <= 7) && (day >= 1)) 321 day += 10; 322 /* it's a weekday; make it the first one of the month */ 323 if (month == -1) { 324 month = tp->tm_mon + 1; 325 interval = MONTHLY; 326 } else if (calendar) 327 adjust_calendar(&day, &month); 328 if ((month > 12) || (month < 1)) 329 return (NULL); 330 } 331 332 /* 2. {Monthname} XYZ ... */ 333 else if (flags & F_ISMONTH) { 334 month = v1; 335 if (month == -1) { 336 month = tp->tm_mon + 1; 337 interval = MONTHLY; 338 } 339 /* Monthname {day,weekday} */ 340 /* if no recognizable day, assume the first day in month */ 341 if (!(day = getfield(endp, &endp, &flags))) 342 day = 1; 343 /* If a weekday was spelled out without an ordering, 344 * assume the first of that day in the month */ 345 if ((flags & F_ISDAY)) { 346 if ((day >= 1) && (day <=7)) 347 day += 10; 348 } else if (calendar) 349 adjust_calendar(&day, &month); 350 } 351 352 /* Hm ... */ 353 else { 354 v2 = getfield(endp, &endp, &flags); 355 356 /* 357 * {Day} {Monthname} ... 358 * where Day <= 12 359 */ 360 if (flags & F_ISMONTH) { 361 day = v1; 362 month = v2; 363 if (month == -1) { 364 month = tp->tm_mon + 1; 365 interval = MONTHLY; 366 } else if (calendar) 367 adjust_calendar(&day, &month); 368 } 369 370 /* {Month} {Weekday,Day} ... */ 371 else { 372 /* F_ISDAY set, v2 > 12, or no way to tell */ 373 month = v1; 374 /* if no recognizable day, assume the first */ 375 day = v2 ? v2 : 1; 376 if ((flags & F_ISDAY)) { 377 if ((day >= 1) && (day <= 7)) 378 day += 10; 379 } else 380 adjust_calendar(&day, &month); 381 } 382 } 383 384 /* convert Weekday into *next* Day, 385 * e.g.: 'Sunday' -> 22 386 * 'SundayLast' -> ?? 387 */ 388 if (flags & F_ISDAY) { 389 #if DEBUG 390 fprintf(stderr, "\nday: %d %s month %d\n", day, endp, month); 391 #endif 392 393 varp = 1; 394 /* variable weekday, SundayLast, MondayFirst ... */ 395 if (day < 0 || day >= 10) 396 vwd = day; 397 else { 398 day = tp->tm_mday + (((day - 1) - tp->tm_wday + 7) % 7); 399 interval = WEEKLY; 400 } 401 } else 402 /* Check for silliness. Note we still catch Feb 29 */ 403 if (!(flags & F_SPECIAL) && 404 (day > (cumdays[month + 1] - cumdays[month]) || day < 1)) { 405 if (!((month == 2 && day == 29) || 406 (interval == MONTHLY && day <= 31))) 407 return (NULL); 408 } 409 410 if (!(flags & F_SPECIAL)) { 411 monthp = month; 412 dayp = day; 413 day = cumdays[month] + day; 414 #if DEBUG 415 fprintf(stderr, "day2: day %d(%d) yday %d\n", dayp, day, tp->tm_yday); 416 #endif 417 /* Speed up processing for the most common situation: yearly events 418 * when the interval being checked is less than a month or so (this 419 * could be less than a year, but then we have to start worrying about 420 * leap years). Only one event can match, and it's easy to find. 421 * Note we can't check special events, because they can wander widely. 422 */ 423 if (((v1 = offset + f_dayAfter) < 50) && (interval == YEARLY)) { 424 memcpy(&tmtmp, tp, sizeof(struct tm)); 425 tmtmp.tm_mday = dayp; 426 tmtmp.tm_mon = monthp - 1; 427 if (vwd) { 428 /* We want the event next year if it's late now 429 * this year. The 50-day limit means we don't have to 430 * worry if next year is or isn't a leap year. 431 */ 432 if (tp->tm_yday > 300 && tmtmp.tm_mon <= 1) 433 variable_weekday(&vwd, tmtmp.tm_mon + 1, 434 tmtmp.tm_year + 1900 + 1); 435 else 436 variable_weekday(&vwd, tmtmp.tm_mon + 1, 437 tmtmp.tm_year + 1900); 438 day = cumdays[tmtmp.tm_mon + 1] + vwd; 439 tmtmp.tm_mday = vwd; 440 } 441 v2 = day - tp->tm_yday; 442 if ((v2 > v1) || (v2 < 0)) { 443 if ((v2 += isleap(tp->tm_year + 1900) ? 366 : 365) 444 <= v1) 445 tmtmp.tm_year++; 446 else if(!bodun || (day - tp->tm_yday) != -1) 447 return(NULL); 448 } 449 if ((tmp = malloc(sizeof(struct match))) == NULL) 450 err(1, NULL); 451 452 if (bodun && (day - tp->tm_yday) == -1) { 453 tmp->when = f_time - 1 * SECSPERDAY; 454 tmtmp.tm_mday++; 455 tmp->bodun = 1; 456 } else { 457 tmp->when = f_time + v2 * SECSPERDAY; 458 tmp->bodun = 0; 459 } 460 461 (void)mktime(&tmtmp); 462 fill_print_date(tmp, &tmtmp); 463 tmp->var = varp; 464 tmp->next = NULL; 465 return(tmp); 466 } 467 } else { 468 varp = 1; 469 /* Set up v1 to the event number and ... */ 470 v1 = vwd % (NUMEV + 1) - 1; 471 vwd /= (NUMEV + 1); 472 if (v1 < 0) { 473 v1 += NUMEV + 1; 474 vwd--; 475 } 476 dayp = monthp = 1; /* Why not */ 477 } 478 479 /* Compare to past and coming instances of the event. The i == 0 part 480 * of the loop corresponds to this specific instance. Note that we 481 * can leave things sort of higgledy-piggledy since a mktime() happens 482 * on this before anything gets printed. Also note that even though 483 * we've effectively gotten rid of f_dayBefore, we still have to check 484 * the one prior event for situations like "the 31st of every month" 485 * and "yearly" events which could happen twice in one year but not in 486 * the next */ 487 tmp2 = matches; 488 for (i = -1; i < 2; i++) { 489 memcpy(&tmtmp, tp, sizeof(struct tm)); 490 tmtmp.tm_mday = dayp; 491 tmtmp.tm_mon = month = monthp - 1; 492 do { 493 v2 = 0; 494 switch (interval) { 495 case WEEKLY: 496 tmtmp.tm_mday += 7 * i; 497 break; 498 case MONTHLY: 499 month += i; 500 tmtmp.tm_mon = month; 501 switch(tmtmp.tm_mon) { 502 case -1: 503 tmtmp.tm_mon = month = 11; 504 tmtmp.tm_year--; 505 break; 506 case 12: 507 tmtmp.tm_mon = month = 0; 508 tmtmp.tm_year++; 509 break; 510 } 511 if (vwd) { 512 v1 = vwd; 513 variable_weekday(&v1, tmtmp.tm_mon + 1, 514 tmtmp.tm_year + 1900); 515 tmtmp.tm_mday = v1; 516 } else 517 tmtmp.tm_mday = dayp; 518 break; 519 case YEARLY: 520 default: 521 tmtmp.tm_year += i; 522 if (flags & F_SPECIAL) { 523 tmtmp.tm_mon = 0; /* Gee, mktime() is nice */ 524 tmtmp.tm_mday = spev[v1].getev(tmtmp.tm_year + 525 1900) + vwd; 526 } else if (vwd) { 527 v1 = vwd; 528 variable_weekday(&v1, tmtmp.tm_mon + 1, 529 tmtmp.tm_year + 1900); 530 tmtmp.tm_mday = v1; 531 } else { 532 /* Need the following to keep Feb 29 from 533 * becoming Mar 1 */ 534 tmtmp.tm_mday = dayp; 535 tmtmp.tm_mon = monthp - 1; 536 } 537 break; 538 } 539 /* How many days apart are we */ 540 if ((ttmp = mktime(&tmtmp)) == -1) 541 warnx("time out of range: %s", endp); 542 else { 543 tdiff = difftime(ttmp, f_time)/ SECSPERDAY; 544 if (tdiff <= offset + f_dayAfter || 545 (bodun && tdiff == -1)) { 546 if (((tmtmp.tm_mon == month) || 547 (flags & F_SPECIAL) || 548 (interval == WEEKLY)) && 549 (tdiff >= 0 || 550 (bodun && tdiff == -1))) { 551 if ((tmp = malloc(sizeof(struct match))) == NULL) 552 err(1, NULL); 553 tmp->when = ttmp; 554 fill_print_date(tmp, &tmtmp); 555 tmp->bodun = bodun && tdiff == -1; 556 tmp->var = varp; 557 tmp->next = NULL; 558 if (tmp2) 559 tmp2->next = tmp; 560 else 561 matches = tmp; 562 tmp2 = tmp; 563 v2 = (i == 1) ? 1 : 0; 564 } 565 } else 566 i = 2; /* No point checking in the future */ 567 } 568 } while (v2 != 0); 569 } 570 return (matches); 571 } 572 573 574 int 575 getmonth(char *s) 576 { 577 char **p; 578 struct fixs *n; 579 580 for (n = fnmonths; n->name; ++n) 581 if (!strncasecmp(s, n->name, n->len)) 582 return ((n - fnmonths) + 1); 583 for (n = nmonths; n->name; ++n) 584 if (!strncasecmp(s, n->name, n->len)) 585 return ((n - nmonths) + 1); 586 for (p = months; *p; ++p) 587 if (!strncasecmp(s, *p, 3)) 588 return ((p - months) + 1); 589 return (0); 590 } 591 592 593 int 594 getday(char *s) 595 { 596 char **p; 597 struct fixs *n; 598 599 for (n = fndays; n->name; ++n) 600 if (!strncasecmp(s, n->name, n->len)) 601 return ((n - fndays) + 1); 602 for (n = ndays; n->name; ++n) 603 if (!strncasecmp(s, n->name, n->len)) 604 return ((n - ndays) + 1); 605 for (p = days; *p; ++p) 606 if (!strncasecmp(s, *p, 3)) 607 return ((p - days) + 1); 608 return (0); 609 } 610 611 /* return offset for variable weekdays 612 * -1 -> last weekday in month 613 * +1 -> first weekday in month 614 * ... etc ... 615 */ 616 int 617 getdayvar(char *s) 618 { 619 int offset; 620 621 622 offset = strlen(s); 623 624 /* Sun+1 or Wednesday-2 625 * ^ ^ */ 626 627 /* printf ("x: %s %s %d\n", s, s + offset - 2, offset); */ 628 switch(*(s + offset - 2)) { 629 case '-': 630 case '+': 631 return(atoi(s + offset - 2)); 632 break; 633 } 634 635 /* 636 * some aliases: last, first, second, third, fourth 637 */ 638 639 /* last */ 640 if (offset > 4 && !strcasecmp(s + offset - 4, "last")) 641 return(-1); 642 else if (offset > 5 && !strcasecmp(s + offset - 5, "first")) 643 return(+1); 644 else if (offset > 6 && !strcasecmp(s + offset - 6, "second")) 645 return(+2); 646 else if (offset > 5 && !strcasecmp(s + offset - 5, "third")) 647 return(+3); 648 else if (offset > 6 && !strcasecmp(s + offset - 6, "fourth")) 649 return(+4); 650 651 /* no offset detected */ 652 return(0); 653 } 654 655 656 int 657 foy(int year) 658 { 659 /* 0-6; what weekday Jan 1 is */ 660 year--; 661 return ((1 - year/100 + year/400 + (int)(365.25 * year)) % 7); 662 } 663 664 665 666 void 667 variable_weekday(int *day, int month, int year) 668 { 669 int v1, v2; 670 int *cumdays; 671 int day1; 672 673 if (isleap(year)) 674 cumdays = daytab[1]; 675 else 676 cumdays = daytab[0]; 677 day1 = foy(year); 678 /* negative offset; last, -4 .. -1 */ 679 if (*day < 0) { 680 v1 = *day/10 - 1; /* offset -4 ... -1 */ 681 *day = 10 + (*day % 10); /* day 1 ... 7 */ 682 683 /* which weekday the end of the month is (1-7) */ 684 v2 = (cumdays[month + 1] + day1) % 7 + 1; 685 686 /* and subtract enough days */ 687 *day = cumdays[month + 1] - cumdays[month] + 688 (v1 + 1) * 7 - (v2 - *day + 7) % 7; 689 #if DEBUG 690 fprintf(stderr, "\nMonth %d ends on weekday %d\n", month, v2); 691 #endif 692 } 693 694 /* first, second ... +1 ... +5 */ 695 else { 696 v1 = *day/10; /* offset */ 697 *day = *day % 10; 698 699 /* which weekday the first of the month is (1-7) */ 700 v2 = (cumdays[month] + 1 + day1) % 7 + 1; 701 702 /* and add enough days */ 703 *day = 1 + (v1 - 1) * 7 + (*day - v2 + 7) % 7; 704 #if DEBUG 705 fprintf(stderr, "\nMonth %d starts on weekday %d\n", month, v2); 706 #endif 707 } 708 } 709