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