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