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