parsetime.c revision 10154
110154Sache/* 27767Sache * parsetime.c - parse time for at(1) 37767Sache * Copyright (C) 1993, 1994 Thomas Koenig 4941Snate * 57767Sache * modifications for english-language times 67767Sache * Copyright (C) 1993 David Parsons 7941Snate * 8941Snate * Redistribution and use in source and binary forms, with or without 9941Snate * modification, are permitted provided that the following conditions 10941Snate * are met: 11941Snate * 1. Redistributions of source code must retain the above copyright 12941Snate * notice, this list of conditions and the following disclaimer. 13941Snate * 2. The name of the author(s) may not be used to endorse or promote 14941Snate * products derived from this software without specific prior written 15941Snate * permission. 16941Snate * 17941Snate * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 18941Snate * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 19941Snate * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 2010154Sache * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 21941Snate * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 22941Snate * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 23941Snate * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 24941Snate * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25941Snate * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26941Snate * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27941Snate * 28941Snate * at [NOW] PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS 29941Snate * /NUMBER [DOT NUMBER] [AM|PM]\ /[MONTH NUMBER [NUMBER]] \ 30941Snate * |NOON | |[TOMORROW] | 317767Sache * |MIDNIGHT | |[DAY OF WEEK] | 327767Sache * \TEATIME / |NUMBER [SLASH NUMBER [SLASH NUMBER]]| 337767Sache * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/ 34941Snate */ 35941Snate 36941Snate/* System Headers */ 37941Snate 387767Sache 39941Snate#include <sys/types.h> 40941Snate#include <errno.h> 41941Snate#include <stdio.h> 42941Snate#include <stdlib.h> 43941Snate#include <string.h> 44941Snate#include <time.h> 45941Snate#include <unistd.h> 46941Snate#include <ctype.h> 477767Sache#ifndef __FreeBSD__ 487767Sache#include <getopt.h> 497767Sache#endif 50941Snate 51941Snate/* Local headers */ 52941Snate 53941Snate#include "at.h" 54941Snate#include "panic.h" 55941Snate 56941Snate 57941Snate/* Structures and unions */ 58941Snate 5910154Sacheenum { /* symbols */ 60941Snate MIDNIGHT, NOON, TEATIME, 61941Snate PM, AM, TOMORROW, TODAY, NOW, 62941Snate MINUTES, HOURS, DAYS, WEEKS, 63941Snate NUMBER, PLUS, DOT, SLASH, ID, JUNK, 64941Snate JAN, FEB, MAR, APR, MAY, JUN, 657767Sache JUL, AUG, SEP, OCT, NOV, DEC, 667767Sache SUN, MON, TUE, WED, THU, FRI, SAT 677767Sache }; 68941Snate 697767Sache/* parse translation table - table driven parsers can be your FRIEND! 70941Snate */ 71941Snatestruct { 7210154Sache char *name; /* token name */ 7310154Sache int value; /* token id */ 747767Sache int plural; /* is this plural? */ 75941Snate} Specials[] = { 767767Sache { "midnight", MIDNIGHT,0 }, /* 00:00:00 of today or tomorrow */ 777767Sache { "noon", NOON,0 }, /* 12:00:00 of today or tomorrow */ 787767Sache { "teatime", TEATIME,0 }, /* 16:00:00 of today or tomorrow */ 797767Sache { "am", AM,0 }, /* morning times for 0-12 clock */ 807767Sache { "pm", PM,0 }, /* evening times for 0-12 clock */ 817767Sache { "tomorrow", TOMORROW,0 }, /* execute 24 hours from time */ 827767Sache { "today", TODAY, 0 }, /* execute today - don't advance time */ 837767Sache { "now", NOW,0 }, /* opt prefix for PLUS */ 84941Snate 857767Sache { "minute", MINUTES,0 }, /* minutes multiplier */ 867767Sache { "minutes", MINUTES,1 }, /* (pluralized) */ 877767Sache { "hour", HOURS,0 }, /* hours ... */ 887767Sache { "hours", HOURS,1 }, /* (pluralized) */ 897767Sache { "day", DAYS,0 }, /* days ... */ 907767Sache { "days", DAYS,1 }, /* (pluralized) */ 917767Sache { "week", WEEKS,0 }, /* week ... */ 927767Sache { "weeks", WEEKS,1 }, /* (pluralized) */ 937767Sache { "jan", JAN,0 }, 947767Sache { "feb", FEB,0 }, 957767Sache { "mar", MAR,0 }, 967767Sache { "apr", APR,0 }, 977767Sache { "may", MAY,0 }, 987767Sache { "jun", JUN,0 }, 997767Sache { "jul", JUL,0 }, 1007767Sache { "aug", AUG,0 }, 1017767Sache { "sep", SEP,0 }, 1027767Sache { "oct", OCT,0 }, 1037767Sache { "nov", NOV,0 }, 1047767Sache { "dec", DEC,0 }, 1057767Sache { "sunday", SUN, 0 }, 1067767Sache { "sun", SUN, 0 }, 1077767Sache { "monday", MON, 0 }, 1087767Sache { "mon", MON, 0 }, 1097767Sache { "tuesday", TUE, 0 }, 1107767Sache { "tue", TUE, 0 }, 1117767Sache { "wednesday", WED, 0 }, 1127767Sache { "wed", WED, 0 }, 1137767Sache { "thursday", THU, 0 }, 1147767Sache { "thu", THU, 0 }, 1157767Sache { "friday", FRI, 0 }, 1167767Sache { "fri", FRI, 0 }, 1177767Sache { "saturday", SAT, 0 }, 1187767Sache { "sat", SAT, 0 }, 119941Snate} ; 120941Snate 121941Snate/* File scope variables */ 122941Snate 123941Snatestatic char **scp; /* scanner - pointer at arglist */ 124941Snatestatic char scc; /* scanner - count of remaining arguments */ 125941Snatestatic char *sct; /* scanner - next char pointer in current argument */ 126941Snatestatic int need; /* scanner - need to advance to next argument */ 127941Snate 128941Snatestatic char *sc_token; /* scanner - token buffer */ 129941Snatestatic size_t sc_len; /* scanner - lenght of token buffer */ 13010154Sachestatic int sc_tokid; /* scanner - token id */ 1317767Sachestatic int sc_tokplur; /* scanner - is token plural? */ 132941Snate 13310154Sachestatic char rcsid[] = "$Id: parsetime.c,v 1.1 1995/05/24 15:07:32 ig25 Exp $"; 134941Snate 135941Snate/* Local functions */ 136941Snate 137941Snate/* 138941Snate * parse a token, checking if it's something special to us 139941Snate */ 14010154Sachestatic int 14110154Sacheparse_token(char *arg) 142941Snate{ 143941Snate int i; 144941Snate 145941Snate for (i=0; i<(sizeof Specials/sizeof Specials[0]); i++) 146941Snate if (strcasecmp(Specials[i].name, arg) == 0) { 1477767Sache sc_tokplur = Specials[i].plural; 148941Snate return sc_tokid = Specials[i].value; 149941Snate } 150941Snate 151941Snate /* not special - must be some random id */ 152941Snate return ID; 153941Snate} /* parse_token */ 154941Snate 155941Snate 156941Snate/* 157941Snate * init_scanner() sets up the scanner to eat arguments 158941Snate */ 159941Snatestatic void 1607767Sacheinit_scanner(int argc, char **argv) 161941Snate{ 162941Snate scp = argv; 163941Snate scc = argc; 164941Snate need = 1; 165941Snate sc_len = 1; 16610154Sache while (argc-- > 0) 16710154Sache sc_len += strlen(*argv++); 168941Snate 1697767Sache sc_token = (char *) mymalloc(sc_len); 170941Snate} /* init_scanner */ 171941Snate 172941Snate/* 173941Snate * token() fetches a token from the input stream 174941Snate */ 17510154Sachestatic int 176941Snatetoken() 177941Snate{ 178941Snate int idx; 179941Snate 180941Snate while (1) { 181941Snate memset(sc_token, 0, sc_len); 182941Snate sc_tokid = EOF; 1837767Sache sc_tokplur = 0; 184941Snate idx = 0; 185941Snate 1867767Sache /* if we need to read another argument, walk along the argument list; 187941Snate * when we fall off the arglist, we'll just return EOF forever 188941Snate */ 189941Snate if (need) { 190941Snate if (scc < 1) 191941Snate return sc_tokid; 192941Snate sct = *scp; 193941Snate scp++; 194941Snate scc--; 195941Snate need = 0; 196941Snate } 1977767Sache /* eat whitespace now - if we walk off the end of the argument, 198941Snate * we'll continue, which puts us up at the top of the while loop 199941Snate * to fetch the next argument in 200941Snate */ 201941Snate while (isspace(*sct)) 202941Snate ++sct; 203941Snate if (!*sct) { 204941Snate need = 1; 205941Snate continue; 206941Snate } 207941Snate 2087767Sache /* preserve the first character of the new token 209941Snate */ 210941Snate sc_token[0] = *sct++; 211941Snate 2127767Sache /* then see what it is 213941Snate */ 214941Snate if (isdigit(sc_token[0])) { 215941Snate while (isdigit(*sct)) 216941Snate sc_token[++idx] = *sct++; 217941Snate sc_token[++idx] = 0; 218941Snate return sc_tokid = NUMBER; 2197767Sache } 2207767Sache else if (isalpha(sc_token[0])) { 221941Snate while (isalpha(*sct)) 222941Snate sc_token[++idx] = *sct++; 223941Snate sc_token[++idx] = 0; 224941Snate return parse_token(sc_token); 225941Snate } 226941Snate else if (sc_token[0] == ':' || sc_token[0] == '.') 227941Snate return sc_tokid = DOT; 228941Snate else if (sc_token[0] == '+') 229941Snate return sc_tokid = PLUS; 2307767Sache else if (sc_token[0] == '/') 231941Snate return sc_tokid = SLASH; 232941Snate else 233941Snate return sc_tokid = JUNK; 234941Snate } /* while (1) */ 235941Snate} /* token */ 236941Snate 237941Snate 238941Snate/* 239941Snate * plonk() gives an appropriate error message if a token is incorrect 240941Snate */ 241941Snatestatic void 2427767Sacheplonk(int tok) 243941Snate{ 244941Snate panic((tok == EOF) ? "incomplete time" 245941Snate : "garbled time"); 246941Snate} /* plonk */ 247941Snate 248941Snate 24910154Sache/* 250941Snate * expect() gets a token and dies most horribly if it's not the token we want 251941Snate */ 252941Snatestatic void 25310154Sacheexpect(int desired) 254941Snate{ 255941Snate if (token() != desired) 256941Snate plonk(sc_tokid); /* and we die here... */ 257941Snate} /* expect */ 258941Snate 259941Snate 260941Snate/* 261941Snate * dateadd() adds a number of minutes to a date. It is extraordinarily 262941Snate * stupid regarding day-of-month overflow, and will most likely not 263941Snate * work properly 264941Snate */ 265941Snatestatic void 2667767Sachedateadd(int minutes, struct tm *tm) 267941Snate{ 268941Snate /* increment days */ 269941Snate 270941Snate while (minutes > 24*60) { 271941Snate minutes -= 24*60; 272941Snate tm->tm_mday++; 273941Snate } 274941Snate 275941Snate /* increment hours */ 276941Snate while (minutes > 60) { 277941Snate minutes -= 60; 278941Snate tm->tm_hour++; 279941Snate if (tm->tm_hour > 23) { 280941Snate tm->tm_mday++; 281941Snate tm->tm_hour = 0; 282941Snate } 283941Snate } 284941Snate 285941Snate /* increment minutes */ 286941Snate tm->tm_min += minutes; 287941Snate 288941Snate if (tm->tm_min > 59) { 289941Snate tm->tm_hour++; 290941Snate tm->tm_min -= 60; 291941Snate 292941Snate if (tm->tm_hour > 23) { 293941Snate tm->tm_mday++; 294941Snate tm->tm_hour = 0; 295941Snate } 296941Snate } 297941Snate} /* dateadd */ 298941Snate 299941Snate 300941Snate/* 301941Snate * plus() parses a now + time 302941Snate * 303941Snate * at [NOW] PLUS NUMBER [MINUTES|HOURS|DAYS|WEEKS] 304941Snate * 305941Snate */ 306941Snatestatic void 3077767Sacheplus(struct tm *tm) 308941Snate{ 309941Snate int delay; 3107767Sache int expectplur; 311941Snate 312941Snate expect(NUMBER); 313941Snate 314941Snate delay = atoi(sc_token); 3157767Sache expectplur = (delay != 1) ? 1 : 0; 316941Snate 317941Snate switch (token()) { 318941Snate case WEEKS: 319941Snate delay *= 7; 320941Snate case DAYS: 321941Snate delay *= 24; 322941Snate case HOURS: 323941Snate delay *= 60; 324941Snate case MINUTES: 3257767Sache if (expectplur != sc_tokplur) 3267767Sache fprintf(stderr, "at: pluralization is wrong\n"); 327941Snate dateadd(delay, tm); 328941Snate return; 329941Snate } 330941Snate plonk(sc_tokid); 331941Snate} /* plus */ 332941Snate 333941Snate 334941Snate/* 335941Snate * tod() computes the time of day 336941Snate * [NUMBER [DOT NUMBER] [AM|PM]] 337941Snate */ 338941Snatestatic void 3397767Sachetod(struct tm *tm) 340941Snate{ 341941Snate int hour, minute = 0; 342941Snate int tlen; 343941Snate 344941Snate hour = atoi(sc_token); 345941Snate tlen = strlen(sc_token); 346941Snate 3477767Sache /* first pick out the time of day - if it's 4 digits, we assume 348941Snate * a HHMM time, otherwise it's HH DOT MM time 349941Snate */ 350941Snate if (token() == DOT) { 351941Snate expect(NUMBER); 352941Snate minute = atoi(sc_token); 353941Snate if (minute > 59) 354941Snate panic("garbled time"); 355941Snate token(); 3567767Sache } 3577767Sache else if (tlen == 4) { 358941Snate minute = hour%100; 359941Snate if (minute > 59) 360941Snate panic("garbeld time"); 361941Snate hour = hour/100; 362941Snate } 363941Snate 3647767Sache /* check if an AM or PM specifier was given 365941Snate */ 366941Snate if (sc_tokid == AM || sc_tokid == PM) { 367941Snate if (hour > 12) 368941Snate panic("garbled time"); 369941Snate 370941Snate if (sc_tokid == PM) 371941Snate hour += 12; 372941Snate token(); 3737767Sache } 3747767Sache else if (hour > 23) 375941Snate panic("garbled time"); 376941Snate 3777767Sache /* if we specify an absolute time, we don't want to bump the day even 378941Snate * if we've gone past that time - but if we're specifying a time plus 379941Snate * a relative offset, it's okay to bump things 380941Snate */ 3817767Sache if ((sc_tokid == EOF || sc_tokid == PLUS) && tm->tm_hour > hour) { 382941Snate tm->tm_mday++; 3837767Sache tm->tm_wday++; 3847767Sache } 385941Snate 386941Snate tm->tm_hour = hour; 387941Snate tm->tm_min = minute; 388941Snate if (tm->tm_hour == 24) { 389941Snate tm->tm_hour = 0; 390941Snate tm->tm_mday++; 391941Snate } 392941Snate} /* tod */ 393941Snate 394941Snate 395941Snate/* 396941Snate * assign_date() assigns a date, wrapping to next year if needed 397941Snate */ 398941Snatestatic void 3997767Sacheassign_date(struct tm *tm, long mday, long mon, long year) 400941Snate{ 401941Snate if (year > 99) { 402941Snate if (year > 1899) 403941Snate year -= 1900; 404941Snate else 405941Snate panic("garbled time"); 406941Snate } 407941Snate 408941Snate if (year < 0 && 409941Snate (tm->tm_mon > mon ||(tm->tm_mon == mon && tm->tm_mday > mday))) 410941Snate year = tm->tm_year + 1; 411941Snate 412941Snate tm->tm_mday = mday; 413941Snate tm->tm_mon = mon; 414941Snate 415941Snate if (year >= 0) 416941Snate tm->tm_year = year; 417941Snate} /* assign_date */ 418941Snate 419941Snate 42010154Sache/* 421941Snate * month() picks apart a month specification 422941Snate * 423941Snate * /[<month> NUMBER [NUMBER]] \ 424941Snate * |[TOMORROW] | 4257767Sache * |[DAY OF WEEK] | 426941Snate * |NUMBER [SLASH NUMBER [SLASH NUMBER]]| 427941Snate * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/ 428941Snate */ 429941Snatestatic void 4307767Sachemonth(struct tm *tm) 431941Snate{ 432941Snate long year= (-1); 4337767Sache long mday, wday, mon; 434941Snate int tlen; 435941Snate 436941Snate switch (sc_tokid) { 437941Snate case PLUS: 438941Snate plus(tm); 439941Snate break; 440941Snate 441941Snate case TOMORROW: 442941Snate /* do something tomorrow */ 443941Snate tm->tm_mday ++; 4447767Sache tm->tm_wday ++; 445941Snate case TODAY: /* force ourselves to stay in today - no further processing */ 446941Snate token(); 447941Snate break; 448941Snate 449941Snate case JAN: case FEB: case MAR: case APR: case MAY: case JUN: 450941Snate case JUL: case AUG: case SEP: case OCT: case NOV: case DEC: 4517767Sache /* do month mday [year] 452941Snate */ 453941Snate mon = (sc_tokid-JAN); 454941Snate expect(NUMBER); 4556079Sbde mday = atol(sc_token); 456941Snate if (token() == NUMBER) { 457941Snate year = atol(sc_token); 458941Snate token(); 459941Snate } 460941Snate assign_date(tm, mday, mon, year); 461941Snate break; 462941Snate 4637767Sache case SUN: case MON: case TUE: 4647767Sache case WED: case THU: case FRI: 4657767Sache case SAT: 4667767Sache /* do a particular day of the week 4677767Sache */ 4687767Sache wday = (sc_tokid-SUN); 4697767Sache 4707767Sache mday = tm->tm_mday; 4717767Sache 4727767Sache /* if this day is < today, then roll to next week 4737767Sache */ 4747767Sache if (wday < tm->tm_wday) 4757767Sache mday += 7 - (tm->tm_wday - wday); 4767767Sache else 4777767Sache mday += (wday - tm->tm_wday); 4787767Sache 4797767Sache tm->tm_wday = wday; 4807767Sache 4817767Sache assign_date(tm, mday, tm->tm_mon, tm->tm_year); 4827767Sache break; 4837767Sache 484941Snate case NUMBER: 4857767Sache /* get numeric MMDDYY, mm/dd/yy, or dd.mm.yy 486941Snate */ 487941Snate tlen = strlen(sc_token); 488941Snate mon = atol(sc_token); 489941Snate token(); 490941Snate 491941Snate if (sc_tokid == SLASH || sc_tokid == DOT) { 49210154Sache int sep; 493941Snate 494941Snate sep = sc_tokid; 495941Snate expect(NUMBER); 496941Snate mday = atol(sc_token); 497941Snate if (token() == sep) { 498941Snate expect(NUMBER); 499941Snate year = atol(sc_token); 500941Snate token(); 501941Snate } 502941Snate 5037767Sache /* flip months and days for european timing 504941Snate */ 505941Snate if (sep == DOT) { 506941Snate int x = mday; 507941Snate mday = mon; 508941Snate mon = x; 509941Snate } 5107767Sache } 5117767Sache else if (tlen == 6 || tlen == 8) { 512941Snate if (tlen == 8) { 513941Snate year = (mon % 10000) - 1900; 514941Snate mon /= 10000; 5157767Sache } 5167767Sache else { 517941Snate year = mon % 100; 518941Snate mon /= 100; 519941Snate } 520941Snate mday = mon % 100; 521941Snate mon /= 100; 5227767Sache } 5237767Sache else 524941Snate panic("garbled time"); 525941Snate 526941Snate mon--; 527941Snate if (mon < 0 || mon > 11 || mday < 1 || mday > 31) 528941Snate panic("garbled time"); 529941Snate 530941Snate assign_date(tm, mday, mon, year); 531941Snate break; 532941Snate } /* case */ 533941Snate} /* month */ 534941Snate 535941Snate 536941Snate/* Global functions */ 537941Snate 538941Snatetime_t 5397767Sacheparsetime(int argc, char **argv) 540941Snate{ 5417767Sache/* Do the argument parsing, die if necessary, and return the time the job 542941Snate * should be run. 543941Snate */ 544941Snate time_t nowtimer, runtimer; 545941Snate struct tm nowtime, runtime; 546941Snate int hr = 0; 547941Snate /* this MUST be initialized to zero for midnight/noon/teatime */ 548941Snate 549941Snate nowtimer = time(NULL); 550941Snate nowtime = *localtime(&nowtimer); 551941Snate 552941Snate runtime = nowtime; 553941Snate runtime.tm_sec = 0; 554941Snate runtime.tm_isdst = 0; 555941Snate 556941Snate if (argc <= optind) 557941Snate usage(); 558941Snate 559941Snate init_scanner(argc-optind, argv+optind); 560941Snate 561941Snate switch (token()) { 562941Snate case NOW: /* now is optional prefix for PLUS tree */ 563941Snate expect(PLUS); 564941Snate case PLUS: 565941Snate plus(&runtime); 566941Snate break; 567941Snate 568941Snate case NUMBER: 569941Snate tod(&runtime); 570941Snate month(&runtime); 571941Snate break; 572941Snate 5737767Sache /* evil coding for TEATIME|NOON|MIDNIGHT - we've initialised 574941Snate * hr to zero up above, then fall into this case in such a 575941Snate * way so we add +12 +4 hours to it for teatime, +12 hours 576941Snate * to it for noon, and nothing at all for midnight, then 577941Snate * set our runtime to that hour before leaping into the 578941Snate * month scanner 579941Snate */ 580941Snate case TEATIME: 581941Snate hr += 4; 582941Snate case NOON: 583941Snate hr += 12; 584941Snate case MIDNIGHT: 5857767Sache if (runtime.tm_hour >= hr) { 586941Snate runtime.tm_mday++; 5877767Sache runtime.tm_wday++; 5887767Sache } 589941Snate runtime.tm_hour = hr; 590941Snate runtime.tm_min = 0; 591941Snate token(); 592941Snate /* fall through to month setting */ 593941Snate default: 594941Snate month(&runtime); 595941Snate break; 596941Snate } /* ugly case statement */ 597941Snate expect(EOF); 598941Snate 5997767Sache /* adjust for daylight savings time 600941Snate */ 601941Snate runtime.tm_isdst = -1; 602941Snate runtimer = mktime(&runtime); 603941Snate if (runtime.tm_isdst > 0) { 604941Snate runtimer -= 3600; 605941Snate runtimer = mktime(&runtime); 606941Snate } 607941Snate 608941Snate if (runtimer < 0) 609941Snate panic("garbled time"); 610941Snate 611941Snate if (nowtimer > runtimer) 612941Snate panic("Trying to travel back in time"); 613941Snate 614941Snate return runtimer; 615941Snate} /* parsetime */ 616