parsetime.c revision 35729
1258343Sdes/* 298675Sdes * parsetime.c - parse time for at(1) 398675Sdes * Copyright (C) 1993, 1994 Thomas Koenig 498675Sdes * 598675Sdes * modifications for english-language times 698675Sdes * Copyright (C) 1993 David Parsons 798675Sdes * 898675Sdes * Redistribution and use in source and binary forms, with or without 998675Sdes * modification, are permitted provided that the following conditions 1098675Sdes * are met: 1198675Sdes * 1. Redistributions of source code must retain the above copyright 1298675Sdes * notice, this list of conditions and the following disclaimer. 1398675Sdes * 2. The name of the author(s) may not be used to endorse or promote 1498675Sdes * products derived from this software without specific prior written 1598675Sdes * permission. 1698675Sdes * 1798675Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 1898675Sdes * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 1998675Sdes * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 2098675Sdes * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 2198675Sdes * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 2298675Sdes * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 2398675Sdes * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 2498675Sdes * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT 2598675Sdes * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 2698675Sdes * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2798675Sdes * 2898675Sdes * at [NOW] PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS 29162852Sdes * /NUMBER [DOT NUMBER] [AM|PM]\ /[MONTH NUMBER [NUMBER]] \ 3098937Sdes * |NOON | |[TOMORROW] | 3198675Sdes * |MIDNIGHT | |[DAY OF WEEK] | 3298937Sdes * \TEATIME / |NUMBER [SLASH NUMBER [SLASH NUMBER]]| 33162852Sdes * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/ 34162852Sdes */ 3598675Sdes 36162852Sdes/* System Headers */ 37162852Sdes 38255767Sdes 39162852Sdes#include <sys/types.h> 40162852Sdes#include <err.h> 41162852Sdes#include <errno.h> 4298675Sdes#include <stdio.h> 4398675Sdes#include <stdlib.h> 4498675Sdes#include <string.h> 4598675Sdes#include <time.h> 4698675Sdes#include <unistd.h> 4798675Sdes#include <ctype.h> 4898675Sdes#ifndef __FreeBSD__ 49106121Sdes#include <getopt.h> 50106121Sdes#endif 51106121Sdes 52106121Sdes/* Local headers */ 53106121Sdes 54106121Sdes#include "at.h" 55106121Sdes#include "panic.h" 56106121Sdes 5798675Sdes 5898675Sdes/* Structures and unions */ 5998675Sdes 6098675Sdesenum { /* symbols */ 6198675Sdes MIDNIGHT, NOON, TEATIME, 6298675Sdes PM, AM, TOMORROW, TODAY, NOW, 6398675Sdes MINUTES, HOURS, DAYS, WEEKS, 6498675Sdes NUMBER, PLUS, DOT, SLASH, ID, JUNK, 6598675Sdes JAN, FEB, MAR, APR, MAY, JUN, 6698675Sdes JUL, AUG, SEP, OCT, NOV, DEC, 6798675Sdes SUN, MON, TUE, WED, THU, FRI, SAT 68258343Sdes }; 6998675Sdes 7098675Sdes/* parse translation table - table driven parsers can be your FRIEND! 7198675Sdes */ 7298675Sdesstruct { 7398675Sdes char *name; /* token name */ 7498675Sdes int value; /* token id */ 7598675Sdes int plural; /* is this plural? */ 7698675Sdes} Specials[] = { 7798675Sdes { "midnight", MIDNIGHT,0 }, /* 00:00:00 of today or tomorrow */ 7898675Sdes { "noon", NOON,0 }, /* 12:00:00 of today or tomorrow */ 7998675Sdes { "teatime", TEATIME,0 }, /* 16:00:00 of today or tomorrow */ 8098675Sdes { "am", AM,0 }, /* morning times for 0-12 clock */ 8198675Sdes { "pm", PM,0 }, /* evening times for 0-12 clock */ 8298675Sdes { "tomorrow", TOMORROW,0 }, /* execute 24 hours from time */ 8398675Sdes { "today", TODAY, 0 }, /* execute today - don't advance time */ 8498675Sdes { "now", NOW,0 }, /* opt prefix for PLUS */ 8598675Sdes 8698675Sdes { "minute", MINUTES,0 }, /* minutes multiplier */ 8798675Sdes { "minutes", MINUTES,1 }, /* (pluralized) */ 8898675Sdes { "hour", HOURS,0 }, /* hours ... */ 8998675Sdes { "hours", HOURS,1 }, /* (pluralized) */ 9098675Sdes { "day", DAYS,0 }, /* days ... */ 91258343Sdes { "days", DAYS,1 }, /* (pluralized) */ 9298675Sdes { "week", WEEKS,0 }, /* week ... */ 9398675Sdes { "weeks", WEEKS,1 }, /* (pluralized) */ 9498675Sdes { "jan", JAN,0 }, 9598675Sdes { "feb", FEB,0 }, 9698675Sdes { "mar", MAR,0 }, 9798675Sdes { "apr", APR,0 }, 9898675Sdes { "may", MAY,0 }, 9998675Sdes { "jun", JUN,0 }, 10098675Sdes { "jul", JUL,0 }, 10198675Sdes { "aug", AUG,0 }, 102106121Sdes { "sep", SEP,0 }, 103146998Sdes { "oct", OCT,0 }, 10498675Sdes { "nov", NOV,0 }, 10598675Sdes { "dec", DEC,0 }, 10698675Sdes { "sunday", SUN, 0 }, 10798675Sdes { "sun", SUN, 0 }, 10898675Sdes { "monday", MON, 0 }, 10998675Sdes { "mon", MON, 0 }, 11098675Sdes { "tuesday", TUE, 0 }, 11198675Sdes { "tue", TUE, 0 }, 11298675Sdes { "wednesday", WED, 0 }, 11398675Sdes { "wed", WED, 0 }, 11498675Sdes { "thursday", THU, 0 }, 11598675Sdes { "thu", THU, 0 }, 11698675Sdes { "friday", FRI, 0 }, 11798675Sdes { "fri", FRI, 0 }, 11898675Sdes { "saturday", SAT, 0 }, 11998675Sdes { "sat", SAT, 0 }, 12098675Sdes} ; 12198675Sdes 12298675Sdes/* File scope variables */ 12398675Sdes 12498675Sdesstatic char **scp; /* scanner - pointer at arglist */ 12598675Sdesstatic char scc; /* scanner - count of remaining arguments */ 12698675Sdesstatic char *sct; /* scanner - next char pointer in current argument */ 12798675Sdesstatic int need; /* scanner - need to advance to next argument */ 128255767Sdes 12998675Sdesstatic char *sc_token; /* scanner - token buffer */ 13098675Sdesstatic size_t sc_len; /* scanner - lenght of token buffer */ 13198675Sdesstatic int sc_tokid; /* scanner - token id */ 13298675Sdesstatic int sc_tokplur; /* scanner - is token plural? */ 13398675Sdes 13498675Sdesstatic char rcsid[] = "$Id: parsetime.c,v 1.11 1997/06/24 06:26:32 charnier Exp $"; 13598675Sdes 13698675Sdes/* Local functions */ 13798675Sdes 13898675Sdes/* 13998675Sdes * parse a token, checking if it's something special to us 14098675Sdes */ 14198675Sdesstatic int 142106121Sdesparse_token(char *arg) 14398675Sdes{ 14498675Sdes int i; 14598675Sdes 14698937Sdes for (i=0; i<(sizeof Specials/sizeof Specials[0]); i++) 14799060Sdes if (strcasecmp(Specials[i].name, arg) == 0) { 14898937Sdes sc_tokplur = Specials[i].plural; 14998937Sdes return sc_tokid = Specials[i].value; 15098675Sdes } 151255767Sdes 15298675Sdes /* not special - must be some random id */ 15398675Sdes return ID; 15498675Sdes} /* parse_token */ 15598675Sdes 15698675Sdes 15798675Sdes/* 15898675Sdes * init_scanner() sets up the scanner to eat arguments 15998675Sdes */ 16098675Sdesstatic void 16198675Sdesinit_scanner(int argc, char **argv) 16298675Sdes{ 16398675Sdes scp = argv; 164258343Sdes scc = argc; 16598675Sdes need = 1; 16698675Sdes sc_len = 1; 16798675Sdes while (argc-- > 0) 16898675Sdes sc_len += strlen(*argv++); 16998675Sdes 17098675Sdes sc_token = (char *) mymalloc(sc_len); 17198675Sdes} /* init_scanner */ 17298675Sdes 17398675Sdes/* 17498675Sdes * token() fetches a token from the input stream 17598675Sdes */ 17698675Sdesstatic int 17798675Sdestoken() 178106121Sdes{ 179106121Sdes int idx; 18098675Sdes 181106121Sdes while (1) { 18298675Sdes memset(sc_token, 0, sc_len); 18398675Sdes sc_tokid = EOF; 18498675Sdes sc_tokplur = 0; 18598675Sdes idx = 0; 18698675Sdes 18798675Sdes /* if we need to read another argument, walk along the argument list; 18898675Sdes * when we fall off the arglist, we'll just return EOF forever 18998675Sdes */ 19098675Sdes if (need) { 19198675Sdes if (scc < 1) 19298675Sdes return sc_tokid; 19398675Sdes sct = *scp; 19498675Sdes scp++; 19598675Sdes scc--; 19698675Sdes need = 0; 19798675Sdes } 19898675Sdes /* eat whitespace now - if we walk off the end of the argument, 19998675Sdes * we'll continue, which puts us up at the top of the while loop 20098675Sdes * to fetch the next argument in 20198675Sdes */ 20298675Sdes while (isspace(*sct)) 203255767Sdes ++sct; 20498675Sdes if (!*sct) { 20598675Sdes need = 1; 20698675Sdes continue; 20798675Sdes } 20898675Sdes 20998675Sdes /* preserve the first character of the new token 21098675Sdes */ 21198675Sdes sc_token[0] = *sct++; 21298675Sdes 21398675Sdes /* then see what it is 21498675Sdes */ 21598675Sdes if (isdigit(sc_token[0])) { 21698675Sdes while (isdigit(*sct)) 21798675Sdes sc_token[++idx] = *sct++; 21898675Sdes sc_token[++idx] = 0; 21998675Sdes return sc_tokid = NUMBER; 22098675Sdes } 22198675Sdes else if (isalpha(sc_token[0])) { 22298675Sdes while (isalpha(*sct)) 22398675Sdes sc_token[++idx] = *sct++; 22498675Sdes sc_token[++idx] = 0; 22598675Sdes return parse_token(sc_token); 22698675Sdes } 22798675Sdes else if (sc_token[0] == ':' || sc_token[0] == '.') 22898675Sdes return sc_tokid = DOT; 22998675Sdes else if (sc_token[0] == '+') 23098675Sdes return sc_tokid = PLUS; 23198675Sdes else if (sc_token[0] == '/') 23298675Sdes return sc_tokid = SLASH; 23398675Sdes else 23498675Sdes return sc_tokid = JUNK; 23598675Sdes } /* while (1) */ 23698675Sdes} /* token */ 23798675Sdes 23898675Sdes 23998675Sdes/* 24098675Sdes * plonk() gives an appropriate error message if a token is incorrect 24198675Sdes */ 24298675Sdesstatic void 24398675Sdesplonk(int tok) 24498675Sdes{ 24598675Sdes panic((tok == EOF) ? "incomplete time" 24698675Sdes : "garbled time"); 24798675Sdes} /* plonk */ 24898675Sdes 24998675Sdes 25098675Sdes/* 25198675Sdes * expect() gets a token and dies most horribly if it's not the token we want 25298675Sdes */ 25398675Sdesstatic void 25498675Sdesexpect(int desired) 25598675Sdes{ 25698675Sdes if (token() != desired) 25798675Sdes plonk(sc_tokid); /* and we die here... */ 25898675Sdes} /* expect */ 259255767Sdes 26098675Sdes 26198675Sdes/* 26298675Sdes * dateadd() adds a number of minutes to a date. It is extraordinarily 26398675Sdes * stupid regarding day-of-month overflow, and will most likely not 26498675Sdes * work properly 26598675Sdes */ 26698675Sdesstatic void 26798675Sdesdateadd(int minutes, struct tm *tm) 26898675Sdes{ 26998675Sdes /* increment days */ 27098675Sdes 27198675Sdes while (minutes > 24*60) { 27298675Sdes minutes -= 24*60; 27398675Sdes tm->tm_mday++; 27498675Sdes } 27598675Sdes 27698675Sdes /* increment hours */ 27798675Sdes while (minutes > 60) { 27898675Sdes minutes -= 60; 27998675Sdes tm->tm_hour++; 28098675Sdes if (tm->tm_hour > 23) { 28198675Sdes tm->tm_mday++; 28298675Sdes tm->tm_hour = 0; 283255767Sdes } 28498675Sdes } 28598675Sdes 28698675Sdes /* increment minutes */ 28798675Sdes tm->tm_min += minutes; 28898675Sdes 28998675Sdes if (tm->tm_min > 59) { 29098675Sdes tm->tm_hour++; 29198675Sdes tm->tm_min -= 60; 29298675Sdes 29398675Sdes if (tm->tm_hour > 23) { 29498675Sdes tm->tm_mday++; 29598675Sdes tm->tm_hour = 0; 29698675Sdes } 29798675Sdes } 29898675Sdes} /* dateadd */ 29998675Sdes 30098675Sdes 30198675Sdes/* 30298675Sdes * plus() parses a now + time 30398675Sdes * 30498675Sdes * at [NOW] PLUS NUMBER [MINUTES|HOURS|DAYS|WEEKS] 30598675Sdes * 30698675Sdes */ 30798675Sdesstatic void 30898675Sdesplus(struct tm *tm) 30998675Sdes{ 31098675Sdes int delay; 31198675Sdes int expectplur; 31298675Sdes 31398675Sdes expect(NUMBER); 31498675Sdes 31598675Sdes delay = atoi(sc_token); 31698675Sdes expectplur = (delay != 1) ? 1 : 0; 31798675Sdes 31898675Sdes switch (token()) { 31998675Sdes case WEEKS: 32098675Sdes delay *= 7; 32198675Sdes case DAYS: 32298675Sdes delay *= 24; 32398675Sdes case HOURS: 32498675Sdes delay *= 60; 32598675Sdes case MINUTES: 32698675Sdes if (expectplur != sc_tokplur) 32798675Sdes warnx("pluralization is wrong"); 32898675Sdes dateadd(delay, tm); 32998675Sdes return; 33098675Sdes } 33198675Sdes plonk(sc_tokid); 33298675Sdes} /* plus */ 33398675Sdes 33498675Sdes 33598675Sdes/* 33698675Sdes * tod() computes the time of day 33798675Sdes * [NUMBER [DOT NUMBER] [AM|PM]] 33898675Sdes */ 33998675Sdesstatic void 34098675Sdestod(struct tm *tm) 34198675Sdes{ 34298675Sdes int hour, minute = 0; 34398675Sdes int tlen; 34498675Sdes 34598675Sdes hour = atoi(sc_token); 34698675Sdes tlen = strlen(sc_token); 34798675Sdes 34898675Sdes /* first pick out the time of day - if it's 4 digits, we assume 34998675Sdes * a HHMM time, otherwise it's HH DOT MM time 35098675Sdes */ 35198675Sdes if (token() == DOT) { 35298675Sdes expect(NUMBER); 35398675Sdes minute = atoi(sc_token); 35498675Sdes if (minute > 59) 355 panic("garbled time"); 356 token(); 357 } 358 else if (tlen == 4) { 359 minute = hour%100; 360 if (minute > 59) 361 panic("garbeld time"); 362 hour = hour/100; 363 } 364 365 /* check if an AM or PM specifier was given 366 */ 367 if (sc_tokid == AM || sc_tokid == PM) { 368 if (hour > 12) 369 panic("garbled time"); 370 371 if (sc_tokid == PM) { 372 if (hour != 12) /* 12:xx PM is 12:xx, not 24:xx */ 373 hour += 12; 374 } else { 375 if (hour == 12) /* 12:xx AM is 00:xx, not 12:xx */ 376 hour = 0; 377 } 378 token(); 379 } 380 else if (hour > 23) 381 panic("garbled time"); 382 383 /* if we specify an absolute time, we don't want to bump the day even 384 * if we've gone past that time - but if we're specifying a time plus 385 * a relative offset, it's okay to bump things 386 */ 387 if ((sc_tokid == EOF || sc_tokid == PLUS) && tm->tm_hour > hour) { 388 tm->tm_mday++; 389 tm->tm_wday++; 390 } 391 392 tm->tm_hour = hour; 393 tm->tm_min = minute; 394 if (tm->tm_hour == 24) { 395 tm->tm_hour = 0; 396 tm->tm_mday++; 397 } 398} /* tod */ 399 400 401/* 402 * assign_date() assigns a date, wrapping to next year if needed 403 */ 404static void 405assign_date(struct tm *tm, long mday, long mon, long year) 406{ 407 if (year > 99) { 408 if (year > 1899) 409 year -= 1900; 410 else 411 panic("garbled time"); 412 } else { 413 struct tm *lt; 414 time_t now; 415 416 time(&now); 417 lt = localtime(&now); 418 419 /* 420 * check if the specified year is in the next century. 421 * allow for one year of user error as many people will 422 * enter n - 1 at the start of year n. 423 */ 424 if (year < (lt->tm_year % 100) - 1) 425 year += 100; 426 /* adjust for the year 2000 and beyond */ 427 year += lt->tm_year - (lt->tm_year % 100); 428 } 429 430 if (year < 0 && 431 (tm->tm_mon > mon ||(tm->tm_mon == mon && tm->tm_mday > mday))) 432 year = tm->tm_year + 1; 433 434 tm->tm_mday = mday; 435 tm->tm_mon = mon; 436 437 if (year >= 0) 438 tm->tm_year = year; 439} /* assign_date */ 440 441 442/* 443 * month() picks apart a month specification 444 * 445 * /[<month> NUMBER [NUMBER]] \ 446 * |[TOMORROW] | 447 * |[DAY OF WEEK] | 448 * |NUMBER [SLASH NUMBER [SLASH NUMBER]]| 449 * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/ 450 */ 451static void 452month(struct tm *tm) 453{ 454 long year= (-1); 455 long mday, wday, mon; 456 int tlen; 457 458 switch (sc_tokid) { 459 case PLUS: 460 plus(tm); 461 break; 462 463 case TOMORROW: 464 /* do something tomorrow */ 465 tm->tm_mday ++; 466 tm->tm_wday ++; 467 case TODAY: /* force ourselves to stay in today - no further processing */ 468 token(); 469 break; 470 471 case JAN: case FEB: case MAR: case APR: case MAY: case JUN: 472 case JUL: case AUG: case SEP: case OCT: case NOV: case DEC: 473 /* do month mday [year] 474 */ 475 mon = (sc_tokid-JAN); 476 expect(NUMBER); 477 mday = atol(sc_token); 478 if (token() == NUMBER) { 479 year = atol(sc_token); 480 token(); 481 } 482 assign_date(tm, mday, mon, year); 483 break; 484 485 case SUN: case MON: case TUE: 486 case WED: case THU: case FRI: 487 case SAT: 488 /* do a particular day of the week 489 */ 490 wday = (sc_tokid-SUN); 491 492 mday = tm->tm_mday; 493 494 /* if this day is < today, then roll to next week 495 */ 496 if (wday < tm->tm_wday) 497 mday += 7 - (tm->tm_wday - wday); 498 else 499 mday += (wday - tm->tm_wday); 500 501 tm->tm_wday = wday; 502 503 assign_date(tm, mday, tm->tm_mon, tm->tm_year); 504 break; 505 506 case NUMBER: 507 /* get numeric MMDDYY, mm/dd/yy, or dd.mm.yy 508 */ 509 tlen = strlen(sc_token); 510 mon = atol(sc_token); 511 token(); 512 513 if (sc_tokid == SLASH || sc_tokid == DOT) { 514 int sep; 515 516 sep = sc_tokid; 517 expect(NUMBER); 518 mday = atol(sc_token); 519 if (token() == sep) { 520 expect(NUMBER); 521 year = atol(sc_token); 522 token(); 523 } 524 525 /* flip months and days for european timing 526 */ 527 if (sep == DOT) { 528 int x = mday; 529 mday = mon; 530 mon = x; 531 } 532 } 533 else if (tlen == 6 || tlen == 8) { 534 if (tlen == 8) { 535 year = (mon % 10000) - 1900; 536 mon /= 10000; 537 } 538 else { 539 year = mon % 100; 540 mon /= 100; 541 } 542 mday = mon % 100; 543 mon /= 100; 544 } 545 else 546 panic("garbled time"); 547 548 mon--; 549 if (mon < 0 || mon > 11 || mday < 1 || mday > 31) 550 panic("garbled time"); 551 552 assign_date(tm, mday, mon, year); 553 break; 554 } /* case */ 555} /* month */ 556 557 558/* Global functions */ 559 560time_t 561parsetime(int argc, char **argv) 562{ 563/* Do the argument parsing, die if necessary, and return the time the job 564 * should be run. 565 */ 566 time_t nowtimer, runtimer; 567 struct tm nowtime, runtime; 568 int hr = 0; 569 /* this MUST be initialized to zero for midnight/noon/teatime */ 570 571 nowtimer = time(NULL); 572 nowtime = *localtime(&nowtimer); 573 574 runtime = nowtime; 575 runtime.tm_sec = 0; 576 runtime.tm_isdst = 0; 577 578 if (argc <= optind) 579 usage(); 580 581 init_scanner(argc-optind, argv+optind); 582 583 switch (token()) { 584 case NOW: /* now is optional prefix for PLUS tree */ 585 expect(PLUS); 586 case PLUS: 587 plus(&runtime); 588 break; 589 590 case NUMBER: 591 tod(&runtime); 592 month(&runtime); 593 break; 594 595 /* evil coding for TEATIME|NOON|MIDNIGHT - we've initialised 596 * hr to zero up above, then fall into this case in such a 597 * way so we add +12 +4 hours to it for teatime, +12 hours 598 * to it for noon, and nothing at all for midnight, then 599 * set our runtime to that hour before leaping into the 600 * month scanner 601 */ 602 case TEATIME: 603 hr += 4; 604 case NOON: 605 hr += 12; 606 case MIDNIGHT: 607 if (runtime.tm_hour >= hr) { 608 runtime.tm_mday++; 609 runtime.tm_wday++; 610 } 611 runtime.tm_hour = hr; 612 runtime.tm_min = 0; 613 token(); 614 /* fall through to month setting */ 615 default: 616 month(&runtime); 617 break; 618 } /* ugly case statement */ 619 expect(EOF); 620 621 /* adjust for daylight savings time 622 */ 623 runtime.tm_isdst = -1; 624 runtimer = mktime(&runtime); 625 if (runtime.tm_isdst > 0) { 626 runtimer -= 3600; 627 runtimer = mktime(&runtime); 628 } 629 630 if (runtimer < 0) 631 panic("garbled time"); 632 633 if (nowtimer > runtimer) 634 panic("Trying to travel back in time"); 635 636 return runtimer; 637} /* parsetime */ 638