%token ID MONTH DAY MERIDIAN NUMBER UNIT MUNIT SUNIT ZONE DAYZONE AGO DST
%{
	/* 	Originally from: Steven M. Bellovin (unc!smb)	*/ 
	/*	Dept. of Computer Science			*/
	/*	University of North Carolina at Chapel Hill	*/
	/*	@(#)getdate.y	2.17	11/30/87			*/

/* MS-DOS port (c) 1990 by Thorsten Ohl, ohl@gnu.ai.mit.edu
   This port is also distributed under the terms of the
   GNU General Public License as published by the
   Free Software Foundation.

   Please note that this file is not identical to the
   original GNU release, you should have received this
   code as patch to the official release.  */

#ifdef MSDOS
static char RCS_Id[] =
  "$Header: e:/gnu/fileutil/RCS/getdate.y 1.4.0.2 90/09/19 12:27:47 tho Exp $";
#endif /* MSDOS */

/* #include "defs.h" JF not used any more */
#include <sys/types.h>
#ifdef USG
struct timeb
{
	time_t	time;
	unsigned short millitm;
	short	timezone;
	short	dstflag;
};
static void ftime ();
#else
#include <sys/timeb.h>
#endif
#include <ctype.h>

#if defined(BSD4_2) || defined (BSD4_1C)
#include <sys/time.h>
#else /* sane */
#include <time.h>
#endif /* sane */

#if defined (STDC_HEADERS) || defined (USG)
#include <string.h>
#endif

#ifdef __GNUC__
#define alloca __builtin_alloca
#else
#ifdef sparc
#include <alloca.h>
#endif
#endif

#ifdef MSDOS
#include <malloc.h>
int getdate_yyparse (void);
long getdate (char *p, struct timeb *now);
void ftime (struct timeb *timeb);
static long dateconv (int mm, int dd, int yy, int h, int m, int s, int mer,
		      int zone, int dayflag);
static long dayconv (int ord, int day, long now);
static long timeconv (int hh, int mm, int ss, int mer);
static long monthadd (long sdate, long relmonth);
static long daylcorr (long future, long now);
static int lookup (char *id);
static int yyerror (char *s);
static int yylex (void);
#endif /* MSDOS */

#ifndef NULL
#define	NULL	0
#endif
#define daysec (24L*60L*60L)
	static int timeflag, zoneflag, dateflag, dayflag, relflag;
	static time_t relsec, relmonth;
	static int hh, mm, ss, merid, day_light;
	static int dayord, dayreq;
	static int month, day, year;
	static int ourzone;
#define AM 1
#define PM 2
#define DAYLIGHT 1
#define STANDARD 2
#define MAYBE    3

static time_t timeconv();
static time_t daylcorr();
static lookup();

static int yylex ();
#define yyparse getdate_yyparse
static int yyerror ();

%}

%%
timedate: 		/* empty */
	| timedate item
	;

item:	tspec
		{timeflag++;}
	| zone
		{zoneflag++;}
	| dtspec
		{dateflag++;}
	| dyspec
		{dayflag++;}
	| rspec
		{relflag++;}
	| nspec;

nspec:	NUMBER
		{if (timeflag && dateflag && !relflag) year = $1;
		else if ($1 > 10000) {
			dateflag++;
			day= $1%100;
			month= ($1/100)%100;
			year = month/10000;
		} else {timeflag++;hh = $1/100;mm = $1%100;ss = 0;merid = 24;}};

tspec:	NUMBER MERIDIAN
		{hh = $1; mm = 0; ss = 0; merid = $2;}
	| NUMBER ':' NUMBER
		{hh = $1; mm = $3; merid = 24;}
	| NUMBER ':' NUMBER MERIDIAN
		{hh = $1; mm = $3; merid = $4;}
	| NUMBER ':' NUMBER NUMBER
		{hh = $1; mm = $3; merid = 24;
		day_light = STANDARD; ourzone = -($4%100 + 60*($4/100));}
	| NUMBER ':' NUMBER ':' NUMBER
		{hh = $1; mm = $3; ss = $5; merid = 24;}
	| NUMBER ':' NUMBER ':' NUMBER MERIDIAN
		{hh = $1; mm = $3; ss = $5; merid = $6;}
	| NUMBER ':' NUMBER ':' NUMBER NUMBER
		{hh = $1; mm = $3; ss = $5; merid = 24;
		day_light = STANDARD; ourzone = -($6%100 + 60*($6/100));};

zone:	ZONE dst
		{ourzone = $1; day_light = $2;}
	| DAYZONE
		{ourzone = $1; day_light = DAYLIGHT;};

dst:	/* empty */
		{ $$ = STANDARD; }
	| DST
		{ $$ = DAYLIGHT; };

dyspec:	DAY
		{dayord = 1; dayreq = $1;}
	| DAY ','
		{dayord = 1; dayreq = $1;}
	| NUMBER DAY
		{dayord = $1; dayreq = $2;};

dtspec:	NUMBER '/' NUMBER
		{month = $1; day = $3;}
	| NUMBER '/' NUMBER '/' NUMBER
		{month = $1; day = $3; year = $5;}
	| MONTH NUMBER
		{month = $1; day = $2;}
	| MONTH NUMBER ',' NUMBER
		{month = $1; day = $2; year = $4;}
	| NUMBER MONTH
		{month = $2; day = $1;}
	| NUMBER MONTH NUMBER
		{month = $2; day = $1; year = $3;};


rspec:	NUMBER UNIT
		{relsec +=  60L * $1 * $2;}
	| NUMBER MUNIT
		{relmonth += $1 * $2;}
	| NUMBER SUNIT
		{relsec += $1;}
	| UNIT
		{relsec +=  60L * $1;}
	| MUNIT
		{relmonth += $1;}
	| SUNIT
		{relsec++;}
	| rspec AGO
		{relsec = -relsec; relmonth = -relmonth;};
%%

static int mdays[12] =
	{31, 0, 31,  30, 31, 30,  31, 31, 30,  31, 30, 31};
#define epoch 1970

extern struct tm *localtime();

static time_t
dateconv(mm, dd, yy, h, m, s, mer, zone, dayflag)
int mm, dd, yy, h, m, s, mer, zone, dayflag;
{
	time_t tod, jdate;
	register int i;

	if (yy < 0) yy = -yy;
	if (yy < 100) yy += 1900;
	mdays[1] = 28 + (yy%4 == 0 && (yy%100 != 0 || yy%400 == 0));
	if (yy < epoch || yy > 1999 || mm < 1 || mm > 12 ||
		dd < 1 || dd > mdays[--mm]) return (-1);
	jdate = dd-1;
        for (i=0; i<mm; i++) jdate += mdays[i];
	for (i = epoch; i < yy; i++) jdate += 365 + (i%4 == 0);
	jdate *= daysec;
	jdate += zone * 60L;
	if ((tod = timeconv(h, m, s, mer)) < 0) return (-1);
	jdate += tod;
	if (dayflag==DAYLIGHT || (dayflag==MAYBE&&localtime(&jdate)->tm_isdst))
		jdate += -1*60*60;
	return (jdate);
}

static time_t
dayconv(ord, day, now)
int ord, day; time_t now;
{
	register struct tm *loctime;
	time_t tod;

	tod = now;
	loctime = localtime(&tod);
	tod += daysec * ((day - loctime->tm_wday + 7) % 7);
	tod += 7*daysec*(ord<=0?ord:ord-1);
	return daylcorr(tod, now);
}

static time_t
timeconv(hh, mm, ss, mer)
register int hh, mm, ss, mer;
{
	if (mm < 0 || mm > 59 || ss < 0 || ss > 59) return (-1);
	switch (mer) {
		case AM: if (hh < 1 || hh > 12) return(-1);
			 return (60L * ((hh%12)*60L + mm)+ss);
		case PM: if (hh < 1 || hh > 12) return(-1);
			 return (60L * ((hh%12 +12)*60L + mm)+ss);
		case 24: if (hh < 0 || hh > 23) return (-1);
			 return (60L * (hh*60L + mm)+ss);
		default: return (-1);
	}
}

static time_t
monthadd(sdate, relmonth)
time_t sdate, relmonth;
{
	struct tm *ltime;
	time_t dateconv();
	int mm, yy;

	if (relmonth == 0) return 0;
	ltime = localtime(&sdate);
	mm = 12*ltime->tm_year + ltime->tm_mon + relmonth;
	yy = mm/12;
	mm = mm%12 + 1;
	return daylcorr(dateconv(mm, ltime->tm_mday, yy, ltime->tm_hour,
		ltime->tm_min, ltime->tm_sec, 24, ourzone, MAYBE), sdate);
}

static time_t
daylcorr(future, now)
time_t future, now;
{
	int fdayl, nowdayl;

	nowdayl = (localtime(&now)->tm_hour+1) % 24;
	fdayl = (localtime(&future)->tm_hour+1) % 24;
	return (future-now) + 60L*60L*(nowdayl-fdayl);
}

static char *lptr;

static int
yylex()
{
	extern int yylval;
	int sign;
	register char c;
	register char *p;
	char idbuf[20];
	int pcnt;

	for (;;) {
		while (isspace(*lptr))
			lptr++;

		if (isdigit(c = *lptr) || c == '-' || c == '+') {
			if (c== '-' || c == '+') {
				if (c=='-') sign = -1;
				else sign = 1;
				if (!isdigit(*++lptr)) {
					/* yylval = sign; return (NUMBER); */
					return yylex();	/* skip the '-' sign */
				}
			} else sign = 1;
			yylval = 0;
			while (isdigit(c = *lptr++))
				yylval = 10*yylval + c - '0';
			yylval *= sign;
			lptr--;
			return (NUMBER);

		} else if (isalpha(c)) {
			p = idbuf;
			while (isalpha(c = *lptr++) || c=='.')
				if (p < &idbuf[sizeof(idbuf)-1]) *p++ = c;
			*p = '\0';
			lptr--;
			return (lookup(idbuf));
		}

		else if (c == '(') {
			pcnt = 0;
			do {
				c = *lptr++;
				if (c == '\0') return(c);
				else if (c == '(') pcnt++;
				else if (c == ')') pcnt--;
			} while (pcnt > 0);
		}

		else return (*lptr++);
	}
}

struct table {
	char *name;
	int type, value;
};

static struct table mdtab[] = {
	{"january", MONTH, 1},
	{"february", MONTH, 2},
	{"march", MONTH, 3},
	{"april", MONTH, 4},
	{"may", MONTH, 5},
	{"june", MONTH, 6},
	{"july", MONTH, 7},
	{"august", MONTH, 8},
	{"september", MONTH, 9},
	{"sept", MONTH, 9},
	{"october", MONTH, 10},
	{"november", MONTH, 11},
	{"december", MONTH, 12},

	{"sunday", DAY, 0},
	{"monday", DAY, 1},
	{"tuesday", DAY, 2},
	{"tues", DAY, 2},
	{"wednesday", DAY, 3},
	{"wednes", DAY, 3},
	{"thursday", DAY, 4},
	{"thur", DAY, 4},
	{"thurs", DAY, 4},
	{"friday", DAY, 5},
	{"saturday", DAY, 6},
	{0, 0, 0}
};

#define HRS *60
#define HALFHR 30

static struct table mztab[] = {
	{"a.m.", MERIDIAN, AM},
	{"am", MERIDIAN, AM},
	{"p.m.", MERIDIAN, PM},
	{"pm", MERIDIAN, PM},
	{"nst", ZONE, 3 HRS + HALFHR},		/* Newfoundland */
	{"n.s.t.", ZONE, 3 HRS + HALFHR},
	{"ast", ZONE, 4 HRS},		/* Atlantic */
	{"a.s.t.", ZONE, 4 HRS},
	{"adt", DAYZONE, 4 HRS},
	{"a.d.t.", DAYZONE, 4 HRS},
	{"est", ZONE, 5 HRS},		/* Eastern */
	{"e.s.t.", ZONE, 5 HRS},
	{"edt", DAYZONE, 5 HRS},
	{"e.d.t.", DAYZONE, 5 HRS},
	{"cst", ZONE, 6 HRS},		/* Central */
	{"c.s.t.", ZONE, 6 HRS},
	{"cdt", DAYZONE, 6 HRS},
	{"c.d.t.", DAYZONE, 6 HRS},
	{"mst", ZONE, 7 HRS},		/* Mountain */
	{"m.s.t.", ZONE, 7 HRS},
	{"mdt", DAYZONE, 7 HRS},
	{"m.d.t.", DAYZONE, 7 HRS},
	{"pst", ZONE, 8 HRS},		/* Pacific */
	{"p.s.t.", ZONE, 8 HRS},
	{"pdt", DAYZONE, 8 HRS},
	{"p.d.t.", DAYZONE, 8 HRS},
	{"yst", ZONE, 9 HRS},		/* Yukon */
	{"y.s.t.", ZONE, 9 HRS},
	{"ydt", DAYZONE, 9 HRS},
	{"y.d.t.", DAYZONE, 9 HRS},
	{"hst", ZONE, 10 HRS},		/* Hawaii */
	{"h.s.t.", ZONE, 10 HRS},
	{"hdt", DAYZONE, 10 HRS},
	{"h.d.t.", DAYZONE, 10 HRS},

	{"gmt", ZONE, 0 HRS},
	{"g.m.t.", ZONE, 0 HRS},
	{"bst", DAYZONE, 0 HRS},		/* British Summer Time */
	{"b.s.t.", DAYZONE, 0 HRS},
	{"eet", ZONE, 0 HRS},		/* European Eastern Time */
	{"e.e.t.", ZONE, 0 HRS},
	{"eest", DAYZONE, 0 HRS},	/* European Eastern Summer Time */
	{"e.e.s.t.", DAYZONE, 0 HRS},
	{"met", ZONE, -1 HRS},		/* Middle European Time */
	{"m.e.t.", ZONE, -1 HRS},
	{"mest", DAYZONE, -1 HRS},	/* Middle European Summer Time */
	{"m.e.s.t.", DAYZONE, -1 HRS},
	{"wet", ZONE, -2 HRS },		/* Western European Time */
	{"w.e.t.", ZONE, -2 HRS },
	{"west", DAYZONE, -2 HRS},	/* Western European Summer Time */
	{"w.e.s.t.", DAYZONE, -2 HRS},

	{"jst", ZONE, -9 HRS},		/* Japan Standard Time */
	{"j.s.t.", ZONE, -9 HRS},	/* Japan Standard Time */
					/* No daylight savings time */

	{"aest", ZONE, -10 HRS},	/* Australian Eastern Time */
	{"a.e.s.t.", ZONE, -10 HRS},
	{"aesst", DAYZONE, -10 HRS},	/* Australian Eastern Summer Time */
	{"a.e.s.s.t.", DAYZONE, -10 HRS},
	{"acst", ZONE, -(9 HRS + HALFHR)},	/* Australian Central Time */
	{"a.c.s.t.", ZONE, -(9 HRS + HALFHR)},
	{"acsst", DAYZONE, -(9 HRS + HALFHR)},	/* Australian Central Summer */
	{"a.c.s.s.t.", DAYZONE, -(9 HRS + HALFHR)},
	{"awst", ZONE, -8 HRS},		/* Australian Western Time */
	{"a.w.s.t.", ZONE, -8 HRS},	/* (no daylight time there, I'm told */
	{"dst", DST, 0},		/* Explicit daylight time */
	{0, 0, 0}};

static struct table unittb[] = {
	{"year", MUNIT, 12},
	{"month", MUNIT, 1},
	{"fortnight", UNIT, 14*24*60},
	{"week", UNIT, 7*24*60},
	{"day", UNIT, 1*24*60},
	{"hour", UNIT, 60},
	{"minute", UNIT, 1},
	{"min", UNIT, 1},
	{"second", SUNIT, 1},
	{"sec", SUNIT, 1},
	{0, 0, 0}};

static struct table othertb[] = {
	{"tomorrow", UNIT, 1*24*60},
	{"yesterday", UNIT, -1*24*60},
	{"today", UNIT, 0},
	{"now", UNIT, 0},
	{"last", NUMBER, -1},
	{"this", UNIT, 0},
	{"next", NUMBER, 2},
	{"first", NUMBER, 1},
	/* {"second", NUMBER, 2}, */
	{"third", NUMBER, 3},
	{"fourth", NUMBER, 4},
	{"fifth", NUMBER, 5},
	{"sixth", NUMBER, 6},
	{"seventh", NUMBER, 7},
	{"eigth", NUMBER, 8},
	{"ninth", NUMBER, 9},
	{"tenth", NUMBER, 10},
	{"eleventh", NUMBER, 11},
	{"twelfth", NUMBER, 12},
	{"ago", AGO, 1},
	{0, 0, 0}};

static struct table milzone[] = {
	{"a", ZONE, 1 HRS},
	{"b", ZONE, 2 HRS},
	{"c", ZONE, 3 HRS},
	{"d", ZONE, 4 HRS},
	{"e", ZONE, 5 HRS},
	{"f", ZONE, 6 HRS},
	{"g", ZONE, 7 HRS},
	{"h", ZONE, 8 HRS},
	{"i", ZONE, 9 HRS},
	{"k", ZONE, 10 HRS},
	{"l", ZONE, 11 HRS},
	{"m", ZONE, 12 HRS},
	{"n", ZONE, -1 HRS},
	{"o", ZONE, -2 HRS},
	{"p", ZONE, -3 HRS},
	{"q", ZONE, -4 HRS},
	{"r", ZONE, -5 HRS},
	{"s", ZONE, -6 HRS},
	{"t", ZONE, -7 HRS},
	{"u", ZONE, -8 HRS},
	{"v", ZONE, -9 HRS},
	{"w", ZONE, -10 HRS},
	{"x", ZONE, -11 HRS},
	{"y", ZONE, -12 HRS},
	{"z", ZONE, 0 HRS},
	{0, 0, 0}};

static int
lookup(id)
char *id;
{
#define gotit (yylval=i->value,  i->type)

	char idvar[128];
	register char *j, *k;
	register struct table *i;
	int abbrev;

	(void) strcpy(idvar, id);
	j = idvar;
	k = id - 1;
	while (*++k)
		*j++ = isupper(*k) ? tolower(*k) : *k;
	*j = '\0';

	if (strlen(idvar) == 3)
		abbrev = 1;
	else
		if (strlen(idvar) == 4 && idvar[3] == '.' && idvar[1] != '.') {
			abbrev = 1;
			idvar[3] = '\0';
		}
	else
		abbrev = 0;

	for (i = mdtab; i->name; i++) {
		k = idvar;
		for (j = i->name; *j++ == *k++;) {
			if (abbrev && j == i->name+3)
				return gotit;
			if (j[-1] == 0)
				return gotit;
		}
	}

	for (i=mztab; i->name; i++)
		if (strcmp(i->name, idvar) == 0)
			return gotit;

	for (i=unittb; i->name; i++)
		if (strcmp(i->name, idvar) == 0)
			return gotit;

	if (idvar[strlen(idvar)-1] == 's')
		idvar[strlen(idvar)-1] = '\0';

	for (i=unittb; i->name; i++)
		if (strcmp(i->name, idvar) == 0)
			return gotit;

	for (i = othertb; i->name; i++)
		if (strcmp(i->name, idvar) == 0)
			return gotit;

	if (strlen(idvar) == 1 && isalpha(*idvar)) {
		for (i = milzone; i->name; i++)
			if (strcmp(i->name, idvar) == 0)
				return gotit;
	}

	return ID;
}

time_t
getdate(p, now)
char *p;
struct timeb *now;
{
#define mcheck(f)	if (f>1) err++
	time_t monthadd();
	int err;
	struct tm *lt;
	struct timeb ftz;

	time_t sdate, tod;

	lptr = p;
	if (now == ((struct timeb *) NULL)) {
		now = &ftz;
		ftime(&ftz);
	}
	lt = localtime(&now->time);
	year = lt->tm_year;
	month = lt->tm_mon+1;
	day = lt->tm_mday;
	relsec = 0; relmonth = 0;
	timeflag=zoneflag=dateflag=dayflag=relflag=0;
	ourzone = now->timezone;
	day_light = MAYBE;
	hh = mm = ss = 0;
	merid = 24;

	if (err = yyparse()) return (-1);

	mcheck(timeflag);
	mcheck(zoneflag);
	mcheck(dateflag);
	mcheck(dayflag);

	if (err) return (-1);
	if (dateflag || timeflag || dayflag) {
		sdate = dateconv(month,day,year,hh,mm,ss,merid,ourzone,day_light);
		if (sdate < 0) return -1;
	}
	else {
		sdate = now->time;
		if (relflag == 0)
			sdate -= (lt->tm_sec + lt->tm_min*60 +
				lt->tm_hour*(60L*60L));
	}

	sdate += relsec;
	sdate += monthadd(sdate, relmonth);

	if (dayflag && !dateflag) {
		tod = dayconv(dayord, dayreq, sdate);
		sdate += tod;
	}

	/*
	** Have to do *something* with a legitimate -1 so it's distinguishable
	** from the error return value.  (Alternately could set errno on error.)
	*/
	return (sdate == -1) ? 0 : sdate;
}

static int
yyerror(s)
char *s;
{
  return 0;
}


#ifdef USG
static void
ftime(timeb)
struct timeb *timeb;
{
	extern long timezone;
#if 0			/* Not all non-BSD libraries have `daylight'. */
	extern int daylight;
#endif
	long t = 0;

	localtime(&t);	/* Dummy to init timzeone */
	time(&(timeb->time));
	timeb->millitm=0;
	timeb->timezone=timezone/60;	/* Timezone is in seconds! */
#if 0
	timeb->dstflag=daylight;
#else
	timeb->dstflag=0;
#endif
}
#endif
