parsetime.c revision 38646
150880Smarkm/* 250880Smarkm * parsetime.c - parse time for at(1) 350880Smarkm * Copyright (C) 1993, 1994 Thomas Koenig 450880Smarkm * 550880Smarkm * modifications for english-language times 6147440Sru * Copyright (C) 1993 David Parsons 7206622Suqs * 850880Smarkm * Redistribution and use in source and binary forms, with or without 950880Smarkm * modification, are permitted provided that the following conditions 1050880Smarkm * are met: 1150880Smarkm * 1. Redistributions of source code must retain the above copyright 1250880Smarkm * notice, this list of conditions and the following disclaimer. 1350880Smarkm * 2. The name of the author(s) may not be used to endorse or promote 1450880Smarkm * products derived from this software without specific prior written 1550880Smarkm * permission. 1650880Smarkm * 1750880Smarkm * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 1850880Smarkm * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 1950880Smarkm * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 2050880Smarkm * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 2150880Smarkm * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 2250880Smarkm * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 2350880Smarkm * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 2450880Smarkm * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT 2570886Sru * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 2650880Smarkm * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2770886Sru * 2850880Smarkm * at [NOW] PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS 2970886Sru * /NUMBER [DOT NUMBER] [AM|PM]\ /[MONTH NUMBER [NUMBER]] \ 3050880Smarkm * |NOON | |[TOMORROW] | 3170886Sru * |MIDNIGHT | |[DAY OF WEEK] | 3270886Sru * \TEATIME / |NUMBER [SLASH NUMBER [SLASH NUMBER]]| 3350880Smarkm * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/ 3470886Sru */ 3550880Smarkm 3670886Sru/* System Headers */ 3750880Smarkm 3850880Smarkm 3950880Smarkm#include <sys/types.h> 4050880Smarkm#include <err.h> 4150880Smarkm#include <errno.h> 4250880Smarkm#include <stdio.h> 4350880Smarkm#include <stdlib.h> 4450880Smarkm#include <string.h> 4550880Smarkm#include <time.h> 4650880Smarkm#include <unistd.h> 4750880Smarkm#include <ctype.h> 4850880Smarkm#ifndef __FreeBSD__ 4950880Smarkm#include <getopt.h> 5050880Smarkm#endif 5150880Smarkm 5250880Smarkm/* Local headers */ 5350880Smarkm 5450880Smarkm#include "at.h" 5550880Smarkm#include "panic.h" 5650880Smarkm 57147440Sru 58147440Sru/* Structures and unions */ 5950880Smarkm 6050880Smarkmenum { /* symbols */ 6150880Smarkm MIDNIGHT, NOON, TEATIME, 6250880Smarkm PM, AM, TOMORROW, TODAY, NOW, 6350880Smarkm MINUTES, HOURS, DAYS, WEEKS, 6472532Sru NUMBER, PLUS, DOT, SLASH, ID, JUNK, 6550880Smarkm JAN, FEB, MAR, APR, MAY, JUN, 6650880Smarkm JUL, AUG, SEP, OCT, NOV, DEC, 6750880Smarkm SUN, MON, TUE, WED, THU, FRI, SAT 6850880Smarkm }; 6950880Smarkm 7050880Smarkm/* parse translation table - table driven parsers can be your FRIEND! 71162382Sru */ 72162382Srustruct { 73162382Sru char *name; /* token name */ 74162382Sru int value; /* token id */ 75162382Sru int plural; /* is this plural? */ 76162382Sru} 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 char rcsid[] = "$Id: parsetime.c,v 1.14 1998/08/08 14:02:06 alex Exp $"; 147 148/* Local functions */ 149 150/* 151 * parse a token, checking if it's something special to us 152 */ 153static int 154parse_token(char *arg) 155{ 156 int i; 157 158 for (i=0; i<(sizeof Specials/sizeof Specials[0]); i++) 159 if (strcasecmp(Specials[i].name, arg) == 0) { 160 sc_tokplur = Specials[i].plural; 161 return sc_tokid = Specials[i].value; 162 } 163 164 /* not special - must be some random id */ 165 return ID; 166} /* parse_token */ 167 168 169/* 170 * init_scanner() sets up the scanner to eat arguments 171 */ 172static void 173init_scanner(int argc, char **argv) 174{ 175 scp = argv; 176 scc = argc; 177 need = 1; 178 sc_len = 1; 179 while (argc-- > 0) 180 sc_len += strlen(*argv++); 181 182 sc_token = (char *) mymalloc(sc_len); 183} /* init_scanner */ 184 185/* 186 * token() fetches a token from the input stream 187 */ 188static int 189token() 190{ 191 int idx; 192 193 while (1) { 194 memset(sc_token, 0, sc_len); 195 sc_tokid = EOF; 196 sc_tokplur = 0; 197 idx = 0; 198 199 /* if we need to read another argument, walk along the argument list; 200 * when we fall off the arglist, we'll just return EOF forever 201 */ 202 if (need) { 203 if (scc < 1) 204 return sc_tokid; 205 sct = *scp; 206 scp++; 207 scc--; 208 need = 0; 209 } 210 /* eat whitespace now - if we walk off the end of the argument, 211 * we'll continue, which puts us up at the top of the while loop 212 * to fetch the next argument in 213 */ 214 while (isspace(*sct)) 215 ++sct; 216 if (!*sct) { 217 need = 1; 218 continue; 219 } 220 221 /* preserve the first character of the new token 222 */ 223 sc_token[0] = *sct++; 224 225 /* then see what it is 226 */ 227 if (isdigit(sc_token[0])) { 228 while (isdigit(*sct)) 229 sc_token[++idx] = *sct++; 230 sc_token[++idx] = 0; 231 return sc_tokid = NUMBER; 232 } 233 else if (isalpha(sc_token[0])) { 234 while (isalpha(*sct)) 235 sc_token[++idx] = *sct++; 236 sc_token[++idx] = 0; 237 return parse_token(sc_token); 238 } 239 else if (sc_token[0] == ':' || sc_token[0] == '.') 240 return sc_tokid = DOT; 241 else if (sc_token[0] == '+') 242 return sc_tokid = PLUS; 243 else if (sc_token[0] == '/') 244 return sc_tokid = SLASH; 245 else 246 return sc_tokid = JUNK; 247 } /* while (1) */ 248} /* token */ 249 250 251/* 252 * plonk() gives an appropriate error message if a token is incorrect 253 */ 254static void 255plonk(int tok) 256{ 257 panic((tok == EOF) ? "incomplete time" 258 : "garbled time"); 259} /* plonk */ 260 261 262/* 263 * expect() gets a token and dies most horribly if it's not the token we want 264 */ 265static void 266expect(int desired) 267{ 268 if (token() != desired) 269 plonk(sc_tokid); /* and we die here... */ 270} /* expect */ 271 272 273/* 274 * dateadd() adds a number of minutes to a date. It is extraordinarily 275 * stupid regarding day-of-month overflow, and will most likely not 276 * work properly 277 */ 278static void 279dateadd(int minutes, struct tm *tm) 280{ 281 /* increment days */ 282 283 while (minutes > 24*60) { 284 minutes -= 24*60; 285 tm->tm_mday++; 286 } 287 288 /* increment hours */ 289 while (minutes > 60) { 290 minutes -= 60; 291 tm->tm_hour++; 292 if (tm->tm_hour > 23) { 293 tm->tm_mday++; 294 tm->tm_hour = 0; 295 } 296 } 297 298 /* increment minutes */ 299 tm->tm_min += minutes; 300 301 if (tm->tm_min > 59) { 302 tm->tm_hour++; 303 tm->tm_min -= 60; 304 305 if (tm->tm_hour > 23) { 306 tm->tm_mday++; 307 tm->tm_hour = 0; 308 } 309 } 310} /* dateadd */ 311 312 313/* 314 * plus() parses a now + time 315 * 316 * at [NOW] PLUS NUMBER [MINUTES|HOURS|DAYS|WEEKS] 317 * 318 */ 319static void 320plus(struct tm *tm) 321{ 322 int delay; 323 int expectplur; 324 325 expect(NUMBER); 326 327 delay = atoi(sc_token); 328 expectplur = (delay != 1) ? 1 : 0; 329 330 switch (token()) { 331 case WEEKS: 332 delay *= 7; 333 case DAYS: 334 delay *= 24; 335 case HOURS: 336 delay *= 60; 337 case MINUTES: 338 if (expectplur != sc_tokplur) 339 warnx("pluralization is wrong"); 340 dateadd(delay, tm); 341 return; 342 } 343 plonk(sc_tokid); 344} /* plus */ 345 346 347/* 348 * tod() computes the time of day 349 * [NUMBER [DOT NUMBER] [AM|PM]] 350 */ 351static void 352tod(struct tm *tm) 353{ 354 int hour, minute = 0; 355 int tlen; 356 357 hour = atoi(sc_token); 358 tlen = strlen(sc_token); 359 360 /* first pick out the time of day - if it's 4 digits, we assume 361 * a HHMM time, otherwise it's HH DOT MM time 362 */ 363 if (token() == DOT) { 364 expect(NUMBER); 365 minute = atoi(sc_token); 366 if (minute > 59) 367 panic("garbled time"); 368 token(); 369 } 370 else if (tlen == 4) { 371 minute = hour%100; 372 if (minute > 59) 373 panic("garbled time"); 374 hour = hour/100; 375 } 376 377 /* check if an AM or PM specifier was given 378 */ 379 if (sc_tokid == AM || sc_tokid == PM) { 380 if (hour > 12) 381 panic("garbled time"); 382 383 if (sc_tokid == PM) { 384 if (hour != 12) /* 12:xx PM is 12:xx, not 24:xx */ 385 hour += 12; 386 } else { 387 if (hour == 12) /* 12:xx AM is 00:xx, not 12:xx */ 388 hour = 0; 389 } 390 token(); 391 } 392 else if (hour > 23) 393 panic("garbled time"); 394 395 /* if we specify an absolute time, we don't want to bump the day even 396 * if we've gone past that time - but if we're specifying a time plus 397 * a relative offset, it's okay to bump things 398 */ 399 if ((sc_tokid == EOF || sc_tokid == PLUS) && tm->tm_hour > hour) { 400 tm->tm_mday++; 401 tm->tm_wday++; 402 } 403 404 tm->tm_hour = hour; 405 tm->tm_min = minute; 406 if (tm->tm_hour == 24) { 407 tm->tm_hour = 0; 408 tm->tm_mday++; 409 } 410} /* tod */ 411 412 413/* 414 * assign_date() assigns a date, wrapping to next year if needed 415 */ 416static void 417assign_date(struct tm *tm, long mday, long mon, long year) 418{ 419 if (year > 99) { 420 if (year > 1899) 421 year -= 1900; 422 else 423 panic("garbled time"); 424 } else if (year != -1) { 425 struct tm *lt; 426 time_t now; 427 428 time(&now); 429 lt = localtime(&now); 430 431 /* 432 * check if the specified year is in the next century. 433 * allow for one year of user error as many people will 434 * enter n - 1 at the start of year n. 435 */ 436 if (year < (lt->tm_year % 100) - 1) 437 year += 100; 438 /* adjust for the year 2000 and beyond */ 439 year += lt->tm_year - (lt->tm_year % 100); 440 } 441 442 if (year < 0 && 443 (tm->tm_mon > mon ||(tm->tm_mon == mon && tm->tm_mday > mday))) 444 year = tm->tm_year + 1; 445 446 tm->tm_mday = mday; 447 tm->tm_mon = mon; 448 449 if (year >= 0) 450 tm->tm_year = year; 451} /* assign_date */ 452 453 454/* 455 * month() picks apart a month specification 456 * 457 * /[<month> NUMBER [NUMBER]] \ 458 * |[TOMORROW] | 459 * |[DAY OF WEEK] | 460 * |NUMBER [SLASH NUMBER [SLASH NUMBER]]| 461 * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/ 462 */ 463static void 464month(struct tm *tm) 465{ 466 long year= (-1); 467 long mday, wday, mon; 468 int tlen; 469 470 switch (sc_tokid) { 471 case PLUS: 472 plus(tm); 473 break; 474 475 case TOMORROW: 476 /* do something tomorrow */ 477 tm->tm_mday ++; 478 tm->tm_wday ++; 479 case TODAY: /* force ourselves to stay in today - no further processing */ 480 token(); 481 break; 482 483 case JAN: case FEB: case MAR: case APR: case MAY: case JUN: 484 case JUL: case AUG: case SEP: case OCT: case NOV: case DEC: 485 /* do month mday [year] 486 */ 487 mon = (sc_tokid-JAN); 488 expect(NUMBER); 489 mday = atol(sc_token); 490 if (token() == NUMBER) { 491 year = atol(sc_token); 492 token(); 493 } 494 assign_date(tm, mday, mon, year); 495 break; 496 497 case SUN: case MON: case TUE: 498 case WED: case THU: case FRI: 499 case SAT: 500 /* do a particular day of the week 501 */ 502 wday = (sc_tokid-SUN); 503 504 mday = tm->tm_mday; 505 506 /* if this day is < today, then roll to next week 507 */ 508 if (wday < tm->tm_wday) 509 mday += 7 - (tm->tm_wday - wday); 510 else 511 mday += (wday - tm->tm_wday); 512 513 tm->tm_wday = wday; 514 515 assign_date(tm, mday, tm->tm_mon, tm->tm_year); 516 break; 517 518 case NUMBER: 519 /* get numeric MMDDYY, mm/dd/yy, or dd.mm.yy 520 */ 521 tlen = strlen(sc_token); 522 mon = atol(sc_token); 523 token(); 524 525 if (sc_tokid == SLASH || sc_tokid == DOT) { 526 int sep; 527 528 sep = sc_tokid; 529 expect(NUMBER); 530 mday = atol(sc_token); 531 if (token() == sep) { 532 expect(NUMBER); 533 year = atol(sc_token); 534 token(); 535 } 536 537 /* flip months and days for european timing 538 */ 539 if (sep == DOT) { 540 int x = mday; 541 mday = mon; 542 mon = x; 543 } 544 } 545 else if (tlen == 6 || tlen == 8) { 546 if (tlen == 8) { 547 year = (mon % 10000) - 1900; 548 mon /= 10000; 549 } 550 else { 551 year = mon % 100; 552 mon /= 100; 553 } 554 mday = mon % 100; 555 mon /= 100; 556 } 557 else 558 panic("garbled time"); 559 560 mon--; 561 if (mon < 0 || mon > 11 || mday < 1 || mday > 31) 562 panic("garbled time"); 563 564 assign_date(tm, mday, mon, year); 565 break; 566 } /* case */ 567} /* month */ 568 569 570/* Global functions */ 571 572time_t 573parsetime(int argc, char **argv) 574{ 575/* Do the argument parsing, die if necessary, and return the time the job 576 * should be run. 577 */ 578 time_t nowtimer, runtimer; 579 struct tm nowtime, runtime; 580 int hr = 0; 581 /* this MUST be initialized to zero for midnight/noon/teatime */ 582 583 nowtimer = time(NULL); 584 nowtime = *localtime(&nowtimer); 585 586 runtime = nowtime; 587 runtime.tm_sec = 0; 588 runtime.tm_isdst = 0; 589 590 if (argc <= optind) 591 usage(); 592 593 init_scanner(argc-optind, argv+optind); 594 595 switch (token()) { 596 case NOW: /* now is optional prefix for PLUS tree */ 597 expect(PLUS); 598 case PLUS: 599 plus(&runtime); 600 break; 601 602 case NUMBER: 603 tod(&runtime); 604 month(&runtime); 605 break; 606 607 /* evil coding for TEATIME|NOON|MIDNIGHT - we've initialised 608 * hr to zero up above, then fall into this case in such a 609 * way so we add +12 +4 hours to it for teatime, +12 hours 610 * to it for noon, and nothing at all for midnight, then 611 * set our runtime to that hour before leaping into the 612 * month scanner 613 */ 614 case TEATIME: 615 hr += 4; 616 case NOON: 617 hr += 12; 618 case MIDNIGHT: 619 if (runtime.tm_hour >= hr) { 620 runtime.tm_mday++; 621 runtime.tm_wday++; 622 } 623 runtime.tm_hour = hr; 624 runtime.tm_min = 0; 625 token(); 626 /* fall through to month setting */ 627 default: 628 month(&runtime); 629 break; 630 } /* ugly case statement */ 631 expect(EOF); 632 633 /* adjust for daylight savings time 634 */ 635 runtime.tm_isdst = -1; 636 runtimer = mktime(&runtime); 637 if (runtime.tm_isdst > 0) { 638 runtimer -= 3600; 639 runtimer = mktime(&runtime); 640 } 641 642 if (runtimer < 0) 643 panic("garbled time"); 644 645 if (nowtimer > runtimer) 646 panic("Trying to travel back in time"); 647 648 return runtimer; 649} /* parsetime */ 650