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