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