parsetime.c revision 6079
10SN/A/* 24475SN/A * parsetime.c - parse time for at(1) 30SN/A * Copyright (C) 1993 Thomas Koenig 40SN/A * 50SN/A * modifications for english-language times 60SN/A * Copyright (C) 1993 David Parsons 72362SN/A * All rights reserved. 80SN/A * 92362SN/A * Redistribution and use in source and binary forms, with or without 100SN/A * modification, are permitted provided that the following conditions 110SN/A * are met: 120SN/A * 1. Redistributions of source code must retain the above copyright 130SN/A * notice, this list of conditions and the following disclaimer. 140SN/A * 2. The name of the author(s) may not be used to endorse or promote 150SN/A * products derived from this software without specific prior written 160SN/A * permission. 170SN/A * 180SN/A * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 190SN/A * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 200SN/A * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 212362SN/A * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 222362SN/A * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 232362SN/A * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 240SN/A * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 250SN/A * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT 260SN/A * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 270SN/A * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 280SN/A * 290SN/A * at [NOW] PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS 300SN/A * /NUMBER [DOT NUMBER] [AM|PM]\ /[MONTH NUMBER [NUMBER]] \ 310SN/A * |NOON | |[TOMORROW] | 320SN/A * |MIDNIGHT | |NUMBER [SLASH NUMBER [SLASH NUMBER]]| 330SN/A * \TEATIME / \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/ 340SN/A */ 350SN/A 360SN/A/* System Headers */ 370SN/A 380SN/A#include <sys/types.h> 390SN/A#include <errno.h> 400SN/A#include <stdio.h> 410SN/A#include <stdlib.h> 420SN/A#include <string.h> 430SN/A#include <time.h> 440SN/A#include <unistd.h> 450SN/A#include <ctype.h> 460SN/A 470SN/A/* Local headers */ 480SN/A 490SN/A#include "at.h" 500SN/A#include "panic.h" 510SN/A 520SN/A 530SN/A/* Structures and unions */ 540SN/A 550SN/Aenum { /* symbols */ 560SN/A MIDNIGHT, NOON, TEATIME, 570SN/A PM, AM, TOMORROW, TODAY, NOW, 580SN/A MINUTES, HOURS, DAYS, WEEKS, 590SN/A NUMBER, PLUS, DOT, SLASH, ID, JUNK, 600SN/A JAN, FEB, MAR, APR, MAY, JUN, 610SN/A JUL, AUG, SEP, OCT, NOV, DEC 620SN/A}; 630SN/A 640SN/A/* 650SN/A * parse translation table - table driven parsers can be your FRIEND! 660SN/A */ 670SN/Astruct { 680SN/A char *name; /* token name */ 690SN/A int value; /* token id */ 700SN/A} Specials[] = { 710SN/A { "midnight", MIDNIGHT }, /* 00:00:00 of today or tomorrow */ 720SN/A { "noon", NOON }, /* 12:00:00 of today or tomorrow */ 730SN/A { "teatime", TEATIME }, /* 16:00:00 of today or tomorrow */ 740SN/A { "am", AM }, /* morning times for 0-12 clock */ 750SN/A { "pm", PM }, /* evening times for 0-12 clock */ 760SN/A { "tomorrow", TOMORROW }, /* execute 24 hours from time */ 770SN/A { "today", TODAY }, /* execute today - don't advance time */ 780SN/A { "now", NOW }, /* opt prefix for PLUS */ 790SN/A 800SN/A { "minute", MINUTES }, /* minutes multiplier */ 810SN/A { "min", MINUTES }, 820SN/A { "m", MINUTES }, 830SN/A { "minutes", MINUTES }, /* (pluralized) */ 840SN/A { "hour", HOURS }, /* hours ... */ 854475SN/A { "hr", HOURS }, /* abbreviated */ 864475SN/A { "h", HOURS }, 874475SN/A { "hours", HOURS }, /* (pluralized) */ 884475SN/A { "day", DAYS }, /* days ... */ 894475SN/A { "d", DAYS }, 900SN/A { "days", DAYS }, /* (pluralized) */ 910SN/A { "week", WEEKS }, /* week ... */ 924475SN/A { "w", WEEKS }, 934475SN/A { "weeks", WEEKS }, /* (pluralized) */ 944475SN/A { "jan", JAN }, 954475SN/A { "feb", FEB }, 964475SN/A { "mar", MAR }, 970SN/A { "apr", APR }, 980SN/A { "may", MAY }, 994475SN/A { "jun", JUN }, 1004475SN/A { "jul", JUL }, 1014475SN/A { "aug", AUG }, 1024475SN/A { "sep", SEP }, 1034475SN/A { "oct", OCT }, 1044475SN/A { "nov", NOV }, 1050SN/A { "dec", DEC } 1060SN/A} ; 1070SN/A 1080SN/A/* File scope variables */ 1090SN/A 1100SN/Astatic char **scp; /* scanner - pointer at arglist */ 1110SN/Astatic char scc; /* scanner - count of remaining arguments */ 1120SN/Astatic char *sct; /* scanner - next char pointer in current argument */ 1130SN/Astatic int need; /* scanner - need to advance to next argument */ 1140SN/A 1150SN/Astatic char *sc_token; /* scanner - token buffer */ 1160SN/Astatic size_t sc_len; /* scanner - lenght of token buffer */ 1170SN/Astatic int sc_tokid; /* scanner - token id */ 1180SN/A 1190SN/Astatic char rcsid[] = "$Id: parsetime.c,v 1.1 1994/01/05 01:09:08 nate Exp $"; 1200SN/A 1210SN/A/* Local functions */ 1220SN/A 1230SN/A/* 1240SN/A * parse a token, checking if it's something special to us 1250SN/A */ 1260SN/Astatic int 1270SN/Aparse_token(arg) 1280SN/A char *arg; 1290SN/A{ 1300SN/A int i; 1310SN/A 1320SN/A for (i=0; i<(sizeof Specials/sizeof Specials[0]); i++) 1330SN/A if (strcasecmp(Specials[i].name, arg) == 0) { 1340SN/A return sc_tokid = Specials[i].value; 1354475SN/A } 1360SN/A 1370SN/A /* not special - must be some random id */ 1380SN/A return ID; 1390SN/A} /* parse_token */ 1400SN/A 1410SN/A 1420SN/A/* 1430SN/A * init_scanner() sets up the scanner to eat arguments 1440SN/A */ 1450SN/Astatic void 1460SN/Ainit_scanner(argc, argv) 1470SN/A int argc; 1480SN/A char **argv; 1490SN/A{ 1500SN/A scp = argv; 1510SN/A scc = argc; 1520SN/A need = 1; 1530SN/A sc_len = 1; 1540SN/A while (--argc > 0) 1550SN/A sc_len += strlen(*++argv); 1560SN/A 1570SN/A sc_token = (char *) malloc(sc_len); 1580SN/A if (sc_token == NULL) 1590SN/A panic("Insufficient virtual memory"); 1600SN/A} /* init_scanner */ 1610SN/A 1624475SN/A/* 1630SN/A * token() fetches a token from the input stream 1640SN/A */ 1650SN/Astatic int 1660SN/Atoken() 1670SN/A{ 1680SN/A int idx; 1690SN/A 1700SN/A while (1) { 1710SN/A memset(sc_token, 0, sc_len); 1720SN/A sc_tokid = EOF; 1730SN/A idx = 0; 1740SN/A 1750SN/A /* 1760SN/A * if we need to read another argument, walk along the argument list; 1770SN/A * when we fall off the arglist, we'll just return EOF forever 1780SN/A */ 1790SN/A if (need) { 1800SN/A if (scc < 1) 1810SN/A return sc_tokid; 1820SN/A sct = *scp; 1830SN/A scp++; 1840SN/A scc--; 1850SN/A need = 0; 1860SN/A } 1874475SN/A /* 1880SN/A * eat whitespace now - if we walk off the end of the argument, 1890SN/A * we'll continue, which puts us up at the top of the while loop 1900SN/A * to fetch the next argument in 1910SN/A */ 1920SN/A while (isspace(*sct)) 1930SN/A ++sct; 1940SN/A if (!*sct) { 1950SN/A need = 1; 1960SN/A continue; 1970SN/A } 1980SN/A 1990SN/A /* 2000SN/A * preserve the first character of the new token 2010SN/A */ 2020SN/A sc_token[0] = *sct++; 2030SN/A 2040SN/A /* 2050SN/A * then see what it is 2060SN/A */ 2070SN/A if (isdigit(sc_token[0])) { 2080SN/A while (isdigit(*sct)) 2090SN/A sc_token[++idx] = *sct++; 2100SN/A sc_token[++idx] = 0; 2110SN/A return sc_tokid = NUMBER; 2124475SN/A } else if (isalpha(sc_token[0])) { 2130SN/A while (isalpha(*sct)) 2140SN/A sc_token[++idx] = *sct++; 2150SN/A sc_token[++idx] = 0; 2160SN/A return parse_token(sc_token); 2170SN/A } 2180SN/A else if (sc_token[0] == ':' || sc_token[0] == '.') 2190SN/A return sc_tokid = DOT; 2200SN/A else if (sc_token[0] == '+') 2210SN/A return sc_tokid = PLUS; 2220SN/A else if (*sct == '/') 2230SN/A return sc_tokid = SLASH; 2240SN/A else 2250SN/A return sc_tokid = JUNK; 2260SN/A } /* while (1) */ 2270SN/A} /* token */ 2280SN/A 2290SN/A 2300SN/A/* 2310SN/A * plonk() gives an appropriate error message if a token is incorrect 2320SN/A */ 2330SN/Astatic void 2340SN/Aplonk(tok) 2350SN/A int tok; 2360SN/A{ 2374475SN/A panic((tok == EOF) ? "incomplete time" 2380SN/A : "garbled time"); 2390SN/A} /* plonk */ 2400SN/A 2410SN/A 2420SN/A/* 2430SN/A * expect() gets a token and dies most horribly if it's not the token we want 2440SN/A */ 2450SN/Astatic void 2460SN/Aexpect(desired) 2470SN/A int desired; 2480SN/A{ 2490SN/A if (token() != desired) 2500SN/A plonk(sc_tokid); /* and we die here... */ 2510SN/A} /* expect */ 2520SN/A 2530SN/A 2540SN/A/* 2550SN/A * dateadd() adds a number of minutes to a date. It is extraordinarily 2560SN/A * stupid regarding day-of-month overflow, and will most likely not 2570SN/A * work properly 2580SN/A */ 2590SN/Astatic void 2600SN/Adateadd(minutes, tm) 2610SN/A int minutes; 2624475SN/A struct tm *tm; 2630SN/A{ 2640SN/A /* increment days */ 2650SN/A 2660SN/A while (minutes > 24*60) { 2670SN/A minutes -= 24*60; 2680SN/A tm->tm_mday++; 2690SN/A } 2700SN/A 2710SN/A /* increment hours */ 2720SN/A while (minutes > 60) { 2730SN/A minutes -= 60; 2740SN/A tm->tm_hour++; 2750SN/A if (tm->tm_hour > 23) { 2760SN/A tm->tm_mday++; 2770SN/A tm->tm_hour = 0; 2780SN/A } 2790SN/A } 2800SN/A 2810SN/A /* increment minutes */ 2820SN/A tm->tm_min += minutes; 2830SN/A 2840SN/A if (tm->tm_min > 59) { 2850SN/A tm->tm_hour++; 2860SN/A tm->tm_min -= 60; 2870SN/A 2880SN/A if (tm->tm_hour > 23) { 2890SN/A tm->tm_mday++; 2900SN/A tm->tm_hour = 0; 2910SN/A } 2920SN/A } 2930SN/A} /* dateadd */ 2940SN/A 2950SN/A 2960SN/A/* 2970SN/A * plus() parses a now + time 2980SN/A * 2990SN/A * at [NOW] PLUS NUMBER [MINUTES|HOURS|DAYS|WEEKS] 3000SN/A * 3010SN/A */ 3020SN/Astatic void 3030SN/Aplus(tm) 3040SN/A struct tm *tm; 3050SN/A{ 3060SN/A int delay; 3070SN/A 3080SN/A expect(NUMBER); 3090SN/A 3100SN/A delay = atoi(sc_token); 3110SN/A 3120SN/A switch (token()) { 3134475SN/A case WEEKS: 3140SN/A delay *= 7; 3154475SN/A case DAYS: 3160SN/A delay *= 24; 3170SN/A case HOURS: 3180SN/A delay *= 60; 3190SN/A case MINUTES: 3200SN/A dateadd(delay, tm); 3210SN/A return; 3220SN/A } 3230SN/A plonk(sc_tokid); 3240SN/A} /* plus */ 3250SN/A 3260SN/A 3270SN/A/* 3280SN/A * tod() computes the time of day 3290SN/A * [NUMBER [DOT NUMBER] [AM|PM]] 3300SN/A */ 3310SN/Astatic void 3320SN/Atod(tm) 3330SN/A struct tm *tm; 3340SN/A{ 3350SN/A int hour, minute = 0; 3360SN/A int tlen; 3370SN/A 3380SN/A hour = atoi(sc_token); 3390SN/A tlen = strlen(sc_token); 3400SN/A 3410SN/A /* 3420SN/A * first pick out the time of day - if it's 4 digits, we assume 3430SN/A * a HHMM time, otherwise it's HH DOT MM time 3440SN/A */ 3450SN/A if (token() == DOT) { 3460SN/A expect(NUMBER); 3470SN/A minute = atoi(sc_token); 3480SN/A if (minute > 59) 3490SN/A panic("garbled time"); 3500SN/A token(); 3510SN/A } else if (tlen == 4) { 3524475SN/A minute = hour%100; 3530SN/A if (minute > 59) 3544475SN/A panic("garbeld time"); 3550SN/A hour = hour/100; 3560SN/A } 3570SN/A 3580SN/A /* 3590SN/A * check if an AM or PM specifier was given 3600SN/A */ 3610SN/A if (sc_tokid == AM || sc_tokid == PM) { 3620SN/A if (hour > 12) 3630SN/A panic("garbled time"); 3640SN/A 3650SN/A if (sc_tokid == PM) 3660SN/A hour += 12; 3670SN/A token(); 3680SN/A } else if (hour > 23) 3690SN/A panic("garbled time"); 3700SN/A 3710SN/A /* 3720SN/A * if we specify an absolute time, we don't want to bump the day even 3730SN/A * if we've gone past that time - but if we're specifying a time plus 3740SN/A * a relative offset, it's okay to bump things 3750SN/A */ 3760SN/A if ((sc_tokid == EOF || sc_tokid == PLUS) && tm->tm_hour > hour) 3770SN/A tm->tm_mday++; 3780SN/A 3790SN/A tm->tm_hour = hour; 3800SN/A tm->tm_min = minute; 3810SN/A if (tm->tm_hour == 24) { 3820SN/A tm->tm_hour = 0; 3830SN/A tm->tm_mday++; 3840SN/A } 3850SN/A} /* tod */ 3860SN/A 3870SN/A 3880SN/A/* 3890SN/A * assign_date() assigns a date, wrapping to next year if needed 3904475SN/A */ 3910SN/Astatic void 3924475SN/Aassign_date(tm, mday, mon, year) 3930SN/A struct tm *tm; 3940SN/A long mday, mon, year; 3950SN/A{ 3960SN/A if (year > 99) { 3970SN/A if (year > 1899) 3980SN/A year -= 1900; 3990SN/A else 4000SN/A panic("garbled time"); 4010SN/A } 4020SN/A 4030SN/A if (year < 0 && 4040SN/A (tm->tm_mon > mon ||(tm->tm_mon == mon && tm->tm_mday > mday))) 4050SN/A year = tm->tm_year + 1; 4060SN/A 4070SN/A tm->tm_mday = mday; 4080SN/A tm->tm_mon = mon; 4090SN/A 4100SN/A if (year >= 0) 4110SN/A tm->tm_year = year; 4120SN/A} /* assign_date */ 4130SN/A 4140SN/A 4150SN/A/* 4164475SN/A * month() picks apart a month specification 4170SN/A * 4180SN/A * /[<month> NUMBER [NUMBER]] \ 4190SN/A * |[TOMORROW] | 4200SN/A * |NUMBER [SLASH NUMBER [SLASH NUMBER]]| 4210SN/A * \PLUS NUMBER MINUTES|HOURS|DAYS|WEEKS/ 4220SN/A */ 4230SN/Astatic void 4240SN/Amonth(tm) 4250SN/A struct tm *tm; 4260SN/A{ 4270SN/A long year= (-1); 4280SN/A long mday, mon; 4290SN/A int tlen; 4300SN/A 4310SN/A switch (sc_tokid) { 4320SN/A case PLUS: 4330SN/A plus(tm); 4340SN/A break; 4350SN/A 4360SN/A case TOMORROW: 4370SN/A /* do something tomorrow */ 4380SN/A tm->tm_mday ++; 4390SN/A case TODAY: /* force ourselves to stay in today - no further processing */ 4400SN/A token(); 4410SN/A break; 4420SN/A 4434475SN/A case JAN: case FEB: case MAR: case APR: case MAY: case JUN: 4440SN/A case JUL: case AUG: case SEP: case OCT: case NOV: case DEC: 4450SN/A /* 4460SN/A * do month mday [year] 4470SN/A */ 4480SN/A mon = (sc_tokid-JAN); 4490SN/A expect(NUMBER); 4500SN/A mday = atol(sc_token); 4510SN/A if (token() == NUMBER) { 4520SN/A year = atol(sc_token); 4530SN/A token(); 4540SN/A } 4550SN/A assign_date(tm, mday, mon, year); 4560SN/A break; 4570SN/A 4580SN/A case NUMBER: 4590SN/A /* 4600SN/A * get numeric MMDDYY, mm/dd/yy, or dd.mm.yy 4610SN/A */ 4620SN/A tlen = strlen(sc_token); 4630SN/A mon = atol(sc_token); 4640SN/A token(); 4650SN/A 4660SN/A if (sc_tokid == SLASH || sc_tokid == DOT) { 4670SN/A int sep; 4680SN/A 4690SN/A sep = sc_tokid; 4700SN/A expect(NUMBER); 4710SN/A mday = atol(sc_token); 4720SN/A if (token() == sep) { 4730SN/A expect(NUMBER); 4740SN/A year = atol(sc_token); 4750SN/A token(); 4760SN/A } 4770SN/A 4780SN/A /* 4790SN/A * flip months and days for european timing 4800SN/A */ 4810SN/A if (sep == DOT) { 4820SN/A int x = mday; 4830SN/A mday = mon; 4840SN/A mon = x; 4850SN/A } 4860SN/A } else if (tlen == 6 || tlen == 8) { 4870SN/A if (tlen == 8) { 4880SN/A year = (mon % 10000) - 1900; 4890SN/A mon /= 10000; 4900SN/A } else { 4910SN/A year = mon % 100; 4920SN/A mon /= 100; 4930SN/A } 4940SN/A mday = mon % 100; 4950SN/A mon /= 100; 4960SN/A } else 4970SN/A panic("garbled time"); 4980SN/A 4990SN/A mon--; 5000SN/A if (mon < 0 || mon > 11 || mday < 1 || mday > 31) 5010SN/A panic("garbled time"); 5020SN/A 5030SN/A assign_date(tm, mday, mon, year); 5040SN/A break; 5050SN/A } /* case */ 5060SN/A} /* month */ 5070SN/A 5080SN/A 5090SN/A/* Global functions */ 5100SN/A 5110SN/Atime_t 5120SN/Aparsetime(argc, argv) 5130SN/A int argc; 5144475SN/A char **argv; 5150SN/A{ 5160SN/A/* 5170SN/A * Do the argument parsing, die if necessary, and return the time the job 5180SN/A * should be run. 5190SN/A */ 5200SN/A time_t nowtimer, runtimer; 5214475SN/A struct tm nowtime, runtime; 5220SN/A int hr = 0; 5230SN/A /* this MUST be initialized to zero for midnight/noon/teatime */ 5240SN/A 5250SN/A nowtimer = time(NULL); 5260SN/A nowtime = *localtime(&nowtimer); 5270SN/A 5280SN/A runtime = nowtime; 5290SN/A runtime.tm_sec = 0; 5300SN/A runtime.tm_isdst = 0; 5310SN/A 5320SN/A if (argc <= optind) 5330SN/A usage(); 5340SN/A 5350SN/A init_scanner(argc-optind, argv+optind); 5360SN/A 5370SN/A switch (token()) { 5380SN/A case NOW: /* now is optional prefix for PLUS tree */ 5390SN/A expect(PLUS); 5400SN/A case PLUS: 5410SN/A plus(&runtime); 5420SN/A break; 5430SN/A 5440SN/A case NUMBER: 5450SN/A tod(&runtime); 5460SN/A month(&runtime); 5470SN/A break; 5480SN/A 5490SN/A /* 5500SN/A * evil coding for TEATIME|NOON|MIDNIGHT - we've initialised 5510SN/A * hr to zero up above, then fall into this case in such a 5520SN/A * way so we add +12 +4 hours to it for teatime, +12 hours 5530SN/A * to it for noon, and nothing at all for midnight, then 5540SN/A * set our runtime to that hour before leaping into the 5550SN/A * month scanner 5560SN/A */ 5570SN/A case TEATIME: 5580SN/A hr += 4; 5590SN/A case NOON: 5600SN/A hr += 12; 5610SN/A case MIDNIGHT: 5620SN/A if (runtime.tm_hour >= hr) 5630SN/A runtime.tm_mday++; 5640SN/A runtime.tm_hour = hr; 5650SN/A runtime.tm_min = 0; 5660SN/A token(); 5670SN/A /* fall through to month setting */ 5680SN/A default: 5690SN/A month(&runtime); 5700SN/A break; 5710SN/A } /* ugly case statement */ 5720SN/A expect(EOF); 5730SN/A 5740SN/A /* 5750SN/A * adjust for daylight savings time 5760SN/A */ 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