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