parsetime.c revision 17221
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 13317221Sjdpstatic char rcsid[] = "$Id: parsetime.c,v 1.6 1995/08/21 12:32:50 ache 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 37017221Sjdp if (sc_tokid == PM) { 37117221Sjdp if (hour != 12) /* 12:xx PM is 12:xx, not 24:xx */ 37217221Sjdp hour += 12; 37317221Sjdp } else { 37417221Sjdp if (hour == 12) /* 12:xx AM is 00:xx, not 12:xx */ 37517221Sjdp hour -= 12; 37617221Sjdp } 377941Snate token(); 3787767Sache } 3797767Sache else if (hour > 23) 380941Snate panic("garbled time"); 381941Snate 3827767Sache /* if we specify an absolute time, we don't want to bump the day even 383941Snate * if we've gone past that time - but if we're specifying a time plus 384941Snate * a relative offset, it's okay to bump things 385941Snate */ 3867767Sache if ((sc_tokid == EOF || sc_tokid == PLUS) && tm->tm_hour > hour) { 387941Snate tm->tm_mday++; 3887767Sache tm->tm_wday++; 3897767Sache } 390941Snate 391941Snate tm->tm_hour = hour; 392941Snate tm->tm_min = minute; 393941Snate if (tm->tm_hour == 24) { 394941Snate tm->tm_hour = 0; 395941Snate tm->tm_mday++; 396941Snate } 397941Snate} /* tod */ 398941Snate 399941Snate 400941Snate/* 401941Snate * assign_date() assigns a date, wrapping to next year if needed 402941Snate */ 403941Snatestatic void 4047767Sacheassign_date(struct tm *tm, long mday, long mon, long year) 405941Snate{ 406941Snate if (year > 99) { 407941Snate if (year > 1899) 408941Snate year -= 1900; 409941Snate else 410941Snate panic("garbled time"); 411941Snate } 412941Snate 413941Snate if (year < 0 && 414941Snate (tm->tm_mon > mon ||(tm->tm_mon == mon && tm->tm_mday > mday))) 415941Snate year = tm->tm_year + 1; 416941Snate 417941Snate tm->tm_mday = mday; 418941Snate tm->tm_mon = mon; 419941Snate 420941Snate if (year >= 0) 421941Snate tm->tm_year = year; 422941Snate} /* assign_date */ 423941Snate 424941Snate 42510154Sache/* 426941Snate * month() picks apart a month specification 427941Snate * 428941Snate * /[<month> NUMBER [NUMBER]] \ 429941Snate * |[TOMORROW] | 4307767Sache * |[DAY OF WEEK] | 431941Snate * |NUMBER [SLASH NUMBER [SLASH NUMBER]]| 432941Snate * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/ 433941Snate */ 434941Snatestatic void 4357767Sachemonth(struct tm *tm) 436941Snate{ 437941Snate long year= (-1); 4387767Sache long mday, wday, mon; 439941Snate int tlen; 440941Snate 441941Snate switch (sc_tokid) { 442941Snate case PLUS: 443941Snate plus(tm); 444941Snate break; 445941Snate 446941Snate case TOMORROW: 447941Snate /* do something tomorrow */ 448941Snate tm->tm_mday ++; 4497767Sache tm->tm_wday ++; 450941Snate case TODAY: /* force ourselves to stay in today - no further processing */ 451941Snate token(); 452941Snate break; 453941Snate 454941Snate case JAN: case FEB: case MAR: case APR: case MAY: case JUN: 455941Snate case JUL: case AUG: case SEP: case OCT: case NOV: case DEC: 4567767Sache /* do month mday [year] 457941Snate */ 458941Snate mon = (sc_tokid-JAN); 459941Snate expect(NUMBER); 4606079Sbde mday = atol(sc_token); 461941Snate if (token() == NUMBER) { 462941Snate year = atol(sc_token); 463941Snate token(); 464941Snate } 465941Snate assign_date(tm, mday, mon, year); 466941Snate break; 467941Snate 4687767Sache case SUN: case MON: case TUE: 4697767Sache case WED: case THU: case FRI: 4707767Sache case SAT: 4717767Sache /* do a particular day of the week 4727767Sache */ 4737767Sache wday = (sc_tokid-SUN); 4747767Sache 4757767Sache mday = tm->tm_mday; 4767767Sache 4777767Sache /* if this day is < today, then roll to next week 4787767Sache */ 4797767Sache if (wday < tm->tm_wday) 4807767Sache mday += 7 - (tm->tm_wday - wday); 4817767Sache else 4827767Sache mday += (wday - tm->tm_wday); 4837767Sache 4847767Sache tm->tm_wday = wday; 4857767Sache 4867767Sache assign_date(tm, mday, tm->tm_mon, tm->tm_year); 4877767Sache break; 4887767Sache 489941Snate case NUMBER: 4907767Sache /* get numeric MMDDYY, mm/dd/yy, or dd.mm.yy 491941Snate */ 492941Snate tlen = strlen(sc_token); 493941Snate mon = atol(sc_token); 494941Snate token(); 495941Snate 496941Snate if (sc_tokid == SLASH || sc_tokid == DOT) { 49710154Sache int sep; 498941Snate 499941Snate sep = sc_tokid; 500941Snate expect(NUMBER); 501941Snate mday = atol(sc_token); 502941Snate if (token() == sep) { 503941Snate expect(NUMBER); 504941Snate year = atol(sc_token); 505941Snate token(); 506941Snate } 507941Snate 5087767Sache /* flip months and days for european timing 509941Snate */ 510941Snate if (sep == DOT) { 511941Snate int x = mday; 512941Snate mday = mon; 513941Snate mon = x; 514941Snate } 5157767Sache } 5167767Sache else if (tlen == 6 || tlen == 8) { 517941Snate if (tlen == 8) { 518941Snate year = (mon % 10000) - 1900; 519941Snate mon /= 10000; 5207767Sache } 5217767Sache else { 522941Snate year = mon % 100; 523941Snate mon /= 100; 524941Snate } 525941Snate mday = mon % 100; 526941Snate mon /= 100; 5277767Sache } 5287767Sache else 529941Snate panic("garbled time"); 530941Snate 531941Snate mon--; 532941Snate if (mon < 0 || mon > 11 || mday < 1 || mday > 31) 533941Snate panic("garbled time"); 534941Snate 535941Snate assign_date(tm, mday, mon, year); 536941Snate break; 537941Snate } /* case */ 538941Snate} /* month */ 539941Snate 540941Snate 541941Snate/* Global functions */ 542941Snate 543941Snatetime_t 5447767Sacheparsetime(int argc, char **argv) 545941Snate{ 5467767Sache/* Do the argument parsing, die if necessary, and return the time the job 547941Snate * should be run. 548941Snate */ 549941Snate time_t nowtimer, runtimer; 550941Snate struct tm nowtime, runtime; 551941Snate int hr = 0; 552941Snate /* this MUST be initialized to zero for midnight/noon/teatime */ 553941Snate 554941Snate nowtimer = time(NULL); 555941Snate nowtime = *localtime(&nowtimer); 556941Snate 557941Snate runtime = nowtime; 558941Snate runtime.tm_sec = 0; 559941Snate runtime.tm_isdst = 0; 560941Snate 561941Snate if (argc <= optind) 562941Snate usage(); 563941Snate 564941Snate init_scanner(argc-optind, argv+optind); 565941Snate 566941Snate switch (token()) { 567941Snate case NOW: /* now is optional prefix for PLUS tree */ 568941Snate expect(PLUS); 569941Snate case PLUS: 570941Snate plus(&runtime); 571941Snate break; 572941Snate 573941Snate case NUMBER: 574941Snate tod(&runtime); 575941Snate month(&runtime); 576941Snate break; 577941Snate 5787767Sache /* evil coding for TEATIME|NOON|MIDNIGHT - we've initialised 579941Snate * hr to zero up above, then fall into this case in such a 580941Snate * way so we add +12 +4 hours to it for teatime, +12 hours 581941Snate * to it for noon, and nothing at all for midnight, then 582941Snate * set our runtime to that hour before leaping into the 583941Snate * month scanner 584941Snate */ 585941Snate case TEATIME: 586941Snate hr += 4; 587941Snate case NOON: 588941Snate hr += 12; 589941Snate case MIDNIGHT: 5907767Sache if (runtime.tm_hour >= hr) { 591941Snate runtime.tm_mday++; 5927767Sache runtime.tm_wday++; 5937767Sache } 594941Snate runtime.tm_hour = hr; 595941Snate runtime.tm_min = 0; 596941Snate token(); 597941Snate /* fall through to month setting */ 598941Snate default: 599941Snate month(&runtime); 600941Snate break; 601941Snate } /* ugly case statement */ 602941Snate expect(EOF); 603941Snate 6047767Sache /* adjust for daylight savings time 605941Snate */ 606941Snate runtime.tm_isdst = -1; 607941Snate runtimer = mktime(&runtime); 608941Snate if (runtime.tm_isdst > 0) { 609941Snate runtimer -= 3600; 610941Snate runtimer = mktime(&runtime); 611941Snate } 612941Snate 613941Snate if (runtimer < 0) 614941Snate panic("garbled time"); 615941Snate 616941Snate if (nowtimer > runtimer) 617941Snate panic("Trying to travel back in time"); 618941Snate 619941Snate return runtimer; 620941Snate} /* parsetime */ 621