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