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