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