/*
 * |-----------------------------------------------------------|
 * | Copyright (c) 1990 MIPS Computer Systems, Inc.            |
 * | All Rights Reserved                                       |
 * |-----------------------------------------------------------|
 * |          Restricted Rights Legend                         |
 * | Use, duplication, or disclosure by the Government is      |
 * | subject to restrictions as set forth in                   |
 * | subparagraph (c)(1)(ii) of the Rights in Technical        |
 * | Data and Computer Software Clause of DFARS 52.227-7013.   |
 * |         MIPS Computer Systems, Inc.                       |
 * |         928 Arques Avenue                                 |
 * |         Sunnyvale, CA 94086                               |
 * |-----------------------------------------------------------|
 */
#ident	"$Header: getdate.c,v 1.1.2.1 92/06/24 14:08:33 marker Exp $"

/*	Copyright (c) 1988 AT&T	*/
/*	  All Rights Reserved  	*/

/*	THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T	*/
/*	The copyright notice above does not evidence any   	*/
/*	actual or intended publication of such source code.	*/

#ident	"@(#)libgen:getdate.c 1.0 AT&T-UP"

#include <fcntl.h>
#include <ctype.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>

#define CFTIME_DIR	"/lib/cftime/"
#define LEN_CT_ROOT	sizeof CFTIME_DIR - 1
#define GETDATE_DIR	"/usr/lib/getdate/"
#define LENGTH		sizeof GETDATE_DIR - 1
#define DFL_LANG	"usa_english"
#define MAXLANGLENGTH	64


extern char	*getenv(), *malloc(), *memchr();
extern int	errno;
extern int	open();
extern int	close();
extern int	read();
extern int	free();
extern int	fstat();
extern int	stat();
extern int	tolower();

int	getdate_err;       /* This global variable is set
		            * when an error condition is
			    * encountered and is used to
			    * differentiate among different
			    * error conditions. */
/* 
 * The following are the possible contents of the getdate_err
 * variable and the corresponding error conditions.
 ********************************************************************
 * 1	The DATEMASK environment variable is null or undefined.
 * 2	Error on open of the template file.
 * 3    Error on stat of the template file.
 * 4    The template file is not a regular file.
 * 5    Error on read of the template file.
 * 6	Malloc failed.
 * 7	There is no line in the template that matches the input.
 * 8	Invalid input specification.
 ********************************************************************
 */

static void	setcftime();
static struct	tm  *calc_date();
static int lookup();
static int read_tmpl();
static int parse_fmt();
static void getnow();
static int verify();
static void Day();
static void DMY();
static void setcal();
static int days();
static int jan1();
static void year();
static void MON();
static void Month();
static void DOW();
static void adddays();
static void DOY();

static char	language[MAXLANGLENGTH] = DFL_LANG;
static char	*input;
static int	name;
static int	hour;
static int	wrong_input;
static int	meridian;
static int	linenum;
static struct   tm  *ct;
static struct   tm   t;
static int	dpm[] =  {
	31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

enum STR 
	{
	   aJan, aFeb, aMar, aApr, aMay, aJun, aJul, aAug, aSep, aOct, aNov, aDec,
	   Jan, Feb, Mar, Apr, May, Jun, Jul, Aug, Sep, Oct, Nov, Dec,
	   aSun, aMon, aTue, aWed, aThu, aFri, aSat,
	   Sun, Mon, Tue, Wed, Thu, Fri, Sat,
	   field1, field2, field3,
	   AM, PM,
	   LAST
	} ;
/* 
 * Default values.
 */
static char * _cftime[] = {
	"jan","feb","mar","apr","may","jun","jul", "aug", "sep","oct", "nov", "dec",
	"january", "february", "march","april",
	"may","june", "july", "august", "september",
	"october", "november", "december",
	"sun","mon", "tue", "wed","thu", "fri","sat",
	"sunday","monday","tuesday","wednesday", "thursday","friday","saturday",
	"", "", "",
	"am", "pm"
	};



struct	tm
*getdate(expression)
char	*expression;
{

	setcftime();
	input = expression;
	wrong_input = 0;
	linenum = 1;
	if (read_tmpl(expression))
		{
		return(calc_date());
		}
	else	{
		if (wrong_input)
			getdate_err = 8;
		return(NULL);
		}
}
/*
 * Read the language specific file and initialize pointers
 * to month and weekday names and to meridian markers.
 */
static void
setcftime()
{
	register char *p;
	register int j;
	register int k = 0;
	char *mystr[LAST], *file, *q;
	static char *ostr = (char *)0 ;
	char *str;
	struct stat buf;
	static char pathname[128] = CFTIME_DIR;
	int fd;


	if ((file = getenv("LANGUAGE")) == 0 || file[0] == '\0')
	{
		/* Use the default _cftime[] */

		if (pathname[LEN_CT_ROOT] == '\0')
			return;
		file = language;
	}	
	else if (strcmp(file,language) == 0)
		return;

	(void) strcpy(&pathname[LEN_CT_ROOT],file);

	if ( (fd = open(pathname,O_RDONLY)) == -1)
		return;

	if( (fstat(fd,&buf)) != 0)
		return;


	if ( (str = malloc(buf.st_size + 2)) == NULL )
		return;

	if ( (read(fd, str, buf.st_size)) != buf.st_size)
	{
		free(str);
		return;
	}

	/* Set last character of str to '\0' */
	q = &str[buf.st_size];
	q[0] = '\n';
	q[1] = '\0';

	/* p will "walk thru" str */
	p = str;  	

	j = -1;
	while (*p != '\0')
	{ 
		/* "Look for a newline, i.e. end of sub-string
		 * and  change it to a '\0'. If LAST pointers
		 * have been set in mystr, but the newline hasn't been seen
		 * yet, keep going thru the string leaving mystr alone.
		 */
		if (++j < LAST) 
			mystr[j] = p;
		p = memchr(p,'\n',buf.st_size + 2);
		*p++ = '\0';
		while (mystr[j][k] != '\0')
			mystr[j][k++] = tolower(mystr[j][k]);
		k = 0;
	}
	if (j == LAST)
	{
		memcpy(_cftime, mystr,sizeof(mystr)); 
		strncpy(language, file, MAXLANGLENGTH);
		if (ostr != 0)	 /* free the previoulsy allocated local array*/
			free(ostr);
		ostr = str;
	}
	(void) close(fd);
	return;
	
}

/*
 * Parse the user specification. Look for names and numbers.
 */
static
token(action)
int	action;
{
	char	c;
	char	*p;
	char	buf[20];
	int	val;

	name = 0;
	while ( (c = *input++) != '\0' )
		{
		if (isspace(c))
			continue;
		if (action)
			return((int) c);
		else
			break;
		}
	input--;
	if (isdigit (c = *input))
	{
		val = 0;
		while (isdigit(c = *input++))
			val = 10 * val + c - '0';
		input--;
		return(val);
	}
	else if (isalpha(c) || !isascii(c))
		{
		p = buf;
		while(isalpha(c = *input++) || !isascii(c))
			*p++ = tolower(c);
		*p = '\0';
		input--;
		return(lookup(buf));
		} else return(*input=='\0'?-1:*input++);
}

static int
lookup(id)
char *id;
{
	int i;

	for (i=0; i < LAST; i++) 
		if ( strcmp(_cftime[i], id) == 0 )
			{
			name++;
			return(i);
			}
	return(-1);
}
/*
 * Because abbreviated names come before full names make sure
 * that when you look for a full name you consider the correct
 * range. 
 */
static
search(start, end, ret)
int	start;
int	end;
int	*ret;
{
	int	i;
	for (i=start; i<=end; i++)
		if(strcmp(_cftime[*ret], _cftime[i])== 0)
			{
			*ret = i;
			return(i);
			}
		return(-1);
}

/*
 * Read the user specified template file by line
 * until a match occurs.
 * The DATEMSK environment variable points to the template file.
 */
static int
read_tmpl(line)
char	*line;
{
	char	*clean_line();
	FILE  *fp;
	static   char	fname[128] = GETDATE_DIR;
	char	*file;
	char *bp, *start;
	struct stat sb;
	int	ret=1;


	if (((file = getenv("DATEMSK")) == 0) || file[0] == '\0')
		{
		getdate_err = 1;
		return(0);
		}
	if ((start = (char *)malloc(512)) == NULL)
		{
		getdate_err = 6;
		return(0);
		}
	(void) strcpy(&fname[LENGTH], file);
	if ((fp = fopen(fname, "r")) == NULL)
		{
		getdate_err = 2;
		return(0);
		}
	if (stat(fname, &sb) < 0)
		{
		getdate_err = 3;
		return(0);
		}
	if ((sb.st_mode & S_IFMT) != S_IFREG)
		{
		getdate_err = 4;
		return(0);
		}
	for(;;) {
	 	bp = start;
		if (!fgets(bp, 512, fp)) {
			if (!feof(fp)) 
				{
				getdate_err = 5;
				ret = 0;
				break;
				}
			getdate_err = 7;
			ret = 0;
			break;
		}
		if (*(bp+strlen(bp)-1) != '\n')  { /* terminating newline? */
			getdate_err = 5;
			ret = 0;
			break;
			}
		bp = clean_line(bp);
#ifdef DEBUG
printf("line number \"%2d\"---> %s\n", linenum, bp);
#endif
		if (strlen(bp))  /*  anything left?  */
			if (parse_fmt(bp))
				break;
		linenum++;
	input = line;
	}
	free(start);
	fclose(fp);
	return(ret?1:0);
}


char  *
clean_line(s)
char *s;
{
	char  *ns;

	*(s + strlen(s) -1) = (char) 0; /* delete newline */
	if (!strlen(s))
		return(s);
	ns = s + strlen(s) - 1; /* s->start; ns->end */
	while ((ns != s) && (isspace(*ns))) {
		*ns = (char)0;	/* delete terminating spaces */
		--ns;
		}
	while (*ns)             /* delete beginning white spaces */
		if (isspace(*s))
			++s;
		else
			break;
	return(s);
}


/*
 * Match lines in the template with input specification.
 */
static int
parse_fmt(bp)
	char	*bp;
{
	char	*fmt;
	int	ret;
	char c;
	getnow();
	fmt = bp;
	while ((c = *fmt++) != '\0') {
		if ( c == '%' ) {
			ret = token(0);
			switch (*fmt++) {
			case 'a':
				if (t.tm_wday)
					{
					wrong_input++;
					return(0);
					}
				if (!name || ret<0)
					return(0);
				if ( !(ret >= aSun && ret <= aSat) )
					{
					if (search(aSun, aSat, &ret)<0)
						return(0);
					}
				t.tm_wday = ret - aSun + 1;
				continue;
			case 'd':
			case 'e':
				if (t.tm_mday)
					{
					wrong_input++;
					return(0);
					}
				if ( (ret >= 1 && ret <= 31)  && !name  )
					t.tm_mday = ret;
				else
					return(0);
				continue;
			case 'A':
				if (t.tm_wday)
					{
					wrong_input++;
					return(0);
					}
				if ( !name || ret<0 )
					return(0);
				if ( !(ret >= Sun && ret <= Sat) )
					{
					if (search(Sun, Sat, &ret)<0 && search(aSun, aSat, &ret)<0)
						return(0);
					}
				t.tm_wday = ret - Sun + 1;
				continue;
			case 'h':
			case 'b':
				if (t.tm_mon)
					{
					wrong_input++;
					return(0);
					}
				if ( (ret >= aJan && ret <= aDec) && name )
					t.tm_mon = ret - aJan + 1;  
				else
					return(0);
				continue;
			case 'B':
				if (t.tm_mon)
					{
					wrong_input++;
					return(0);
					}
				if (!name || ret<0)
					return(0);
				if ( !(ret >= Jan && ret <= Dec) )
					{
					if (search(Jan, Dec, &ret)<0)
						return(0);
					}
				t.tm_mon = ret - Jan + 1;
				continue;
			case 'Y':
				if (t.tm_year)
					{
					wrong_input++;
					return(0);
					}
	/* The last time UNIX can handle is 1/18/2038;
	   for simplicity stop at 2028 */
				if (ret < 1970 || ret > 2037)
					return(0);
				else
					t.tm_year = ret - 1900;
				continue;
			case 'y':
				if (t.tm_year)
					{
					wrong_input;
					return(0);
					}
				if ((ct->tm_year < 100 && ret >= 70 && ret < 100) || (ct->tm_year >=100 && ret < 38))
					t.tm_year=ct->tm_year>=100?100+ret:ret;
				else
					return(0);
				continue;
			case 'm':
				if (t.tm_mon)
					{
					wrong_input++;
					return(0);
					}
				if (( ret > 0 && ret <= 12 ) && !name)
					t.tm_mon = ret;
				else
					return(0);
				continue;
			case 'I':
				if (t.tm_hour)
					{
					wrong_input++;
					return(0);
				}
				if (ret < 1 || ret > 12)
					return(0);
				else	
					t.tm_hour = ret;
				continue;
			case 'p':
				if (meridian)
					{
					wrong_input++;
					return(0);
				}
				if (ret == AM)
					meridian = AM;
				else  if (ret == PM)
						meridian = PM;
				      else	return(0);
				continue;
			case 'H':
				if (hour)
					{
					wrong_input++;
					return(0);
				}
				if ( ret >= 0 && ret <= 23 )
					hour = ret + 1;
				else
					return(0);
				continue;
			case 'M':
				if (t.tm_min)
					{
					wrong_input++;
					return(0);
				}
				if ( ret >= 0 && ret <= 59 )
					t.tm_min = ret + 1;
				else
					return(0);
				continue;
			case 'S':
				if (t.tm_sec)
					{
					wrong_input++;
					return(0);
					}
				if ( ret >= 0 && ret <= 59 )
					t.tm_sec = ret + 1;
				else
					return(0);
				continue;
			default:
				return(0);			
			}
		}
		else
			{
			fmt--;
			while (((c = *fmt++) != '%') && c!='\0')
				{
				if (isspace(c))
					continue;
				if (c != token(1))
					return(0);
				else
					break;
				}
			if (c=='%' || c=='\0')
				fmt--;
			}
	}
	if (  token(1) != -1 )
		return(0);
	if (verify())
		return(1);
	else	return(0);
}

static void
getnow()	/*  get current date */
{
	long now, time();

	now = time(NULL);
	ct = localtime(&now);
	ct->tm_yday += 1;
	t.tm_year = t.tm_mon = t.tm_mday = t.tm_wday = t.tm_hour = t.tm_min = t.tm_sec = hour = meridian = 0;
}

/*
 * Check validity of input
 */
static int
verify()
{
	int min = 0;
	int sec = 0;
	int hr = 0;

	if (t.tm_year)
		year(t.tm_year);
	if (t.tm_mon)
		MON(t.tm_mon - 1);
	if (t.tm_mday)
		Day(t.tm_mday);
	if (t.tm_wday)
		DOW(t.tm_wday - 1);
	if (  ((t.tm_mday)&&((t.tm_mday != ct->tm_mday)||(t.tm_mday > dpm[ct->tm_mon]))) 
		||
	      ((t.tm_wday)&&((t.tm_wday-1)!=ct->tm_wday)) 
		||
	      ((t.tm_hour&&hour)||(t.tm_hour&&!meridian)||
	      (!t.tm_hour&&meridian)||(hour&&meridian))  )
		{
		wrong_input++;
		return(0);
		}
	if (t.tm_hour)
		{
		switch (meridian) 
			{
			case PM:
				t.tm_hour %= 12;
				t.tm_hour += 12;
				break;
			case AM:
				t.tm_hour %= 12;
				if ( ! t.tm_hour )
					hr++;
				break;
			default:
				return(0);
			}
		}
	if (hour)
		t.tm_hour = hour - 1;
	if (t.tm_min)
		{
		min++;
		t.tm_min -= 1;
		}
	if (t.tm_sec)
		{
		sec++;
		t.tm_sec -= 1;
		}
	if ( (! t.tm_year && ! t.tm_mon && ! t.tm_mday && ! t.tm_wday) && ( (t.tm_hour < ct->tm_hour) || ((t.tm_hour == ct->tm_hour) && (t.tm_min < ct->tm_min)) || ((t.tm_hour == ct->tm_hour) && (t.tm_min == ct->tm_min) && (t.tm_sec < ct->tm_sec)) ) )
		t.tm_hour += 24;
	if (t.tm_hour || hour || hr || min || sec)
		{
		ct->tm_hour = t.tm_hour;
		ct->tm_min = t.tm_min; 
		ct->tm_sec = t.tm_sec;
		}
	return(1);
}


static void
Day(day)
int day;
{
	if (day < ct->tm_mday)
		if( ++ct->tm_mon == 12 )  ++ct->tm_year;
	ct->tm_mday = day;
	DMY();
}


static void
DMY()
{
	int i, doy;
	setcal(ct->tm_year);
	for( i=doy=0; i<ct->tm_mon; i++ )   doy += dpm[i];
	ct->tm_yday = doy + ct->tm_mday;
	ct->tm_wday = (jan1(ct->tm_year) + ct->tm_yday - 1) % 7;
}

static void
setcal(year)
int	year;
{
	switch( days(year) ) {
	case	365: dpm[1]=28; break;
	case    366: dpm[1]=29; break;
	}
}

static int
days(y)
int	y;
{
	y += 1900;
	return( y%4==0 && y%100!=0 || y%400==0 ? 366 : 365 );
}


/*
 *	return day of the week
 *	of jan 1 of given year
 */

static int
jan1(yr)
{
	register y, d;

/*
 *	normal gregorian calendar
 *	one extra day per four years
 */

	y = yr + 1900;
	d = 4+y+(y+3)/4;

/*
 *	julian calendar
 *	regular gregorian
 *	less three days per 400
 */

	if(y > 1800) {
		d -= (y-1701)/100;
		d += (y-1601)/400;
	}

/*
 *	great calendar changeover instant
 */

	if(y > 1752)
		d += 3;

	return(d%7);
}

static void
year(yr)
int	yr;
{
	ct->tm_mon = 0;
	ct->tm_mday = 1;
	ct->tm_year = yr;
	DMY();
}

static void
MON(month)
int month;
{
	ct->tm_mday = 1;
	Month(month);
}

static void
Month(month)
int month;
{
	if( month < ct->tm_mon )  ct->tm_year++;
	ct->tm_mon = month;
	DMY();
}

static void
DOW(dow)
int	dow;
{
	adddays((dow+7-ct->tm_wday)%7);
}

static void
adddays(n)
int	n;
{
	DOY(ct->tm_yday+n);
}

static void
DOY(doy)
int	doy;
{
	int i;

	if ( doy>days(ct->tm_year) ) {
		doy -= days(ct->tm_year);
		ct->tm_year++;
	}
	ct->tm_yday = doy;
	setcal(ct->tm_year);
	for (i=0; doy>dpm[i]; i++)  
		doy -= dpm[i];
	ct->tm_mday = doy;
	ct->tm_mon = i;
	ct->tm_wday = (jan1(ct->tm_year)+ct->tm_yday-1) % 7;
}

/*
 * return time from time structure
 */
static struct  tm
*calc_date()
{
	register i;
	long	tv;

	tv = 0;
	for (i = 1970; i < ct->tm_year+1900; i++)
		tv += days(i-1900);
	for (i = 0; i < ct->tm_mon; ++i)
		tv += dpm[i];
	tv += ct->tm_mday - 1;
	tv = 24 * tv + ct->tm_hour;
	tv = 60 * tv + ct->tm_min;
	tv = 60 * tv + ct->tm_sec;
	tv += timezone;
	if (localtime(&tv)->tm_isdst)
		tv = tv - (timezone - altzone);
	return(localtime(&tv));
}

