/*
 * PARTIME		parse date/time string into a TM structure
 *
 * Usage:
 *      #include "time.h"             -- expanded tm structure
 *	char *str; struct tm *tp;
 *	partime(str,tp);
 * Returns:
 *	0 if parsing failed
 *	else time values in specified TM structure (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?
 *	Flush AMPM from TM structure and handle locally within PARTIME,
 *		like midnight/noon?
 */

#ifndef lint
static char rcsid[]=
"$Header: /usr/src/local/bin/rcs/src/RCS/partime.c,v 1.4 89/05/01 14:48:46 narten Exp $";
#endif

/* $Log:	partime.c,v $
 * Revision 1.4  89/05/01  14:48:46  narten
 * fixed #ifdef DEBUG construct
 * 
 * Revision 1.3  88/11/08  12:02:15  narten
 * changes from  eggert@sm.unisys.com (Paul Eggert)
 * 
 * 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  84/01/23  14:50:07  kcs
 * Initial revision
 * 
 * Revision 1.1  82/05/06  11:38:26  wft
 * Initial revision
 * 
 */

#include <stdio.h>
#include <ctype.h>
#include "time.h"

#ifndef lint
static char timeid[] = TIMEID;
#endif

struct tmwent {
	char *went;
	long wval;	/* must be big enough to hold pointer or integer */
	char wflgs;
	char wtype;
};
	/* wflgs */
#define TWSPEC 01	/* Word wants special processing */
#define TWTIME 02	/* Word is a time value (absence implies date) */
#define TWDST  04	/* Word is a DST-type timezone */
#define TW1200 010	/* Word is NOON or MIDNIGHT (sigh) */

int pt12hack();
int ptnoise();
struct tmwent 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 */
	{"gst",          0*60, TWTIME, TM_ZON},
	{"gdt",          0*60, TWTIME+TWDST, TM_ZON},     /* ?? */

	{"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 */
	{"yst",          9*60, TWTIME, TM_ZON},   /* Yukon */
	{"hst",          10*60, TWTIME, TM_ZON},  /* Hawaii */
	{"bst",          11*60, TWTIME, TM_ZON},  /* Bering */

	{"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 */
	{"ydt",          9*60, TWTIME+TWDST, TM_ZON},     /* Yukon */
	{"hdt",          10*60, TWTIME+TWDST, TM_ZON},    /* Hawaii */
	{"bdt",          11*60, TWTIME+TWDST, TM_ZON},    /* Bering */

	{"daylight",     1, TWTIME+TWDST, TM_ZON},        /* Local Daylight */
	{"standard",     1, TWTIME, TM_ZON},      /* Local Standard */
	{"std",          1, TWTIME, TM_ZON},      /*   "       "    */

	{"am",           1, TWTIME, TM_AMPM},
	{"pm",           2, TWTIME, TM_AMPM},
	{"noon",         12,TWTIME+TW1200, 0},    /* Special frobs */
	{"midnight",     0, TWTIME+TW1200, 0},
	{"at",           (long)ptnoise, TWSPEC, 0},    /* Noise word */

	{0, 0, 0, 0},             /* Zero entry to terminate searches */
};

#define TMWILD (-2)	/* Value meaning item specified as wild-card */
			/* (May use someday...) */

struct token {
	char *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 *ttmw;/* ptr to a tmwent. */
	} tval;
};

partime(astr, atm)
char *astr;
struct tm *atm;
{	register int *tp;
	register struct tmwent *twp;
	register int i;
	struct token btoken, atoken;
	char *cp, ch;
	int ord, midnoon;
	int (*aproc)();

	tp = (int *)atm;
	zaptime(tp);			 /* Initialize the TM structure */
	midnoon = TMNULL;		/* and our own temp stuff */
	btoken.tcnt = btoken.tbrkl = 0;
	btoken.tcp = astr;

domore:
	if(!ptitoken(btoken.tcp+btoken.tcnt,&btoken))	/* Get a token */
	  {     if(btoken.tval.tnum) return(0);         /* Read error? */
		if(midnoon != TMNULL)			/* EOF, wrap up */
			return(pt12hack(tp, midnoon));
		return(1);				/* Win return! */
	  }
	if(btoken.tflg == 0)		/* Alpha? */
	  {     twp = btoken.tval.ttmw;         /* Yes, get ptr to entry */
		if(twp->wflgs&TWSPEC)		/* Special alpha crock */
		  {     aproc = (int (*) ()) (twp->wval);
			if(!(*aproc)(tp, twp, &btoken))
				return(0);	/* ERR: special word err */
			goto domore;
		  }
		if(twp->wflgs&TW1200)
			if(ptstash(&midnoon,(int)twp->wval))
				return(0);	/* ERR: noon/midnite clash */
			else goto domore;
		if(ptstash(&tp[twp->wtype],(int)twp->wval))
			return(0);		/* ERR: val already set */
		if(twp->wtype == TM_ZON)	/* If was zone, hack DST */
			if(ptstash(&tp[TM_ISDST],(twp->wflgs&TWDST)))
				return(0);	/* ERR: DST conflict */
		goto domore;
	  }

	/* Token is number.  Lots of hairy heuristics. */
	if(btoken.tcnt >= 7)	/* More than 6 digits in string? */
		return(0);	/* ERR: number too big */
	if(btoken.tcnt == 6)	/* 6 digits = HHMMSS.  Needs special crock */
	  {			/* since 6 digits are too big for integer! */
		i = (btoken.tcp[0]-'0')*10	/* Gobble 1st 2 digits */
		   + btoken.tcp[1]-'0';
		btoken.tcnt = 2;		/* re-read last 4 chars */
		goto coltime;
	  }

	i = btoken.tval.tnum;   /* Value now known to be valid; get it. */
	if( btoken.tcnt == 5	/*  5 digits = HMMSS */
	 || btoken.tcnt == 3)	/*  3 digits = HMM   */
	  {	if(btoken.tcnt != 3)
			if(ptstash(&tp[TM_SEC], i%100))
				return(0);	/* ERR: sec conflict */
			else i /= 100;
hhmm4:		if(ptstash(&tp[TM_MIN], i%100))
			return(0);		/* ERR: min conflict */
		i /= 100;
hh2:            if(ptstash(&tp[TM_HOUR], i))
			return(0);		/* ERR: hour conflict */
		goto domore;
	  }

	if(btoken.tcnt == 4)	/* 4 digits = YEAR or HHMM */
	  {	if(tp[TM_YEAR] != TMNULL) goto hhmm4;	/* Already got yr? */
		if(tp[TM_HOUR] != TMNULL) goto year4;	/* Already got hr? */
		if((i%100) > 59) goto year4;		/* MM >= 60? */
		if(btoken.tbrk == ':')			/* HHMM:SS ? */
			if( ptstash(&tp[TM_HOUR],i/100)
			 || ptstash(&tp[TM_MIN], i%100))
				return(0);		/* ERR: hr/min clash */
			else goto coltm2;		/* Go handle SS */
		if(btoken.tbrk != ',' && btoken.tbrk != '/'
		  && ptitoken(btoken.tcp+btoken.tcnt,&atoken)	/* Peek */
		  && atoken.tflg == 0			/* alpha */
		  && (atoken.tval.ttmw->wflgs&TWTIME))  /* HHMM-ZON */
			goto hhmm4;
		if(btoken.tbrkl == '-'		/* DD-Mon-YYYY */
		  || btoken.tbrkl == ','	/* Mon DD, YYYY */
		  || btoken.tbrkl == '/'	/* MM/DD/YYYY */
		  || btoken.tbrkl == '.'	/* DD.MM.YYYY */
		  || btoken.tbrk == '-'		/* YYYY-MM-DD */
			) goto year4;
		goto hhmm4;			/* Give up, assume HHMM. */
	  }

	/* From this point on, assume tcnt == 1 or 2 */
	/* 2 digits = YY, MM, DD, or HH (MM and SS caught at coltime) */
	if(btoken.tbrk == ':')		/* HH:MM[:SS] */
		goto coltime;		/*  must be part of time. */
	if(i > 31) goto yy2;		/* If >= 32, only YY poss. */

	/* 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)
			  {	if(ptitoken(btoken.tcp+btoken.tcnt,&atoken)
				  && atoken.tflg == 0
				  && atoken.tval.ttmw->wtype == TM_MON)
					goto dd2;
				if(ord)goto mm2; else goto dd2; /* "NN-" */
			  }				/* "-NN-" */
			if(tp[TM_DAY] == TMNULL
			&& tp[TM_YEAR] != TMNULL)	/* If "YY-NN-" */
				goto mm2;		/* then always MM */
			if(ord)goto dd2; else goto mm2;
		  }
		if(btoken.tbrkl == ch			/* "-NN" */
		  && tp[ord ? TM_MON : TM_DAY] != TMNULL)
			if(tp[ord ? TM_DAY : TM_MON] == TMNULL)	/* MM/DD */
				if(ord)goto dd2; else goto mm2;
			else goto yy2;			/* "-YY" */
	  }

	/* At this point only YY, DD, and HH are left.
	 * YY is very unlikely since value is <= 32 and there was
	 * no numerical format date.  Make one last try at YY
	 * before dropping through to DD vs HH code.
	 */
	if(btoken.tcnt == 2		/* If 2 digits */
	  && tp[TM_HOUR] != TMNULL	/* and already have hour */
	  && tp[TM_DAY] != TMNULL	/* and day, but  */
	  && tp[TM_YEAR] == TMNULL)	/* no year, then assume */
		goto yy2;		/* that's what we have. */

	/* Now reduced to choice between HH and DD */
	if(tp[TM_HOUR] != TMNULL) goto dd2;	/* Have hour? Assume day. */
	if(tp[TM_DAY] != TMNULL) goto hh2;	/* Have day? Assume hour. */
	if(i > 24) goto dd2;			/* Impossible HH means DD */
	if(!ptitoken(btoken.tcp+btoken.tcnt, &atoken))	/* Read ahead! */
		if(atoken.tval.tnum) return(0); /* ERR: bad token */
		else goto dd2;			/* EOF, assume day. */
	if( atoken.tflg == 0		/* If next token is an alpha */
	 && atoken.tval.ttmw->wflgs&TWTIME)  /* time-spec, assume hour */
		goto hh2;		/* e.g. "3 PM", "11-EDT"  */

dd2:	if(ptstash(&tp[TM_DAY],i))	/* Store day (1 based) */
		return(0);
	goto domore;

mm2:	if(ptstash(&tp[TM_MON], i-1))	/* Store month (make zero based) */
		return(0);
	goto domore;

yy2:	i += 1900;
year4:	if(ptstash(&tp[TM_YEAR],i))	/* Store year (full number) */
		return(0);		/* ERR: year conflict */
	goto domore;

	/* Hack HH:MM[[:]SS] */
coltime:
	if(ptstash(&tp[TM_HOUR],i)) return(0);
	if(!ptitoken(btoken.tcp+btoken.tcnt,&btoken))
		return(!btoken.tval.tnum);
	if(!btoken.tflg) return(0);	/* ERR: HH:<alpha> */
	if(btoken.tcnt == 4)		/* MMSS */
		if(ptstash(&tp[TM_MIN],btoken.tval.tnum/100)
		  || ptstash(&tp[TM_SEC],btoken.tval.tnum%100))
			return(0);
		else goto domore;
	if(btoken.tcnt != 2
	  || ptstash(&tp[TM_MIN],btoken.tval.tnum))
		return(0);		/* ERR: MM bad */
	if(btoken.tbrk != ':') goto domore;	/* Seconds follow? */
coltm2:	if(!ptitoken(btoken.tcp+btoken.tcnt,&btoken))
		return(!btoken.tval.tnum);
	if(!btoken.tflg || btoken.tcnt != 2	/* Verify SS */
	  || ptstash(&tp[TM_SEC], btoken.tval.tnum))
		return(0);		/* ERR: SS bad */
	goto domore;
}

/* Store date/time value, return 0 if successful.
 * Fails if entry already set to a different value.
 */
ptstash(adr,val)
int *adr;
{	register int *a;
	if( *(a=adr) != TMNULL)
		return(*a != val);
	*a = val;
	return(0);
}

/* This subroutine is invoked for NOON or MIDNIGHT when wrapping up
 * just prior to returning from partime.
 */
pt12hack(atp, aval)
int *atp, aval;
{	register int *tp, i, h;
	tp = atp;
	if (((i=tp[TM_MIN]) && i != TMNULL)	/* Ensure mins, secs */
	 || ((i=tp[TM_SEC]) && i != TMNULL))	/* are 0 or unspec'd */
		return(0);			/* ERR: MM:SS not 00:00 */
	i = aval;			/* Get 0 or 12 (midnite or noon) */
	if ((h = tp[TM_HOUR]) == TMNULL	/* If hour unspec'd, win */
	 || h == 12)			/* or if 12:00 (matches either) */
		tp[TM_HOUR] = i;	/* Then set time */
	else if(!(i == 0		/* Nope, but if midnight and */
		&&(h == 0 || h == 24)))	/* time matches, can pass. */
			return(0);	/* ERR: HH conflicts */
	tp[TM_AMPM] = TMNULL;		/* Always reset this value if won */
	return(1);
}

/* Null routine for no-op tokens */

ptnoise() { 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
 */

ptitoken(astr, tkp)
register struct token *tkp;
char *astr;
{
	register char *cp;
	register int i;

	tkp->tval.tnum = 0;
	if(pttoken(astr,tkp) == 0)
#ifdef DEBUG
	    {
		VOID printf("EOF\n");
		return(0);
	    }
#else
		return(0);
#endif	
	cp = tkp->tcp;

#ifdef DEBUG
	i = cp[tkp->tcnt];
	cp[tkp->tcnt] = 0;
	VOID printf("Token: \"%s\" ",cp);
	cp[tkp->tcnt] = i;
#endif

	if(tkp->tflg)
		for(i = tkp->tcnt; i > 0; i--)
			tkp->tval.tnum = (int)tkp->tval.tnum*10 + ((*cp++)-'0');
	else
	  {     i = ptmatchstr(cp, tkp->tcnt, tmwords);
		tkp->tval.tnum = i ? i : -1;         /* Set -1 for error */

#ifdef DEBUG
		if(!i) VOID printf("Not found!\n");
#endif

		if(!i) 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 */
pttoken(astr,tkp)
register struct token *tkp;
char *astr;
{
	register char *cp;
	register int c;

	tkp->tcp = cp = astr;
	tkp->tbrkl = tkp->tbrk;		/* Set "last break" */
	tkp->tcnt = tkp->tbrk = tkp->tflg = 0;

	while(c = *cp++)
	  {	switch(c)
		  {	case ' ': case '\t':	/* Flush all whitespace */
				while((c = *cp++) && isspace(c));
				cp--;		/* Drop thru to handle brk */
			case '(': case ')':	/* Perhaps any non-alphanum */
			case '-': case ',':	/* shd qualify as break? */
			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 == 0)		/* If first char of token, */
			tkp->tflg = isdigit(c);	/*    determine type */
	  	if(( isdigit(c) &&  tkp->tflg)	/* If not first, make sure */
		 ||(!isdigit(c) && !tkp->tflg))	/*    char matches type */
			tkp->tcnt++;		/* Win, add to token. */
		else {
			cp--;			/* Wrong type, back up */
			tkp->tbrk = c;
			return(tkp->tcnt);
		  }
	  }
	return(tkp->tcnt);		/* When hit EOF */
}


ptmatchstr(astr,cnt,astruc)
char *astr;
int cnt;
struct tmwent *astruc;
{	register char *cp, *mp;
	register int c;
	struct tmwent *lastptr;
	struct integ { int word; };   /* For getting at array ptr */
	int i;

	lastptr = 0;
	for(;mp = (char *)((struct integ *)astruc)->word; astruc += 1)
	  {	cp = astr;
		for(i = cnt; i > 0; i--)
		  {	switch((c = *cp++) ^ *mp++)	/* XOR the chars */
			  {	case 0: continue;	/* Exact match */
				case 040: if(isalpha(c))
					continue;
			  }
			break;
		  }
		if(i==0)
			if(*mp == 0) return((unsigned int)astruc);    /* Exact match */
			else if(lastptr) return(0);	/* Ambiguous */
			else lastptr = astruc;		/* 1st ambig */
	  }
	return((unsigned int)lastptr);
}



zaptime(tp)
register int *tp;
/* clears tm structure pointed to by tp */
{	register int i;
	i = (sizeof (struct tm))/(sizeof (int));
	do *tp++ = TMNULL;		/* Set entry to "unspecified" */
	while(--i);			/* Faster than FOR */
}
