parsetime.c revision 6079
10SN/A/*
24475SN/A * parsetime.c - parse time for at(1)
30SN/A * Copyright (C) 1993  Thomas Koenig
40SN/A *
50SN/A * modifications for english-language times
60SN/A * Copyright (C) 1993  David Parsons
72362SN/A * All rights reserved.
80SN/A *
92362SN/A * Redistribution and use in source and binary forms, with or without
100SN/A * modification, are permitted provided that the following conditions
110SN/A * are met:
120SN/A * 1. Redistributions of source code must retain the above copyright
130SN/A *    notice, this list of conditions and the following disclaimer.
140SN/A * 2. The name of the author(s) may not be used to endorse or promote
150SN/A *    products derived from this software without specific prior written
160SN/A *    permission.
170SN/A *
180SN/A * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
190SN/A * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
200SN/A * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
212362SN/A * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
222362SN/A * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
232362SN/A * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
240SN/A * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
250SN/A * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
260SN/A * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
270SN/A * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
280SN/A *
290SN/A *  at [NOW] PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS
300SN/A *     /NUMBER [DOT NUMBER] [AM|PM]\ /[MONTH NUMBER [NUMBER]]             \
310SN/A *     |NOON                       | |[TOMORROW]                          |
320SN/A *     |MIDNIGHT                   | |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
330SN/A *     \TEATIME                    / \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/
340SN/A */
350SN/A
360SN/A/* System Headers */
370SN/A
380SN/A#include <sys/types.h>
390SN/A#include <errno.h>
400SN/A#include <stdio.h>
410SN/A#include <stdlib.h>
420SN/A#include <string.h>
430SN/A#include <time.h>
440SN/A#include <unistd.h>
450SN/A#include <ctype.h>
460SN/A
470SN/A/* Local headers */
480SN/A
490SN/A#include "at.h"
500SN/A#include "panic.h"
510SN/A
520SN/A
530SN/A/* Structures and unions */
540SN/A
550SN/Aenum {	/* symbols */
560SN/A    MIDNIGHT, NOON, TEATIME,
570SN/A    PM, AM, TOMORROW, TODAY, NOW,
580SN/A    MINUTES, HOURS, DAYS, WEEKS,
590SN/A    NUMBER, PLUS, DOT, SLASH, ID, JUNK,
600SN/A    JAN, FEB, MAR, APR, MAY, JUN,
610SN/A    JUL, AUG, SEP, OCT, NOV, DEC
620SN/A};
630SN/A
640SN/A/*
650SN/A * parse translation table - table driven parsers can be your FRIEND!
660SN/A */
670SN/Astruct {
680SN/A    char *name;	/* token name */
690SN/A    int value;	/* token id */
700SN/A} Specials[] = {
710SN/A    { "midnight", MIDNIGHT },	/* 00:00:00 of today or tomorrow */
720SN/A    { "noon", NOON },		/* 12:00:00 of today or tomorrow */
730SN/A    { "teatime", TEATIME },	/* 16:00:00 of today or tomorrow */
740SN/A    { "am", AM },		/* morning times for 0-12 clock */
750SN/A    { "pm", PM },		/* evening times for 0-12 clock */
760SN/A    { "tomorrow", TOMORROW },	/* execute 24 hours from time */
770SN/A    { "today", TODAY },		/* execute today - don't advance time */
780SN/A    { "now", NOW },		/* opt prefix for PLUS */
790SN/A
800SN/A    { "minute", MINUTES },	/* minutes multiplier */
810SN/A    { "min", MINUTES },
820SN/A    { "m", MINUTES },
830SN/A    { "minutes", MINUTES },	/* (pluralized) */
840SN/A    { "hour", HOURS },		/* hours ... */
854475SN/A    { "hr", HOURS },		/* abbreviated */
864475SN/A    { "h", HOURS },
874475SN/A    { "hours", HOURS },		/* (pluralized) */
884475SN/A    { "day", DAYS },		/* days ... */
894475SN/A    { "d", DAYS },
900SN/A    { "days", DAYS },		/* (pluralized) */
910SN/A    { "week", WEEKS },		/* week ... */
924475SN/A    { "w", WEEKS },
934475SN/A    { "weeks", WEEKS },		/* (pluralized) */
944475SN/A    { "jan", JAN },
954475SN/A    { "feb", FEB },
964475SN/A    { "mar", MAR },
970SN/A    { "apr", APR },
980SN/A    { "may", MAY },
994475SN/A    { "jun", JUN },
1004475SN/A    { "jul", JUL },
1014475SN/A    { "aug", AUG },
1024475SN/A    { "sep", SEP },
1034475SN/A    { "oct", OCT },
1044475SN/A    { "nov", NOV },
1050SN/A    { "dec", DEC }
1060SN/A} ;
1070SN/A
1080SN/A/* File scope variables */
1090SN/A
1100SN/Astatic char **scp;	/* scanner - pointer at arglist */
1110SN/Astatic char scc;	/* scanner - count of remaining arguments */
1120SN/Astatic char *sct;	/* scanner - next char pointer in current argument */
1130SN/Astatic int need;	/* scanner - need to advance to next argument */
1140SN/A
1150SN/Astatic char *sc_token;	/* scanner - token buffer */
1160SN/Astatic size_t sc_len;   /* scanner - lenght of token buffer */
1170SN/Astatic int sc_tokid;	/* scanner - token id */
1180SN/A
1190SN/Astatic char rcsid[] = "$Id: parsetime.c,v 1.1 1994/01/05 01:09:08 nate Exp $";
1200SN/A
1210SN/A/* Local functions */
1220SN/A
1230SN/A/*
1240SN/A * parse a token, checking if it's something special to us
1250SN/A */
1260SN/Astatic int
1270SN/Aparse_token(arg)
1280SN/A	char *arg;
1290SN/A{
1300SN/A    int i;
1310SN/A
1320SN/A    for (i=0; i<(sizeof Specials/sizeof Specials[0]); i++)
1330SN/A	if (strcasecmp(Specials[i].name, arg) == 0) {
1340SN/A	    return sc_tokid = Specials[i].value;
1354475SN/A	}
1360SN/A
1370SN/A    /* not special - must be some random id */
1380SN/A    return ID;
1390SN/A} /* parse_token */
1400SN/A
1410SN/A
1420SN/A/*
1430SN/A * init_scanner() sets up the scanner to eat arguments
1440SN/A */
1450SN/Astatic void
1460SN/Ainit_scanner(argc, argv)
1470SN/A	int argc;
1480SN/A	char **argv;
1490SN/A{
1500SN/A    scp = argv;
1510SN/A    scc = argc;
1520SN/A    need = 1;
1530SN/A    sc_len = 1;
1540SN/A    while (--argc > 0)
1550SN/A	sc_len += strlen(*++argv);
1560SN/A
1570SN/A    sc_token = (char *) malloc(sc_len);
1580SN/A    if (sc_token == NULL)
1590SN/A	panic("Insufficient virtual memory");
1600SN/A} /* init_scanner */
1610SN/A
1624475SN/A/*
1630SN/A * token() fetches a token from the input stream
1640SN/A */
1650SN/Astatic int
1660SN/Atoken()
1670SN/A{
1680SN/A    int idx;
1690SN/A
1700SN/A    while (1) {
1710SN/A	memset(sc_token, 0, sc_len);
1720SN/A	sc_tokid = EOF;
1730SN/A	idx = 0;
1740SN/A
1750SN/A	/*
1760SN/A	 * if we need to read another argument, walk along the argument list;
1770SN/A	 * when we fall off the arglist, we'll just return EOF forever
1780SN/A	 */
1790SN/A	if (need) {
1800SN/A	    if (scc < 1)
1810SN/A		return sc_tokid;
1820SN/A	    sct = *scp;
1830SN/A	    scp++;
1840SN/A	    scc--;
1850SN/A	    need = 0;
1860SN/A	}
1874475SN/A	/*
1880SN/A	 * eat whitespace now - if we walk off the end of the argument,
1890SN/A	 * we'll continue, which puts us up at the top of the while loop
1900SN/A	 * to fetch the next argument in
1910SN/A	 */
1920SN/A	while (isspace(*sct))
1930SN/A	    ++sct;
1940SN/A	if (!*sct) {
1950SN/A	    need = 1;
1960SN/A	    continue;
1970SN/A	}
1980SN/A
1990SN/A	/*
2000SN/A	 * preserve the first character of the new token
2010SN/A	 */
2020SN/A	sc_token[0] = *sct++;
2030SN/A
2040SN/A	/*
2050SN/A	 * then see what it is
2060SN/A	 */
2070SN/A	if (isdigit(sc_token[0])) {
2080SN/A	    while (isdigit(*sct))
2090SN/A		sc_token[++idx] = *sct++;
2100SN/A	    sc_token[++idx] = 0;
2110SN/A	    return sc_tokid = NUMBER;
2124475SN/A	} else if (isalpha(sc_token[0])) {
2130SN/A	    while (isalpha(*sct))
2140SN/A		sc_token[++idx] = *sct++;
2150SN/A	    sc_token[++idx] = 0;
2160SN/A	    return parse_token(sc_token);
2170SN/A	}
2180SN/A	else if (sc_token[0] == ':' || sc_token[0] == '.')
2190SN/A	    return sc_tokid = DOT;
2200SN/A	else if (sc_token[0] == '+')
2210SN/A	    return sc_tokid = PLUS;
2220SN/A	else if (*sct == '/')
2230SN/A	    return sc_tokid = SLASH;
2240SN/A	else
2250SN/A	    return sc_tokid = JUNK;
2260SN/A    } /* while (1) */
2270SN/A} /* token */
2280SN/A
2290SN/A
2300SN/A/*
2310SN/A * plonk() gives an appropriate error message if a token is incorrect
2320SN/A */
2330SN/Astatic void
2340SN/Aplonk(tok)
2350SN/A	int tok;
2360SN/A{
2374475SN/A    panic((tok == EOF) ? "incomplete time"
2380SN/A		       : "garbled time");
2390SN/A} /* plonk */
2400SN/A
2410SN/A
2420SN/A/*
2430SN/A * expect() gets a token and dies most horribly if it's not the token we want
2440SN/A */
2450SN/Astatic void
2460SN/Aexpect(desired)
2470SN/A	int desired;
2480SN/A{
2490SN/A    if (token() != desired)
2500SN/A	plonk(sc_tokid);	/* and we die here... */
2510SN/A} /* expect */
2520SN/A
2530SN/A
2540SN/A/*
2550SN/A * dateadd() adds a number of minutes to a date.  It is extraordinarily
2560SN/A * stupid regarding day-of-month overflow, and will most likely not
2570SN/A * work properly
2580SN/A */
2590SN/Astatic void
2600SN/Adateadd(minutes, tm)
2610SN/A	int minutes;
2624475SN/A	struct tm *tm;
2630SN/A{
2640SN/A    /* increment days */
2650SN/A
2660SN/A    while (minutes > 24*60) {
2670SN/A	minutes -= 24*60;
2680SN/A	tm->tm_mday++;
2690SN/A    }
2700SN/A
2710SN/A    /* increment hours */
2720SN/A    while (minutes > 60) {
2730SN/A	minutes -= 60;
2740SN/A	tm->tm_hour++;
2750SN/A	if (tm->tm_hour > 23) {
2760SN/A	    tm->tm_mday++;
2770SN/A	    tm->tm_hour = 0;
2780SN/A	}
2790SN/A    }
2800SN/A
2810SN/A    /* increment minutes */
2820SN/A    tm->tm_min += minutes;
2830SN/A
2840SN/A    if (tm->tm_min > 59) {
2850SN/A	tm->tm_hour++;
2860SN/A	tm->tm_min -= 60;
2870SN/A
2880SN/A	if (tm->tm_hour > 23) {
2890SN/A	    tm->tm_mday++;
2900SN/A	    tm->tm_hour = 0;
2910SN/A	}
2920SN/A    }
2930SN/A} /* dateadd */
2940SN/A
2950SN/A
2960SN/A/*
2970SN/A * plus() parses a now + time
2980SN/A *
2990SN/A *  at [NOW] PLUS NUMBER [MINUTES|HOURS|DAYS|WEEKS]
3000SN/A *
3010SN/A */
3020SN/Astatic void
3030SN/Aplus(tm)
3040SN/A	struct tm *tm;
3050SN/A{
3060SN/A    int delay;
3070SN/A
3080SN/A    expect(NUMBER);
3090SN/A
3100SN/A    delay = atoi(sc_token);
3110SN/A
3120SN/A    switch (token()) {
3134475SN/A    case WEEKS:
3140SN/A	    delay *= 7;
3154475SN/A    case DAYS:
3160SN/A	    delay *= 24;
3170SN/A    case HOURS:
3180SN/A	    delay *= 60;
3190SN/A    case MINUTES:
3200SN/A	    dateadd(delay, tm);
3210SN/A	    return;
3220SN/A    }
3230SN/A    plonk(sc_tokid);
3240SN/A} /* plus */
3250SN/A
3260SN/A
3270SN/A/*
3280SN/A * tod() computes the time of day
3290SN/A *     [NUMBER [DOT NUMBER] [AM|PM]]
3300SN/A */
3310SN/Astatic void
3320SN/Atod(tm)
3330SN/A	struct tm *tm;
3340SN/A{
3350SN/A    int hour, minute = 0;
3360SN/A    int tlen;
3370SN/A
3380SN/A    hour = atoi(sc_token);
3390SN/A    tlen = strlen(sc_token);
3400SN/A
3410SN/A    /*
3420SN/A     * first pick out the time of day - if it's 4 digits, we assume
3430SN/A     * a HHMM time, otherwise it's HH DOT MM time
3440SN/A     */
3450SN/A    if (token() == DOT) {
3460SN/A	expect(NUMBER);
3470SN/A	minute = atoi(sc_token);
3480SN/A	if (minute > 59)
3490SN/A	    panic("garbled time");
3500SN/A	token();
3510SN/A    } else if (tlen == 4) {
3524475SN/A	minute = hour%100;
3530SN/A	if (minute > 59)
3544475SN/A	    panic("garbeld time");
3550SN/A	hour = hour/100;
3560SN/A    }
3570SN/A
3580SN/A    /*
3590SN/A     * check if an AM or PM specifier was given
3600SN/A     */
3610SN/A    if (sc_tokid == AM || sc_tokid == PM) {
3620SN/A	if (hour > 12)
3630SN/A	    panic("garbled time");
3640SN/A
3650SN/A	if (sc_tokid == PM)
3660SN/A	    hour += 12;
3670SN/A	token();
3680SN/A    } else if (hour > 23)
3690SN/A	panic("garbled time");
3700SN/A
3710SN/A    /*
3720SN/A     * if we specify an absolute time, we don't want to bump the day even
3730SN/A     * if we've gone past that time - but if we're specifying a time plus
3740SN/A     * a relative offset, it's okay to bump things
3750SN/A     */
3760SN/A    if ((sc_tokid == EOF || sc_tokid == PLUS) && tm->tm_hour > hour)
3770SN/A	tm->tm_mday++;
3780SN/A
3790SN/A    tm->tm_hour = hour;
3800SN/A    tm->tm_min = minute;
3810SN/A    if (tm->tm_hour == 24) {
3820SN/A	tm->tm_hour = 0;
3830SN/A	tm->tm_mday++;
3840SN/A    }
3850SN/A} /* tod */
3860SN/A
3870SN/A
3880SN/A/*
3890SN/A * assign_date() assigns a date, wrapping to next year if needed
3904475SN/A */
3910SN/Astatic void
3924475SN/Aassign_date(tm, mday, mon, year)
3930SN/A	struct tm *tm;
3940SN/A	long mday, mon, year;
3950SN/A{
3960SN/A    if (year > 99) {
3970SN/A	if (year > 1899)
3980SN/A	    year -= 1900;
3990SN/A	else
4000SN/A	    panic("garbled time");
4010SN/A    }
4020SN/A
4030SN/A    if (year < 0 &&
4040SN/A	(tm->tm_mon > mon ||(tm->tm_mon == mon && tm->tm_mday > mday)))
4050SN/A	year = tm->tm_year + 1;
4060SN/A
4070SN/A    tm->tm_mday = mday;
4080SN/A    tm->tm_mon = mon;
4090SN/A
4100SN/A    if (year >= 0)
4110SN/A	tm->tm_year = year;
4120SN/A} /* assign_date */
4130SN/A
4140SN/A
4150SN/A/*
4164475SN/A * month() picks apart a month specification
4170SN/A *
4180SN/A *  /[<month> NUMBER [NUMBER]]           \
4190SN/A *  |[TOMORROW]                          |
4200SN/A *  |NUMBER [SLASH NUMBER [SLASH NUMBER]]|
4210SN/A *  \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/
4220SN/A */
4230SN/Astatic void
4240SN/Amonth(tm)
4250SN/A	struct tm *tm;
4260SN/A{
4270SN/A    long year= (-1);
4280SN/A    long mday, mon;
4290SN/A    int tlen;
4300SN/A
4310SN/A    switch (sc_tokid) {
4320SN/A    case PLUS:
4330SN/A	    plus(tm);
4340SN/A	    break;
4350SN/A
4360SN/A    case TOMORROW:
4370SN/A	    /* do something tomorrow */
4380SN/A	    tm->tm_mday ++;
4390SN/A    case TODAY:	/* force ourselves to stay in today - no further processing */
4400SN/A	    token();
4410SN/A	    break;
4420SN/A
4434475SN/A    case JAN: case FEB: case MAR: case APR: case MAY: case JUN:
4440SN/A    case JUL: case AUG: case SEP: case OCT: case NOV: case DEC:
4450SN/A	    /*
4460SN/A	     * do month mday [year]
4470SN/A	     */
4480SN/A	    mon = (sc_tokid-JAN);
4490SN/A	    expect(NUMBER);
4500SN/A	    mday = atol(sc_token);
4510SN/A	    if (token() == NUMBER) {
4520SN/A		year = atol(sc_token);
4530SN/A		token();
4540SN/A	    }
4550SN/A	    assign_date(tm, mday, mon, year);
4560SN/A	    break;
4570SN/A
4580SN/A    case NUMBER:
4590SN/A	    /*
4600SN/A	     * get numeric MMDDYY, mm/dd/yy, or dd.mm.yy
4610SN/A	     */
4620SN/A	    tlen = strlen(sc_token);
4630SN/A	    mon = atol(sc_token);
4640SN/A	    token();
4650SN/A
4660SN/A	    if (sc_tokid == SLASH || sc_tokid == DOT) {
4670SN/A		int sep;
4680SN/A
4690SN/A		sep = sc_tokid;
4700SN/A		expect(NUMBER);
4710SN/A		mday = atol(sc_token);
4720SN/A		if (token() == sep) {
4730SN/A		    expect(NUMBER);
4740SN/A		    year = atol(sc_token);
4750SN/A		    token();
4760SN/A		}
4770SN/A
4780SN/A		/*
4790SN/A		 * flip months and days for european timing
4800SN/A		 */
4810SN/A		if (sep == DOT) {
4820SN/A		    int x = mday;
4830SN/A		    mday = mon;
4840SN/A		    mon = x;
4850SN/A		}
4860SN/A	    } else if (tlen == 6 || tlen == 8) {
4870SN/A		if (tlen == 8) {
4880SN/A		    year = (mon % 10000) - 1900;
4890SN/A		    mon /= 10000;
4900SN/A		} else {
4910SN/A		    year = mon % 100;
4920SN/A		    mon /= 100;
4930SN/A		}
4940SN/A		mday = mon % 100;
4950SN/A		mon /= 100;
4960SN/A	    } else
4970SN/A		panic("garbled time");
4980SN/A
4990SN/A	    mon--;
5000SN/A	    if (mon < 0 || mon > 11 || mday < 1 || mday > 31)
5010SN/A		panic("garbled time");
5020SN/A
5030SN/A	    assign_date(tm, mday, mon, year);
5040SN/A	    break;
5050SN/A    } /* case */
5060SN/A} /* month */
5070SN/A
5080SN/A
5090SN/A/* Global functions */
5100SN/A
5110SN/Atime_t
5120SN/Aparsetime(argc, argv)
5130SN/A	int argc;
5144475SN/A	char **argv;
5150SN/A{
5160SN/A/*
5170SN/A * Do the argument parsing, die if necessary, and return the time the job
5180SN/A * should be run.
5190SN/A */
5200SN/A    time_t nowtimer, runtimer;
5214475SN/A    struct tm nowtime, runtime;
5220SN/A    int hr = 0;
5230SN/A    /* this MUST be initialized to zero for midnight/noon/teatime */
5240SN/A
5250SN/A    nowtimer = time(NULL);
5260SN/A    nowtime = *localtime(&nowtimer);
5270SN/A
5280SN/A    runtime = nowtime;
5290SN/A    runtime.tm_sec = 0;
5300SN/A    runtime.tm_isdst = 0;
5310SN/A
5320SN/A    if (argc <= optind)
5330SN/A	usage();
5340SN/A
5350SN/A    init_scanner(argc-optind, argv+optind);
5360SN/A
5370SN/A    switch (token()) {
5380SN/A    case NOW:	/* now is optional prefix for PLUS tree */
5390SN/A	    expect(PLUS);
5400SN/A    case PLUS:
5410SN/A	    plus(&runtime);
5420SN/A	    break;
5430SN/A
5440SN/A    case NUMBER:
5450SN/A	    tod(&runtime);
5460SN/A	    month(&runtime);
5470SN/A	    break;
5480SN/A
5490SN/A	    /*
5500SN/A	     * evil coding for TEATIME|NOON|MIDNIGHT - we've initialised
5510SN/A	     * hr to zero up above, then fall into this case in such a
5520SN/A	     * way so we add +12 +4 hours to it for teatime, +12 hours
5530SN/A	     * to it for noon, and nothing at all for midnight, then
5540SN/A	     * set our runtime to that hour before leaping into the
5550SN/A	     * month scanner
5560SN/A	     */
5570SN/A    case TEATIME:
5580SN/A	    hr += 4;
5590SN/A    case NOON:
5600SN/A	    hr += 12;
5610SN/A    case MIDNIGHT:
5620SN/A	    if (runtime.tm_hour >= hr)
5630SN/A		runtime.tm_mday++;
5640SN/A	    runtime.tm_hour = hr;
5650SN/A	    runtime.tm_min = 0;
5660SN/A	    token();
5670SN/A	    /* fall through to month setting */
5680SN/A    default:
5690SN/A	    month(&runtime);
5700SN/A	    break;
5710SN/A    } /* ugly case statement */
5720SN/A    expect(EOF);
5730SN/A
5740SN/A    /*
5750SN/A     * adjust for daylight savings time
5760SN/A     */
577    runtime.tm_isdst = -1;
578    runtimer = mktime(&runtime);
579    if (runtime.tm_isdst > 0) {
580	runtimer -= 3600;
581	runtimer = mktime(&runtime);
582    }
583
584    if (runtimer < 0)
585	panic("garbled time");
586
587    if (nowtimer > runtimer)
588	panic("Trying to travel back in time");
589
590    return runtimer;
591} /* parsetime */
592