
/* General routines to provide date support for the DateStamp
 * date format.
 *
 * Author:		Mark R. Rinfret	(mark@unisec.usi.com)
 * Date:		07/18/87
 *
 * This source is released to the public domain by the author, without
 * restrictions.  However, it is requested that you give credit where
 * credit is due and share any bug fixes or enhancements with him.
 *
 * History:		(most recent change first)
 *
 * 08/20/87 -MRR- Gack!  I wrote this?  In the interests of orthogonality,
 *                I have replaced SetDateStamp with PackDS, the inverse
 *                of UnpackDS.  I have also renamed the package MRDates
 *                to provide a unique name, hopefully relieving conflicts
 *                with other packages providing date/time functions.  The
 *				  UnpackedDS type is now defined in MRDates.h and can
 *                be included by modules which require PackDS and UnpackDS.
 */

/* The following #define MUST be set to inhibit certain declarations in
 * the include file MRDates.h:
 */

#define MRDATES

#include <libraries/dos.h>
#include <exec/memory.h>
#include <ctype.h>
#include <functions.h>
#include "MRDates.h"

/* #define DEBUG */


char *daynames[] = {
	"Sunday", "Monday", "Tuesday", "Wednesday", 
	"Thursday", "Friday", "Saturday"
	};

USHORT monthdays[12] =  {0,31,59,90,120,151,181,212,243,273,304,334};

char *monthnames[12] = {
	"January", "February", "March", "April", "May", "June",
	"July", "August", "September", "October", "November", "December"
	};

/* Compare two DateStamp values.
 * Called with:
 *		d1,d2:		pointers to DateStamp structs
 * 
 * Returns:
 *		< 0 => d1 < d2
 *		  0 => d1 == d2
 *		> 0 => d1 > d2
 *
 * Note:
 *		This routine makes an assumption about the DateStamp structure,
 *		specifically that it can be viewed as an array of 3 long integers
 *		in days, minutes and ticks order.
 */

int 
CompareDS(d1, d2)
	long *d1, *d2;
{
	USHORT i;
	long compare;

	for (i = 0; i < 3; ++i) {
		if (compare = (d1[i] - d2[i])) {
			if (compare < 0) return -1;
			return 1;
		}
	}
	return 0;						/* dates match */
}

/* Convert a DateStamp to a formatted string.
 * Called with:
 *		fmt:	format string
 *			The format of the format string is very similar to that
 *			for printf, with the exception that the following letters
 *			have special significance:
 *				y => year minus 1900
 *				Y => full year value
 *				m => month value as integer
 *				M => month name
 *				d => day of month (1..31)
 *				D => day name ("Monday".."Sunday")
 *				h => hour in twenty-four hour notation
 *				H => hour in twelve hour notation
 *				i => 12 hour indicator for H notation (AM or PM)
 *				I => same as i
 *				n => minutes	(sorry...conflict with m = months)
 *				N => same as n
 *				s => seconds
 *				S => same as s
 *
 *			All other characters are passed through as part of the normal
 *			formatting process.  The following are some examples with
 *			Saturday, July 18, 1987, 13:53 as an input date:
 *
 *				"%y/%m/%d"			=> 87/7/18
 *				"%02m/%02d/%2y"		=> 07/18/87
 *				"%D, %M %d, %Y"		=> Saturday, July 18, 1987
 *				"%02H:%02m i"		=> 01:53 PM
 *				"Time now: %h%m"	=> Time now: 13:53
 *
 *		str:	string to write date on
 *		d:		pointer to DateStamp structure
 *		
 */
void
DS2Str(str,fmt,d)
	char *str, *fmt; struct DateStamp *d;
{
	UnpackedDS date;
	char fc,*fs,*out;
	USHORT ivalue;
	char new_fmt[256];			/* make it big to be "safe" */
	USHORT new_fmt_lng;
	char *svalue;

	UnpackDS(d, &date);			/* convert DateStamp to unpacked format */

	*str = '\0';				/* insure output is empty */
	out = str;
	fs = fmt;					/* make copy of format string pointer */

	while (fc = *fs++) {		/* get format characters */
		if (fc == '%') {		/* formatting meta-character? */
			new_fmt_lng = 0;
			new_fmt[new_fmt_lng++] = fc;
			/* copy width information */
			while (isdigit(fc = *fs++) || fc == '-')
				new_fmt[new_fmt_lng++] = fc;

			switch (fc) {		/* what are we trying to do? */
			case 'y':			/* year - 1980 */
				ivalue = date.year - 1900;
write_int:
				new_fmt[new_fmt_lng++] = 'd';
				new_fmt[new_fmt_lng] = '\0';
				sprintf(out,new_fmt,ivalue);
				out = str + strlen(str);
				break;
			case 'Y':			/* full year value */
				ivalue = date.year;
				goto write_int;

			case 'm':			/* month */
				ivalue = date.month;
				goto write_int;

			case 'M':			/* month name */
				svalue = monthnames[date.month - 1];
write_str:
				new_fmt[new_fmt_lng++] = 's';
				new_fmt[new_fmt_lng] = '\0';
				sprintf(out,new_fmt,svalue);
				out = str + strlen(str);
				break;

			case 'd':			/* day */
				ivalue = date.day;
				goto write_int;

			case 'D':			/* day name */
				svalue = daynames[d->ds_Days % 7];
				goto write_str;

			case 'h':			/* hour */
				ivalue = date.hour;
				goto write_int;

			case 'H':			/* hour in 12 hour notation */
				ivalue = date.hour;
				if (ivalue >= 12) ivalue -= 12;
				goto write_int;

			case 'i':			/* AM/PM indicator */
			case 'I':
				if (date.hour >= 12)
					svalue = "PM";
				else
					svalue = "AM";
				goto write_str;

			case 'n':			/* minutes */
			case 'N':
				ivalue = date.minute;
				goto write_int;

			case 's':			/* seconds */
			case 'S':
				ivalue = date.second;
				goto write_int;

			default:
				/* We are in deep caca - don't know what to do with this
				 * format character.  Copy the raw format string to the
				 * output as debugging information.
				 */
				new_fmt[new_fmt_lng++] = fc;
				new_fmt[new_fmt_lng] = '\0';
				strcat(out, new_fmt);
				out = out + strlen(out);	/* advance string pointer */
				break;
			}
		}
		else
			*out++ = fc;		/* copy literal character */
	}
	*out = '\0';				/* terminating null */
}

/* Convert a string to a DateStamp.
 * Called with:
 *		str:	string containing date in MM/DD/YY format		
 *		d:		pointer to DateStamp structure
 * Returns:
 *	    status code (0 => success, 1 => failure)
 */

int
Str2DS(str, d)
	char *str; struct DateStamp *d;
{
	register char c;
	int count;
	int i, item;
	UnpackedDS upd;				/* unpacked DateStamp */
	char *s;

	int values[3];
	int value;
	

	s = str;
	for (item = 0; item < 2; ++item) {	/* item = date, then time */
		for (i = 0; i < 3; ++i) values[i] = 0;
		count = 0;
		while (c = *s++) {			/* get date value */
			if (c <= ' ') 
				break;

			if (isdigit(c)) {
				value = 0;
				do {
					value = value*10 + c - '0';
					c = *s++;
				} while (isdigit(c));
				if (count == 3) {
	bad_value:
#ifdef DEBUG
					puts("Error in date-time format.\n");
					printf("at %s: values(%d) = %d, %d, %d\n",
						s, count, values[0], values[1], values[2]);
#endif
					return 1;
				}
				values[count++] = value;
				if (c <= ' ')
					break;
			}
		}							/* end while */
		if (item) {					/* getting time? */
			upd.hour = values[0];
			upd.minute = values[1];
			upd.second = values[2];
		}
		else {						/* getting date? */

/* It's OK to have a null date string, but it's not OK to specify only
 * 1 or 2 of the date components.
 */
			if (count && count != 3)
				goto bad_value;
			upd.month = values[0];
			upd.day = values[1];
			upd.year = values[2];
		}
	}								/* end for */
	PackDS(d,&upd);
	return 0;
}


/* Set a DateStamp structure, given the date/time components.
 * Called with:
 *		d:			pointer to DateStamp
 *      upd:		pointer to UnpackedDS
 */
PackDS(d,upd)
	struct DateStamp *d; UnpackedDS *upd;
{
	USHORT leapyear;
	short year,month,day,hour,minute,second;

	year = upd->year;			/* copy date components to locals */
	month = upd->month;
	day = upd->day;
	hour = upd->hour;
	minute = upd->minute;
	second = upd->second;

	if (year > 1900)
		year = year - 1900;

	leapyear = (year % 4 ? 0 : 1);

	year = year - 78;
	if (month < 1 || month > 12)	/* somebody goofed? */
		month = 1;

	day = day - 1 + monthdays[month-1];
	if (leapyear && (month > 2))
		++day;

	d->ds_Days = year * 365 + (year + 1) / 4 + day;
	d->ds_Minute = hour * 60 + minute;
	d->ds_Tick = second * TICKS_PER_SECOND;
}

/* Unpack a DateStamp structure into an UnpackedDS structure. 
 * Called with:
 *		ds:		pointer to DateStamp structure
 *		du:		pointer to UnpackedDS structure
 */
UnpackDS(ds, du)
	struct DateStamp *ds; UnpackedDS *du;
{
	USHORT i, leap_years, leap, temp, test_value;

	du->year = ds->ds_Days / 365 + 1978;
	
	/* is current year a leapyear? */
	leap = ( (du->year % 4) == 0); 

	/* how many leap years since "beginning of time"? */
	leap_years = (du->year - 1976 - 1) / 4;

	/* get days remaining in year */
	temp = ds->ds_Days % 365 - leap_years;

	/* find month */

	du->month = 0;
	du->day = 0;
	for (i = 11; i >= 0; --i) {
		test_value = monthdays[i];
		if (i > 2) test_value += leap;
		if (temp >= test_value) {
			du->month = i + 1;
			du->day = temp - test_value + 1;
			break;
		}
	}

	du->hour = ds->ds_Minute / 60;
	du->minute = ds->ds_Minute % 60;
	du->second = ds->ds_Tick / TICKS_PER_SECOND;
}


#ifdef DEBUG
main()
{
	int compflag;
	char datestr[81], instr[81];
	struct DateStamp *ds, *now;
	UnpackedDS du;

	now = (struct DateStamp *) 
		AllocMem((long) sizeof(struct DateStamp), MEMF_PUBLIC);

	ds = (struct DateStamp *) 
		AllocMem((long) sizeof(struct DateStamp), MEMF_PUBLIC);

	puts("Enter a date string and I will convert it.  To quit, hit RETURN");
	while (1) {
		DateStamp(now);
		UnpackDS(now, &du);

		printf("\nCurrent date and time: %02d/%02d/%02d %02d:%02d:%02d\n",
			du.month,du.day,du.year,du.hour,du.minute,du.second);

		puts("\nEnter the date [and time]:");
		gets(instr);
		if (*instr == '\0') break;
		if (Str2DS(instr,ds))
			puts("Error encountered in input string");
		else {
			DS2Str(datestr, "%02m/%02d/%02y %02h:%02n:%02s", ds);
			puts(datestr);

			DS2Str(datestr, "%D, %M %d, %Y", ds);
			puts(datestr);

			DS2Str(datestr, "The time entered is %02H:%02N %i", ds);
			puts(datestr);

			compflag = CompareDS(ds,now);
			printf("The date input is ");
			if (compflag < 0)
				printf("earlier than");
			else if (compflag == 0)
				printf("the same as");
			else
				printf("later than");
			puts(" the current date.");
		}
	}

	FreeMem(ds, (long) sizeof(struct DateStamp));
	FreeMem(now, (long) sizeof(struct DateStamp));
}
#endif
