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