diff options
Diffstat (limited to 'gnu/usr.bin/rcs/lib/partime.c')
-rw-r--r-- | gnu/usr.bin/rcs/lib/partime.c | 639 |
1 files changed, 639 insertions, 0 deletions
diff --git a/gnu/usr.bin/rcs/lib/partime.c b/gnu/usr.bin/rcs/lib/partime.c new file mode 100644 index 0000000..4751fc5 --- /dev/null +++ b/gnu/usr.bin/rcs/lib/partime.c @@ -0,0 +1,639 @@ +/* + * 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; +} |