freebsd-dev/gnu/usr.bin/rcs/lib/partime.c
1993-06-18 04:22:21 +00:00

640 lines
18 KiB
C

/*
* PARTIME parse date/time string into a TM structure
*
* Returns:
* 0 if parsing failed
* else time values in specified TM structure and zone (unspecified values
* set to TMNULL)
* Notes:
* This code is quasi-public; it may be used freely in like software.
* It is not to be sold, nor used in licensed software without
* permission of the author.
* For everyone's benefit, please report bugs and improvements!
* Copyright 1980 by Ken Harrenstien, SRI International.
* (ARPANET: KLH @ SRI)
*/
/* Hacknotes:
* If parsing changed so that no backup needed, could perhaps modify
* to use a FILE input stream. Need terminator, though.
* Perhaps should return 0 on success, else a non-zero error val?
*/
/* $Log: partime.c,v $
* Revision 5.6 1991/08/19 03:13:55 eggert
* Update timezones.
*
* Revision 5.5 1991/04/21 11:58:18 eggert
* Don't put , just before } in initializer.
*
* Revision 5.4 1990/10/04 06:30:15 eggert
* Remove date vs time heuristics that fail between 2000 and 2400.
* Check for overflow when lexing an integer.
* Parse 'Jan 10 LT' as 'Jan 10, LT', not 'Jan, 10 LT'.
*
* Revision 5.3 1990/09/24 18:56:31 eggert
* Update timezones.
*
* Revision 5.2 1990/09/04 08:02:16 eggert
* Don't parse two-digit years, because it won't work after 1999/12/31.
* Don't permit 'Aug Aug'.
*
* Revision 5.1 1990/08/29 07:13:49 eggert
* Be able to parse our own date format. Don't assume year<10000.
*
* Revision 5.0 1990/08/22 08:12:40 eggert
* Switch to GMT and fix the bugs exposed thereby. Update timezones.
* Ansify and Posixate. Fix peekahead and int-size bugs.
*
* Revision 1.4 89/05/01 14:48:46 narten
* fixed #ifdef DEBUG construct
*
* Revision 1.3 88/08/28 14:53:40 eggert
* Remove unportable "#endif XXX"s.
*
* Revision 1.2 87/03/27 14:21:53 jenkins
* Port to suns
*
* Revision 1.1 82/05/06 11:38:26 wft
* Initial revision
*
*/
#include "rcsbase.h"
libId(partId, "$Id: partime.c,v 5.6 1991/08/19 03:13:55 eggert Exp $")
#define given(v) (0 <= (v))
#define TMNULL (-1) /* Items not given are given this value */
#define TZ_OFFSET (24*60) /* TMNULL < zone_offset - TZ_OFFSET */
struct tmwent {
char const *went;
short wval;
char wflgs;
char wtype;
};
/* wflgs */
#define TWTIME 02 /* Word is a time value (absence implies date) */
#define TWDST 04 /* Word is a DST-type timezone */
/* wtype */
#define TM_MON 1 /* month name */
#define TM_WDAY 2 /* weekday name */
#define TM_ZON 3 /* time zone name */
#define TM_LT 4 /* local time */
#define TM_DST 5 /* daylight savings time */
#define TM_12 6 /* AM, PM, NOON, or MIDNIGHT */
/* wval (for wtype==TM_12) */
#define T12_AM 1
#define T12_PM 2
#define T12_NOON 12
#define T12_MIDNIGHT 0
static struct tmwent const tmwords [] = {
{"january", 0, 0, TM_MON},
{"february", 1, 0, TM_MON},
{"march", 2, 0, TM_MON},
{"april", 3, 0, TM_MON},
{"may", 4, 0, TM_MON},
{"june", 5, 0, TM_MON},
{"july", 6, 0, TM_MON},
{"august", 7, 0, TM_MON},
{"september", 8, 0, TM_MON},
{"october", 9, 0, TM_MON},
{"november", 10, 0, TM_MON},
{"december", 11, 0, TM_MON},
{"sunday", 0, 0, TM_WDAY},
{"monday", 1, 0, TM_WDAY},
{"tuesday", 2, 0, TM_WDAY},
{"wednesday", 3, 0, TM_WDAY},
{"thursday", 4, 0, TM_WDAY},
{"friday", 5, 0, TM_WDAY},
{"saturday", 6, 0, TM_WDAY},
{"gmt", 0*60, TWTIME, TM_ZON}, /* Greenwich */
{"utc", 0*60, TWTIME, TM_ZON},
{"ut", 0*60, TWTIME, TM_ZON},
{"cut", 0*60, TWTIME, TM_ZON},
{"nzst", -12*60, TWTIME, TM_ZON}, /* New Zealand */
{"jst", -9*60, TWTIME, TM_ZON}, /* Japan */
{"kst", -9*60, TWTIME, TM_ZON}, /* Korea */
{"ist", -5*60-30, TWTIME, TM_ZON},/* India */
{"eet", -2*60, TWTIME, TM_ZON}, /* Eastern Europe */
{"cet", -1*60, TWTIME, TM_ZON}, /* Central Europe */
{"met", -1*60, TWTIME, TM_ZON}, /* Middle Europe */
{"wet", 0*60, TWTIME, TM_ZON}, /* Western Europe */
{"nst", 3*60+30, TWTIME, TM_ZON},/* Newfoundland */
{"ast", 4*60, TWTIME, TM_ZON}, /* Atlantic */
{"est", 5*60, TWTIME, TM_ZON}, /* Eastern */
{"cst", 6*60, TWTIME, TM_ZON}, /* Central */
{"mst", 7*60, TWTIME, TM_ZON}, /* Mountain */
{"pst", 8*60, TWTIME, TM_ZON}, /* Pacific */
{"akst", 9*60, TWTIME, TM_ZON}, /* Alaska */
{"hast", 10*60, TWTIME, TM_ZON}, /* Hawaii-Aleutian */
{"hst", 10*60, TWTIME, TM_ZON}, /* Hawaii */
{"sst", 11*60, TWTIME, TM_ZON}, /* Samoa */
{"nzdt", -12*60, TWTIME+TWDST, TM_ZON}, /* New Zealand */
{"kdt", -9*60, TWTIME+TWDST, TM_ZON}, /* Korea */
{"bst", 0*60, TWTIME+TWDST, TM_ZON}, /* Britain */
{"ndt", 3*60+30, TWTIME+TWDST, TM_ZON}, /* Newfoundland */
{"adt", 4*60, TWTIME+TWDST, TM_ZON}, /* Atlantic */
{"edt", 5*60, TWTIME+TWDST, TM_ZON}, /* Eastern */
{"cdt", 6*60, TWTIME+TWDST, TM_ZON}, /* Central */
{"mdt", 7*60, TWTIME+TWDST, TM_ZON}, /* Mountain */
{"pdt", 8*60, TWTIME+TWDST, TM_ZON}, /* Pacific */
{"akdt", 9*60, TWTIME+TWDST, TM_ZON}, /* Alaska */
{"hadt", 10*60, TWTIME+TWDST, TM_ZON}, /* Hawaii-Aleutian */
#if 0
/*
* The following names are duplicates or are not well attested.
* A standard is needed.
*/
{"east", -10*60, TWTIME, TM_ZON}, /* Eastern Australia */
{"cast", -9*60-30, TWTIME, TM_ZON},/* Central Australia */
{"cst", -8*60, TWTIME, TM_ZON}, /* China */
{"hkt", -8*60, TWTIME, TM_ZON}, /* Hong Kong */
{"sst", -8*60, TWTIME, TM_ZON}, /* Singapore */
{"wast", -8*60, TWTIME, TM_ZON}, /* Western Australia */
{"?", -6*60-30, TWTIME, TM_ZON},/* Burma */
{"?", -4*60-30, TWTIME, TM_ZON},/* Afghanistan */
{"it", -3*60-30, TWTIME, TM_ZON},/* Iran */
{"ist", -2*60, TWTIME, TM_ZON}, /* Israel */
{"mez", -1*60, TWTIME, TM_ZON}, /* Mittel-Europaeische Zeit */
{"ast", 1*60, TWTIME, TM_ZON}, /* Azores */
{"fst", 2*60, TWTIME, TM_ZON}, /* Fernando de Noronha */
{"bst", 3*60, TWTIME, TM_ZON}, /* Brazil */
{"wst", 4*60, TWTIME, TM_ZON}, /* Western Brazil */
{"ast", 5*60, TWTIME, TM_ZON}, /* Acre Brazil */
{"?", 9*60+30, TWTIME, TM_ZON},/* Marquesas */
{"?", 12*60, TWTIME, TM_ZON}, /* Kwajalein */
{"eadt", -10*60, TWTIME+TWDST, TM_ZON}, /* Eastern Australia */
{"cadt", -9*60-30, TWTIME+TWDST, TM_ZON}, /* Central Australia */
{"cdt", -8*60, TWTIME+TWDST, TM_ZON}, /* China */
{"wadt", -8*60, TWTIME+TWDST, TM_ZON}, /* Western Australia */
{"idt", -2*60, TWTIME+TWDST, TM_ZON}, /* Israel */
{"eest", -2*60, TWTIME+TWDST, TM_ZON}, /* Eastern Europe */
{"cest", -1*60, TWTIME+TWDST, TM_ZON}, /* Central Europe */
{"mest", -1*60, TWTIME+TWDST, TM_ZON}, /* Middle Europe */
{"mesz", -1*60, TWTIME+TWDST, TM_ZON}, /* Mittel-Europaeische Sommerzeit */
{"west", 0*60, TWTIME+TWDST, TM_ZON}, /* Western Europe */
{"adt", 1*60, TWTIME+TWDST, TM_ZON}, /* Azores */
{"fdt", 2*60, TWTIME+TWDST, TM_ZON}, /* Fernando de Noronha */
{"edt", 3*60, TWTIME+TWDST, TM_ZON}, /* Eastern Brazil */
{"wdt", 4*60, TWTIME+TWDST, TM_ZON}, /* Western Brazil */
{"adt", 5*60, TWTIME+TWDST, TM_ZON}, /* Acre Brazil */
#endif
{"lt", 0, TWTIME, TM_LT}, /* local time */
{"dst", 1*60, TWTIME, TM_DST}, /* daylight savings time */
{"ddst", 2*60, TWTIME, TM_DST}, /* double dst */
{"am", T12_AM, TWTIME, TM_12},
{"pm", T12_PM, TWTIME, TM_12},
{"noon", T12_NOON, TWTIME, TM_12},
{"midnight", T12_MIDNIGHT, TWTIME, TM_12},
{0, 0, 0, 0} /* Zero entry to terminate searches */
};
struct token {
char const *tcp;/* pointer to string */
int tcnt; /* # chars */
char tbrk; /* "break" char */
char tbrkl; /* last break char */
char tflg; /* 0 = alpha, 1 = numeric */
union { /* Resulting value; */
int tnum;/* either a #, or */
struct tmwent const *ttmw;/* a ptr to a tmwent. */
} tval;
};
static struct tmwent const*ptmatchstr P((char const*,int,struct tmwent const*));
static int pt12hack P((struct tm *,int));
static int ptitoken P((struct token *));
static int ptstash P((int *,int));
static int pttoken P((struct token *));
static int
goodzone(t, offset, am)
register struct token const *t;
int offset;
int *am;
{
register int m;
if (
t->tflg &&
t->tcnt == 4+offset &&
(m = t->tval.tnum) <= 2400 &&
isdigit(t->tcp[offset]) &&
(m%=100) < 60
) {
m += t->tval.tnum/100 * 60;
if (t->tcp[offset-1]=='+')
m = -m;
*am = m;
return 1;
}
return 0;
}
int
partime(astr, atm, zone)
char const *astr;
register struct tm *atm;
int *zone;
{
register int i;
struct token btoken, atoken;
int zone_offset; /* minutes west of GMT, plus TZ_OFFSET */
register char const *cp;
register char ch;
int ord, midnoon;
int *atmfield, dst, m;
int got1 = 0;
atm->tm_sec = TMNULL;
atm->tm_min = TMNULL;
atm->tm_hour = TMNULL;
atm->tm_mday = TMNULL;
atm->tm_mon = TMNULL;
atm->tm_year = TMNULL;
atm->tm_wday = TMNULL;
atm->tm_yday = TMNULL;
midnoon = TMNULL; /* and our own temp stuff */
zone_offset = TMNULL;
dst = TMNULL;
btoken.tcnt = btoken.tbrk = 0;
btoken.tcp = astr;
for (;; got1=1) {
if (!ptitoken(&btoken)) /* Get a token */
{ if(btoken.tval.tnum) return(0); /* Read error? */
if (given(midnoon)) /* EOF, wrap up */
if (!pt12hack(atm, midnoon))
return 0;
if (!given(atm->tm_min))
atm->tm_min = 0;
*zone =
(given(zone_offset) ? zone_offset-TZ_OFFSET : 0)
- (given(dst) ? dst : 0);
return got1;
}
if(btoken.tflg == 0) /* Alpha? */
{ i = btoken.tval.ttmw->wval;
switch (btoken.tval.ttmw->wtype) {
default:
return 0;
case TM_MON:
atmfield = &atm->tm_mon;
break;
case TM_WDAY:
atmfield = &atm->tm_wday;
break;
case TM_DST:
atmfield = &dst;
break;
case TM_LT:
if (ptstash(&dst, 0))
return 0;
i = 48*60; /* local time magic number -- see maketime() */
/* fall into */
case TM_ZON:
i += TZ_OFFSET;
if (btoken.tval.ttmw->wflgs & TWDST)
if (ptstash(&dst, 60))
return 0;
/* Peek ahead for offset immediately afterwards. */
if (
(btoken.tbrk=='-' || btoken.tbrk=='+') &&
(atoken=btoken, ++atoken.tcnt, ptitoken(&atoken)) &&
goodzone(&atoken, 0, &m)
) {
i += m;
btoken = atoken;
}
atmfield = &zone_offset;
break;
case TM_12:
atmfield = &midnoon;
}
if (ptstash(atmfield, i))
return(0); /* ERR: val already set */
continue;
}
/* Token is number. Lots of hairy heuristics. */
if (!isdigit(*btoken.tcp)) {
if (!goodzone(&btoken, 1, &m))
return 0;
zone_offset = TZ_OFFSET + m;
continue;
}
i = btoken.tval.tnum; /* Value now known to be valid; get it. */
if (btoken.tcnt == 3) /* 3 digits = HMM */
{
hhmm4: if (ptstash(&atm->tm_min, i%100))
return(0); /* ERR: min conflict */
i /= 100;
hh2: if (ptstash(&atm->tm_hour, i))
return(0); /* ERR: hour conflict */
continue;
}
if (4 < btoken.tcnt)
goto year4; /* far in the future */
if(btoken.tcnt == 4) /* 4 digits = YEAR or HHMM */
{ if (given(atm->tm_year)) goto hhmm4; /* Already got yr? */
if (given(atm->tm_hour)) goto year4; /* Already got hr? */
if(btoken.tbrk == ':') /* HHMM:SS ? */
if ( ptstash(&atm->tm_hour, i/100)
|| ptstash(&atm->tm_min, i%100))
return(0); /* ERR: hr/min clash */
else goto coltm2; /* Go handle SS */
if(btoken.tbrk != ',' && btoken.tbrk != '/'
&& (atoken=btoken, ptitoken(&atoken)) /* Peek */
&& ( atoken.tflg
? !isdigit(*atoken.tcp)
: atoken.tval.ttmw->wflgs & TWTIME)) /* HHMM-ZON */
goto hhmm4;
goto year4; /* Give up, assume year. */
}
/* From this point on, assume tcnt == 1 or 2 */
/* 2 digits = MM, DD, or HH (MM and SS caught at coltime) */
if(btoken.tbrk == ':') /* HH:MM[:SS] */
goto coltime; /* must be part of time. */
if (31 < i)
return 0;
/* Check for numerical-format date */
for (cp = "/-."; ch = *cp++;)
{ ord = (ch == '.' ? 0 : 1); /* n/m = D/M or M/D */
if(btoken.tbrk == ch) /* "NN-" */
{ if(btoken.tbrkl != ch)
{
atoken = btoken;
atoken.tcnt++;
if (ptitoken(&atoken)
&& atoken.tflg == 0
&& atoken.tval.ttmw->wtype == TM_MON)
goto dd2;
if(ord)goto mm2; else goto dd2; /* "NN-" */
} /* "-NN-" */
if (!given(atm->tm_mday)
&& given(atm->tm_year)) /* If "YYYY-NN-" */
goto mm2; /* then always MM */
if(ord)goto dd2; else goto mm2;
}
if(btoken.tbrkl == ch /* "-NN" */
&& given(ord ? atm->tm_mon : atm->tm_mday))
if (!given(ord ? atm->tm_mday : atm->tm_mon)) /* MM/DD */
if(ord)goto dd2; else goto mm2;
}
/* Now reduced to choice between HH and DD */
if (given(atm->tm_hour)) goto dd2; /* Have hour? Assume day. */
if (given(atm->tm_mday)) goto hh2; /* Have day? Assume hour. */
if (given(atm->tm_mon)) goto dd2; /* Have month? Assume day. */
if(i > 24) goto dd2; /* Impossible HH means DD */
atoken = btoken;
if (!ptitoken(&atoken)) /* Read ahead! */
if(atoken.tval.tnum) return(0); /* ERR: bad token */
else goto dd2; /* EOF, assume day. */
if ( atoken.tflg
? !isdigit(*atoken.tcp)
: atoken.tval.ttmw->wflgs & TWTIME)
/* If next token is a time spec, assume hour */
goto hh2; /* e.g. "3 PM", "11-EDT" */
dd2: if (ptstash(&atm->tm_mday, i)) /* Store day (1 based) */
return(0);
continue;
mm2: if (ptstash(&atm->tm_mon, i-1)) /* Store month (make zero based) */
return(0);
continue;
year4: if ((i-=1900) < 0 || ptstash(&atm->tm_year, i)) /* Store year-1900 */
return(0); /* ERR: year conflict */
continue;
/* Hack HH:MM[[:]SS] */
coltime:
if (ptstash(&atm->tm_hour, i)) return 0;
if (!ptitoken(&btoken))
return(!btoken.tval.tnum);
if(!btoken.tflg) return(0); /* ERR: HH:<alpha> */
if(btoken.tcnt == 4) /* MMSS */
if (ptstash(&atm->tm_min, btoken.tval.tnum/100)
|| ptstash(&atm->tm_sec, btoken.tval.tnum%100))
return(0);
else continue;
if(btoken.tcnt != 2
|| ptstash(&atm->tm_min, btoken.tval.tnum))
return(0); /* ERR: MM bad */
if (btoken.tbrk != ':') continue; /* Seconds follow? */
coltm2: if (!ptitoken(&btoken))
return(!btoken.tval.tnum);
if(!btoken.tflg || btoken.tcnt != 2 /* Verify SS */
|| ptstash(&atm->tm_sec, btoken.tval.tnum))
return(0); /* ERR: SS bad */
}
}
/* Store date/time value, return 0 if successful.
* Fail if entry is already set.
*/
static int
ptstash(adr,val)
int *adr;
int val;
{ register int *a;
if (given(*(a=adr)))
return 1;
*a = val;
return(0);
}
/* This subroutine is invoked for AM, PM, NOON and MIDNIGHT when wrapping up
* just prior to returning from partime.
*/
static int
pt12hack(tm, aval)
register struct tm *tm;
register int aval;
{ register int h = tm->tm_hour;
switch (aval) {
case T12_AM:
case T12_PM:
if (h > 12)
return 0;
if (h == 12)
tm->tm_hour = 0;
if (aval == T12_PM)
tm->tm_hour += 12;
break;
default:
if (0 < tm->tm_min || 0 < tm->tm_sec)
return 0;
if (!given(h) || h==12)
tm->tm_hour = aval;
else if (aval==T12_MIDNIGHT && (h==0 || h==24))
return 0;
}
return 1;
}
/* Get a token and identify it to some degree.
* Returns 0 on failure; token.tval will be 0 for normal EOF, otherwise
* hit error of some sort
*/
static int
ptitoken(tkp)
register struct token *tkp;
{
register char const *cp;
register int i, j, k;
if (!pttoken(tkp))
#ifdef DEBUG
{
VOID printf("EOF\n");
return(0);
}
#else
return(0);
#endif
cp = tkp->tcp;
#ifdef DEBUG
VOID printf("Token: \"%.*s\" ", tkp->tcnt, cp);
#endif
if (tkp->tflg) {
i = tkp->tcnt;
if (*cp == '+' || *cp == '-') {
cp++;
i--;
}
while (0 <= --i) {
j = tkp->tval.tnum*10;
k = j + (*cp++ - '0');
if (j/10 != tkp->tval.tnum || k < j) {
/* arithmetic overflow */
tkp->tval.tnum = 1;
return 0;
}
tkp->tval.tnum = k;
}
} else if (!(tkp->tval.ttmw = ptmatchstr(cp, tkp->tcnt, tmwords)))
{
#ifdef DEBUG
VOID printf("Not found!\n");
#endif
tkp->tval.tnum = 1;
return 0;
}
#ifdef DEBUG
if(tkp->tflg)
VOID printf("Val: %d.\n",tkp->tval.tnum);
else VOID printf("Found: \"%s\", val: %d, type %d\n",
tkp->tval.ttmw->went,tkp->tval.ttmw->wval,tkp->tval.ttmw->wtype);
#endif
return(1);
}
/* Read token from input string into token structure */
static int
pttoken(tkp)
register struct token *tkp;
{
register char const *cp;
register int c;
char const *astr;
tkp->tcp = astr = cp = tkp->tcp + tkp->tcnt;
tkp->tbrkl = tkp->tbrk; /* Set "last break" */
tkp->tcnt = tkp->tbrk = tkp->tflg = 0;
tkp->tval.tnum = 0;
while(c = *cp++)
{ switch(c)
{ case ' ': case '\t': /* Flush all whitespace */
case '\r': case '\n':
case '\v': case '\f':
if (!tkp->tcnt) { /* If no token yet */
tkp->tcp = cp; /* ignore the brk */
continue; /* and go on. */
}
/* fall into */
case '(': case ')': /* Perhaps any non-alphanum */
case '-': case ',': /* shd qualify as break? */
case '+':
case '/': case ':': case '.': /* Break chars */
if(tkp->tcnt == 0) /* If no token yet */
{ tkp->tcp = cp; /* ignore the brk */
tkp->tbrkl = c;
continue; /* and go on. */
}
tkp->tbrk = c;
return(tkp->tcnt);
}
if (!tkp->tcnt++) { /* If first char of token, */
if (isdigit(c)) {
tkp->tflg = 1;
if (astr<cp-2 && (cp[-2]=='-'||cp[-2]=='+')) {
/* timezone is break+sign+digit */
tkp->tcp--;
tkp->tcnt++;
}
}
} else if ((isdigit(c)!=0) != tkp->tflg) { /* else check type */
tkp->tbrk = c;
return --tkp->tcnt; /* Wrong type, back up */
}
}
return(tkp->tcnt); /* When hit EOF */
}
static struct tmwent const *
ptmatchstr(astr,cnt,astruc)
char const *astr;
int cnt;
struct tmwent const *astruc;
{
register char const *cp, *mp;
register int c;
struct tmwent const *lastptr;
int i;
lastptr = 0;
for(;mp = astruc->went; astruc += 1)
{ cp = astr;
for(i = cnt; i > 0; i--)
{
switch (*cp++ - (c = *mp++))
{ case 0: continue; /* Exact match */
case 'A'-'a':
if (ctab[c] == Letter)
continue;
}
break;
}
if(i==0)
if (!*mp) return astruc; /* Exact match */
else if(lastptr) return(0); /* Ambiguous */
else lastptr = astruc; /* 1st ambig */
}
return lastptr;
}