1 /*- 2 * parsetime.c - parse time for at(1) 3 * 4 * SPDX-License-Identifier: BSD-2-Clause 5 * 6 * Copyright (C) 1993, 1994 Thomas Koenig 7 * 8 * modifications for English-language times 9 * Copyright (C) 1993 David Parsons 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 1. Redistributions of source code must retain the above copyright 15 * notice, this list of conditions and the following disclaimer. 16 * 2. The name of the author(s) may not be used to endorse or promote 17 * products derived from this software without specific prior written 18 * permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 21 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 22 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 23 * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 24 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 25 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 29 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 * 31 * at [NOW] PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS 32 * /NUMBER [DOT NUMBER] [AM|PM]\ /[MONTH NUMBER [NUMBER]] \ 33 * |NOON | |[TOMORROW] | 34 * |MIDNIGHT | |[DAY OF WEEK] | 35 * \TEATIME / |NUMBER [SLASH NUMBER [SLASH NUMBER]]| 36 * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/ 37 */ 38 39 #include <sys/cdefs.h> 40 __FBSDID("$FreeBSD$"); 41 42 /* System Headers */ 43 44 #include <sys/types.h> 45 #include <ctype.h> 46 #include <err.h> 47 #include <errno.h> 48 #include <stdio.h> 49 #include <stdlib.h> 50 #include <string.h> 51 #include <time.h> 52 #include <unistd.h> 53 #ifndef __FreeBSD__ 54 #include <getopt.h> 55 #endif 56 57 /* Local headers */ 58 59 #include "at.h" 60 #include "panic.h" 61 #include "parsetime.h" 62 63 64 /* Structures and unions */ 65 66 enum { /* symbols */ 67 MIDNIGHT, NOON, TEATIME, 68 PM, AM, TOMORROW, TODAY, NOW, 69 MINUTES, HOURS, DAYS, WEEKS, MONTHS, YEARS, 70 NUMBER, PLUS, MINUS, DOT, SLASH, ID, JUNK, 71 JAN, FEB, MAR, APR, MAY, JUN, 72 JUL, AUG, SEP, OCT, NOV, DEC, 73 SUN, MON, TUE, WED, THU, FRI, SAT 74 }; 75 76 /* parse translation table - table driven parsers can be your FRIEND! 77 */ 78 static const struct { 79 const char *name; /* token name */ 80 int value; /* token id */ 81 int plural; /* is this plural? */ 82 } Specials[] = { 83 { "midnight", MIDNIGHT,0 }, /* 00:00:00 of today or tomorrow */ 84 { "noon", NOON,0 }, /* 12:00:00 of today or tomorrow */ 85 { "teatime", TEATIME,0 }, /* 16:00:00 of today or tomorrow */ 86 { "am", AM,0 }, /* morning times for 0-12 clock */ 87 { "pm", PM,0 }, /* evening times for 0-12 clock */ 88 { "tomorrow", TOMORROW,0 }, /* execute 24 hours from time */ 89 { "today", TODAY, 0 }, /* execute today - don't advance time */ 90 { "now", NOW,0 }, /* opt prefix for PLUS */ 91 92 { "minute", MINUTES,0 }, /* minutes multiplier */ 93 { "minutes", MINUTES,1 }, /* (pluralized) */ 94 { "hour", HOURS,0 }, /* hours ... */ 95 { "hours", HOURS,1 }, /* (pluralized) */ 96 { "day", DAYS,0 }, /* days ... */ 97 { "days", DAYS,1 }, /* (pluralized) */ 98 { "week", WEEKS,0 }, /* week ... */ 99 { "weeks", WEEKS,1 }, /* (pluralized) */ 100 { "month", MONTHS,0 }, /* month ... */ 101 { "months", MONTHS,1 }, /* (pluralized) */ 102 { "year", YEARS,0 }, /* year ... */ 103 { "years", YEARS,1 }, /* (pluralized) */ 104 { "jan", JAN,0 }, 105 { "feb", FEB,0 }, 106 { "mar", MAR,0 }, 107 { "apr", APR,0 }, 108 { "may", MAY,0 }, 109 { "jun", JUN,0 }, 110 { "jul", JUL,0 }, 111 { "aug", AUG,0 }, 112 { "sep", SEP,0 }, 113 { "oct", OCT,0 }, 114 { "nov", NOV,0 }, 115 { "dec", DEC,0 }, 116 { "january", JAN,0 }, 117 { "february", FEB,0 }, 118 { "march", MAR,0 }, 119 { "april", APR,0 }, 120 { "may", MAY,0 }, 121 { "june", JUN,0 }, 122 { "july", JUL,0 }, 123 { "august", AUG,0 }, 124 { "september", SEP,0 }, 125 { "october", OCT,0 }, 126 { "november", NOV,0 }, 127 { "december", DEC,0 }, 128 { "sunday", SUN, 0 }, 129 { "sun", SUN, 0 }, 130 { "monday", MON, 0 }, 131 { "mon", MON, 0 }, 132 { "tuesday", TUE, 0 }, 133 { "tue", TUE, 0 }, 134 { "wednesday", WED, 0 }, 135 { "wed", WED, 0 }, 136 { "thursday", THU, 0 }, 137 { "thu", THU, 0 }, 138 { "friday", FRI, 0 }, 139 { "fri", FRI, 0 }, 140 { "saturday", SAT, 0 }, 141 { "sat", SAT, 0 }, 142 } ; 143 144 /* File scope variables */ 145 146 static char **scp; /* scanner - pointer at arglist */ 147 static char scc; /* scanner - count of remaining arguments */ 148 static char *sct; /* scanner - next char pointer in current argument */ 149 static int need; /* scanner - need to advance to next argument */ 150 151 static char *sc_token; /* scanner - token buffer */ 152 static size_t sc_len; /* scanner - length of token buffer */ 153 static int sc_tokid; /* scanner - token id */ 154 static int sc_tokplur; /* scanner - is token plural? */ 155 156 /* Local functions */ 157 158 /* 159 * parse a token, checking if it's something special to us 160 */ 161 static int 162 parse_token(char *arg) 163 { 164 size_t i; 165 166 for (i=0; i<(sizeof Specials/sizeof Specials[0]); i++) 167 if (strcasecmp(Specials[i].name, arg) == 0) { 168 sc_tokplur = Specials[i].plural; 169 return sc_tokid = Specials[i].value; 170 } 171 172 /* not special - must be some random id */ 173 return ID; 174 } /* parse_token */ 175 176 177 /* 178 * init_scanner() sets up the scanner to eat arguments 179 */ 180 static void 181 init_scanner(int argc, char **argv) 182 { 183 scp = argv; 184 scc = argc; 185 need = 1; 186 sc_len = 1; 187 while (argc-- > 0) 188 sc_len += strlen(*argv++); 189 190 if ((sc_token = malloc(sc_len)) == NULL) 191 errx(EXIT_FAILURE, "virtual memory exhausted"); 192 } /* init_scanner */ 193 194 /* 195 * token() fetches a token from the input stream 196 */ 197 static int 198 token(void) 199 { 200 int idx; 201 202 while (1) { 203 memset(sc_token, 0, sc_len); 204 sc_tokid = EOF; 205 sc_tokplur = 0; 206 idx = 0; 207 208 /* if we need to read another argument, walk along the argument list; 209 * when we fall off the arglist, we'll just return EOF forever 210 */ 211 if (need) { 212 if (scc < 1) 213 return sc_tokid; 214 sct = *scp; 215 scp++; 216 scc--; 217 need = 0; 218 } 219 /* eat whitespace now - if we walk off the end of the argument, 220 * we'll continue, which puts us up at the top of the while loop 221 * to fetch the next argument in 222 */ 223 while (isspace(*sct)) 224 ++sct; 225 if (!*sct) { 226 need = 1; 227 continue; 228 } 229 230 /* preserve the first character of the new token 231 */ 232 sc_token[0] = *sct++; 233 234 /* then see what it is 235 */ 236 if (isdigit(sc_token[0])) { 237 while (isdigit(*sct)) 238 sc_token[++idx] = *sct++; 239 sc_token[++idx] = 0; 240 return sc_tokid = NUMBER; 241 } 242 else if (isalpha(sc_token[0])) { 243 while (isalpha(*sct)) 244 sc_token[++idx] = *sct++; 245 sc_token[++idx] = 0; 246 return parse_token(sc_token); 247 } 248 else if (sc_token[0] == ':' || sc_token[0] == '.') 249 return sc_tokid = DOT; 250 else if (sc_token[0] == '+') 251 return sc_tokid = PLUS; 252 else if (sc_token[0] == '-') 253 return sc_tokid = MINUS; 254 else if (sc_token[0] == '/') 255 return sc_tokid = SLASH; 256 else 257 return sc_tokid = JUNK; 258 } /* while (1) */ 259 } /* token */ 260 261 262 /* 263 * plonk() gives an appropriate error message if a token is incorrect 264 */ 265 static void 266 plonk(int tok) 267 { 268 panic((tok == EOF) ? "incomplete time" 269 : "garbled time"); 270 } /* plonk */ 271 272 273 /* 274 * expect() gets a token and dies most horribly if it's not the token we want 275 */ 276 static void 277 expect(int desired) 278 { 279 if (token() != desired) 280 plonk(sc_tokid); /* and we die here... */ 281 } /* expect */ 282 283 284 /* 285 * plus_or_minus() holds functionality common to plus() and minus() 286 */ 287 static void 288 plus_or_minus(struct tm *tm, int delay) 289 { 290 int expectplur; 291 292 expectplur = (delay != 1 && delay != -1) ? 1 : 0; 293 294 switch (token()) { 295 case YEARS: 296 tm->tm_year += delay; 297 break; 298 case MONTHS: 299 tm->tm_mon += delay; 300 break; 301 case WEEKS: 302 delay *= 7; 303 case DAYS: 304 tm->tm_mday += delay; 305 break; 306 case HOURS: 307 tm->tm_hour += delay; 308 break; 309 case MINUTES: 310 tm->tm_min += delay; 311 break; 312 default: 313 plonk(sc_tokid); 314 break; 315 } 316 317 if (expectplur != sc_tokplur) 318 warnx("pluralization is wrong"); 319 320 tm->tm_isdst = -1; 321 if (mktime(tm) < 0) 322 plonk(sc_tokid); 323 } /* plus_or_minus */ 324 325 326 /* 327 * plus() parses a now + time 328 * 329 * at [NOW] PLUS NUMBER [MINUTES|HOURS|DAYS|WEEKS|MONTHS|YEARS] 330 * 331 */ 332 static void 333 plus(struct tm *tm) 334 { 335 int delay; 336 337 expect(NUMBER); 338 339 delay = atoi(sc_token); 340 plus_or_minus(tm, delay); 341 } /* plus */ 342 343 344 /* 345 * minus() is like plus but can not be used with NOW 346 */ 347 static void 348 minus(struct tm *tm) 349 { 350 int delay; 351 352 expect(NUMBER); 353 354 delay = -atoi(sc_token); 355 plus_or_minus(tm, delay); 356 } /* minus */ 357 358 359 /* 360 * tod() computes the time of day 361 * [NUMBER [DOT NUMBER] [AM|PM]] 362 */ 363 static void 364 tod(struct tm *tm) 365 { 366 int hour, minute = 0; 367 int tlen; 368 369 hour = atoi(sc_token); 370 tlen = strlen(sc_token); 371 372 /* first pick out the time of day - if it's 4 digits, we assume 373 * a HHMM time, otherwise it's HH DOT MM time 374 */ 375 if (token() == DOT) { 376 expect(NUMBER); 377 minute = atoi(sc_token); 378 if (minute > 59) 379 panic("garbled time"); 380 token(); 381 } 382 else if (tlen == 4) { 383 minute = hour%100; 384 if (minute > 59) 385 panic("garbled time"); 386 hour = hour/100; 387 } 388 389 /* check if an AM or PM specifier was given 390 */ 391 if (sc_tokid == AM || sc_tokid == PM) { 392 if (hour > 12) 393 panic("garbled time"); 394 395 if (sc_tokid == PM) { 396 if (hour != 12) /* 12:xx PM is 12:xx, not 24:xx */ 397 hour += 12; 398 } else { 399 if (hour == 12) /* 12:xx AM is 00:xx, not 12:xx */ 400 hour = 0; 401 } 402 token(); 403 } 404 else if (hour > 23) 405 panic("garbled time"); 406 407 /* if we specify an absolute time, we don't want to bump the day even 408 * if we've gone past that time - but if we're specifying a time plus 409 * a relative offset, it's okay to bump things 410 */ 411 if ((sc_tokid == EOF || sc_tokid == PLUS || sc_tokid == MINUS) && 412 tm->tm_hour > hour) { 413 tm->tm_mday++; 414 tm->tm_wday++; 415 } 416 417 tm->tm_hour = hour; 418 tm->tm_min = minute; 419 if (tm->tm_hour == 24) { 420 tm->tm_hour = 0; 421 tm->tm_mday++; 422 } 423 } /* tod */ 424 425 426 /* 427 * assign_date() assigns a date, wrapping to next year if needed 428 */ 429 static void 430 assign_date(struct tm *tm, long mday, long mon, long year) 431 { 432 433 /* 434 * Convert year into tm_year format (year - 1900). 435 * We may be given the year in 2 digit, 4 digit, or tm_year format. 436 */ 437 if (year != -1) { 438 if (year >= 1900) 439 year -= 1900; /* convert from 4 digit year */ 440 else if (year < 100) { 441 /* convert from 2 digit year */ 442 struct tm *lt; 443 time_t now; 444 445 time(&now); 446 lt = localtime(&now); 447 448 /* Convert to tm_year assuming current century */ 449 year += (lt->tm_year / 100) * 100; 450 451 if (year == lt->tm_year - 1) year++; 452 else if (year < lt->tm_year) 453 year += 100; /* must be in next century */ 454 } 455 } 456 457 if (year < 0 && 458 (tm->tm_mon > mon ||(tm->tm_mon == mon && tm->tm_mday > mday))) 459 year = tm->tm_year + 1; 460 461 tm->tm_mday = mday; 462 tm->tm_mon = mon; 463 464 if (year >= 0) 465 tm->tm_year = year; 466 } /* assign_date */ 467 468 469 /* 470 * month() picks apart a month specification 471 * 472 * /[<month> NUMBER [NUMBER]] \ 473 * |[TOMORROW] | 474 * |[DAY OF WEEK] | 475 * |NUMBER [SLASH NUMBER [SLASH NUMBER]]| 476 * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/ 477 */ 478 static void 479 month(struct tm *tm) 480 { 481 long year= (-1); 482 long mday = 0, wday, mon; 483 int tlen; 484 485 switch (sc_tokid) { 486 case PLUS: 487 plus(tm); 488 break; 489 case MINUS: 490 minus(tm); 491 break; 492 493 case TOMORROW: 494 /* do something tomorrow */ 495 tm->tm_mday ++; 496 tm->tm_wday ++; 497 case TODAY: /* force ourselves to stay in today - no further processing */ 498 token(); 499 break; 500 501 case JAN: case FEB: case MAR: case APR: case MAY: case JUN: 502 case JUL: case AUG: case SEP: case OCT: case NOV: case DEC: 503 /* do month mday [year] 504 */ 505 mon = (sc_tokid-JAN); 506 expect(NUMBER); 507 mday = atol(sc_token); 508 if (token() == NUMBER) { 509 year = atol(sc_token); 510 token(); 511 } 512 assign_date(tm, mday, mon, year); 513 break; 514 515 case SUN: case MON: case TUE: 516 case WED: case THU: case FRI: 517 case SAT: 518 /* do a particular day of the week 519 */ 520 wday = (sc_tokid-SUN); 521 522 mday = tm->tm_mday; 523 524 /* if this day is < today, then roll to next week 525 */ 526 if (wday < tm->tm_wday) 527 mday += 7 - (tm->tm_wday - wday); 528 else 529 mday += (wday - tm->tm_wday); 530 531 tm->tm_wday = wday; 532 533 assign_date(tm, mday, tm->tm_mon, tm->tm_year); 534 break; 535 536 case NUMBER: 537 /* get numeric MMDDYY, mm/dd/yy, or dd.mm.yy 538 */ 539 tlen = strlen(sc_token); 540 mon = atol(sc_token); 541 token(); 542 543 if (sc_tokid == SLASH || sc_tokid == DOT) { 544 int sep; 545 546 sep = sc_tokid; 547 expect(NUMBER); 548 mday = atol(sc_token); 549 if (token() == sep) { 550 expect(NUMBER); 551 year = atol(sc_token); 552 token(); 553 } 554 555 /* flip months and days for European timing 556 */ 557 if (sep == DOT) { 558 int x = mday; 559 mday = mon; 560 mon = x; 561 } 562 } 563 else if (tlen == 6 || tlen == 8) { 564 if (tlen == 8) { 565 year = (mon % 10000) - 1900; 566 mon /= 10000; 567 } 568 else { 569 year = mon % 100; 570 mon /= 100; 571 } 572 mday = mon % 100; 573 mon /= 100; 574 } 575 else 576 panic("garbled time"); 577 578 mon--; 579 if (mon < 0 || mon > 11 || mday < 1 || mday > 31) 580 panic("garbled time"); 581 582 assign_date(tm, mday, mon, year); 583 break; 584 } /* case */ 585 } /* month */ 586 587 588 /* Global functions */ 589 590 time_t 591 parsetime(int argc, char **argv) 592 { 593 /* Do the argument parsing, die if necessary, and return the time the job 594 * should be run. 595 */ 596 time_t nowtimer, runtimer; 597 struct tm nowtime, runtime; 598 int hr = 0; 599 /* this MUST be initialized to zero for midnight/noon/teatime */ 600 601 nowtimer = time(NULL); 602 nowtime = *localtime(&nowtimer); 603 604 runtime = nowtime; 605 runtime.tm_sec = 0; 606 runtime.tm_isdst = 0; 607 608 if (argc <= optind) 609 usage(); 610 611 init_scanner(argc-optind, argv+optind); 612 613 switch (token()) { 614 case NOW: 615 if (scc < 1) { 616 return nowtimer; 617 } 618 /* now is optional prefix for PLUS tree */ 619 expect(PLUS); 620 /* FALLTHROUGH */ 621 case PLUS: 622 plus(&runtime); 623 break; 624 625 /* MINUS is different from PLUS in that NOW is not 626 * an optional prefix for it 627 */ 628 case MINUS: 629 minus(&runtime); 630 break; 631 case NUMBER: 632 tod(&runtime); 633 month(&runtime); 634 break; 635 636 /* evil coding for TEATIME|NOON|MIDNIGHT - we've initialised 637 * hr to zero up above, then fall into this case in such a 638 * way so we add +12 +4 hours to it for teatime, +12 hours 639 * to it for noon, and nothing at all for midnight, then 640 * set our runtime to that hour before leaping into the 641 * month scanner 642 */ 643 case TEATIME: 644 hr += 4; 645 /* FALLTHROUGH */ 646 case NOON: 647 hr += 12; 648 /* FALLTHROUGH */ 649 case MIDNIGHT: 650 if (runtime.tm_hour >= hr) { 651 runtime.tm_mday++; 652 runtime.tm_wday++; 653 } 654 runtime.tm_hour = hr; 655 runtime.tm_min = 0; 656 token(); 657 /* FALLTHROUGH to month setting */ 658 default: 659 month(&runtime); 660 break; 661 } /* ugly case statement */ 662 expect(EOF); 663 664 /* convert back to time_t 665 */ 666 runtime.tm_isdst = -1; 667 runtimer = mktime(&runtime); 668 669 if (runtimer < 0) 670 panic("garbled time"); 671 672 if (nowtimer > runtimer) 673 panic("trying to travel back in time"); 674 675 return runtimer; 676 } /* parsetime */ 677