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