#!mkcmd
# $Id: datecalc.m,v 1.16 2009/03/12 19:05:14 ksb Exp $
# $Mkcmd: mkcmd %f
#
from '<stdlib.h>'
from '<stdio.h>'
from '<ctype.h>'
from '<errno.h>'
from '<sys/types.h>'
from '<sysexits.h>'
from '<time.h>'
from '"machine.h"'

basename "datecalc" ""

require "std_help.m" "std_version.m"
require "util_time.m"
# for better conversion (in local time zone)
require "time_tz.m"

%i
static char rcsid[] =
	"$Id: datecalc.m,v 1.16 2009/03/12 19:05:14 ksb Exp $";

#define ISNOT		0
#define ISTIME		1
#define ISDATE		2
#define ISSCALAR	3

extern int TimeToSeconds(char *, time_t *);
extern char *SecondsToTime(time_t);
extern char *EatWhite(char *);
extern char *EatNumber(char *);
static char acAtSpec[] = "%H%M %h %d";
static char acAtYears[] = "-t %G%m%d%H%M";
%%

# Operations
exclude "asmdc"
boolean 'a' {
	named "fAdd"
	init "0"
	update "%ran = !%i;"
	help "Add a time string to a date"
}
boolean 's' {
	named "fSubtract"
	init "0"
	update "%rsn = !%i;"
	help "Subtract a time string from a date"
}
boolean 'm' {
	named "fMultipy"
	init "0"
	update "%rmn = !%i;"
	exclude "asdoefnpty"
	help "Multiply a time string by a scalar"
}
boolean 'd' {
	named "fDivide"
	init "0"
	update "%rdn = !%i;"
	help "Divide a time string by a scalar"
}
# remainder/modulus?
#boolean 'r' {
#	named "fRemainder"
#	init "0"
#	help "Modulus a time by a scaler"
#}
boolean 'c' {
	named "fCompare"
	init "0"
	update "%rcn = !%i;"
	help "Compare two dates or times"
}

# Ouptut formats
exclude "petfny"
boolean 'e' {
	named "fUnEpoch"
	init "0"
	update "%ren = !%i;"
	help "Convert seconds since the epoch to a date"
}
boolean 'f' {
	named "fFixedFormat"
	init "0"
	update "%rfn = !%i;"
	help "Convert a free format date to fixed format"
}
boolean 'p' {
	named "fEpoch"
	init "0"
	update "%rpn = !%i;"
	help "Convert a date to seconds since the epoch"
}
action 't' {
	init "0"
	update "%rfn = !%rfi;"
	user 'pcFormat = acAtSpec;'
	exclude "o"
	help "Convert a free format date to at's format"
}
boolean 'n' {
	named "fSeconds"
	exclude "o"
	init "0"
	help "Convert a time string to seconds"
}
boolean 'y' {
	named "fYears"
	exclude "o"
	init "0"
	help "Convert a time string to Years Days Hours Minutes Seconds"
}
char* 'o' {
	named "pcFormat"
	init '(char *)0'
	param "format"
	help "Specify a strftime(3) output format"
}

boolean 'v' {
	named "fVerbose"
	init "0"
	help "Verbose"
}

list {
	named "Process"
	param "dates"
	help "reference date/time for computation"
	aborts "exit(EX_TEMPFAIL);"
}

%c
/*									(mm)
 * When we are doing -t and the target is not in the current year
 * we need to change the output format to at's -t format (to get years),
 * some at commands won't take years w/o -t.
 */
void
Output(time_t ttParam)
{
	auto time_t tNow;
	auto struct tm *tm_date;
	auto char acLine[4096];
	auto int iYear;

	(void)time(&tNow);
	tm_date = localtime(&tNow);
	iYear = tm_date->tm_year;
	tm_date = localtime(&ttParam);

	if ((char *)0 == pcFormat) {
		pcFormat = "%a %h %d %T %Z %Y";
	} else if (acAtSpec != pcFormat) {
		/* we don't mess with a user format */
	} else if (iYear != tm_date->tm_year) {
		pcFormat = acAtYears;
	}
	strftime(acLine, sizeof(acLine)-1, pcFormat, tm_date);
	printf("%s\n", acLine);
}

/*									(mm)
 * ksb added strftime's %D (%m/%d/%y) format as a date.
 */
int
TimeOrDate(char *pcStr, time_t *piTime, time_t *pttParam, int *piScalar)
{
	register int iTmp;
	register char *pcNum;

	if (NULL == pcStr || '\000' == *pcStr) {
		return ISNOT;
	}

	if ((time_t *)0 != piTime && TimeToSeconds(pcStr, piTime)) {
		return ISTIME;
	}

	/* Ouch!  We hard coded the timezone here, let's let mkcmd find it.
	 */
	if (pttParam) {
		if (0 == strcmp("yesterday", pcStr)) {
			time(pttParam);
			*pttParam -= (24*60*60);
			return ISDATE;
		}
		if (0 == strcmp("tomorrow", pcStr)) {
			time(pttParam);
			*pttParam += (24*60*60);
			return ISDATE;
		}
		if (0 == strcmp("now", pcStr) || 0 == strcmp("today", pcStr)) {
			time(pttParam);
			return ISDATE;
		}
		iTmp = atotm(pcStr, Atz_West);
		if (iTmp >= 0) {
			*pttParam = iTmp;
			return ISDATE;
		}
	}

	pcNum = EatWhite(pcStr);
	pcStr = EatNumber(pcNum);
	if ('\000' != *pcStr && (!isascii(*pcStr) || !isspace(*pcStr))){
		return ISNOT;
	}
	pcStr = EatWhite(pcStr);
	if ('\000' != *pcStr) {
		return ISNOT;
	}

	*piScalar = atoi(pcNum);
	return ISSCALAR;
}

/*									(mm)
 */
void
Process(int argc, char **argv)
{
	register int iArg1Type, iArg2Type;
	auto time_t ttTime, ttDummy;
	auto time_t ttDate1, ttDate2, ttTotal;
	auto int iScalar, iDummy;

	if (0 == argc) {
		fprintf(stderr, "%s: missing parameters\n", progname);
		exit(EX_USAGE);
	}
	iArg1Type = ISNOT;
	iArg2Type = ISNOT;

	switch (iArg1Type = TimeOrDate(argv[0], &ttTime, &ttDate1, &iScalar)) {
	case ISNOT: /* 1st Arg */
		break;
	case ISTIME: /* 1st Arg */
		switch (iArg2Type = TimeOrDate(argv[1], &ttDummy, &ttDate1, &iScalar)) {
		case ISNOT: /* 2nd Arg */
			break;
		case ISTIME: /* 2nd Arg */
			if (!fCompare) {
				fprintf(stderr,"%s: Second time (\"%s\") not valid.\n", progname, argv[1]);
				exit(EX_DATAERR);
			}
			break;
		case ISDATE: /* 2nd Arg */
			break;
		case ISSCALAR: /* 2nd Arg */
			break;
		default: /* 2nd Arg */
			fprintf(stderr,"%s: Something went horribly wrong in TimeOrDate().\n", progname);
			exit(EX_SOFTWARE);
		}
		break;
	case ISDATE: /* 1st Arg */
		switch (iArg2Type = TimeOrDate(argv[1], &ttTime, &ttDate2, &iScalar)) {
		case ISNOT: /* 2nd Arg */
			break;
		case ISTIME: /* 2nd Arg */
			break;
		case ISDATE: /* 2nd Arg */
			break;
		case ISSCALAR: /* 2nd Arg */
			if (fAdd || fSubtract || fCompare || fFixedFormat || fSeconds || fYears) {
				fprintf(stderr,"%s: A scalar is not valid for this operation\n", progname);
				exit(EX_DATAERR);
			}
			break;
		default: /* 2nd Arg */
			fprintf(stderr,"%s: Something went horribly wrong in TimeOrDate().\n", progname);
			exit(EX_SOFTWARE);
		}
		break;
	case ISSCALAR: /* 1st Arg */
		switch (iArg2Type = TimeOrDate(argv[1], &ttTime, &ttDate2, &iDummy)) {
		case ISNOT: /* 2nd Arg */
			break;
		case ISTIME: /* 2nd Arg */
			break;
		case ISDATE: /* 2nd Arg */
			break;
		case ISSCALAR: /* 2nd Arg */
			if (fAdd || fSubtract || fCompare || fFixedFormat || fSeconds || fYears) {
				fprintf(stderr,"%s: A scalar is not valid for this operation\n", progname);
				exit(EX_DATAERR);
			}
			break;
		default: /* 2nd Arg */
			fprintf(stderr,"%s: Something went horribly wrong in TimeOrDate().\n", progname);
			exit(EX_SOFTWARE);
		}
		break;
	default: /* 1st Arg */
		fprintf(stderr,"%s: Something went horribly wrong in TimeOrDate().\n", progname);
		exit(EX_SOFTWARE);
	}

	if (fUnEpoch || fEpoch || fFixedFormat || fSeconds || fYears) {
		if (ISNOT != iArg2Type) {
			fprintf(stderr,"%s: Extra argument (\"%s\") ignored\n", progname, argv[1]);
		}
	} else if (fAdd || fSubtract || fMultipy || fDivide || fCompare) {
		/* Null */;
	} else if (ISNOT == iArg2Type) {
		/* default to add 0 seconds (or a date like tomorrow) */
		fAdd = 1;
		iArg2Type = ISTIME;
		ttTime = 0;
	} else {
		fprintf(stderr,"%s: Unknown operation\n", progname);
		exit(EX_UNAVAILABLE);
	}

	if (fAdd) {		/* -a */
		if ( !(ISDATE == iArg1Type && ISTIME == iArg2Type) &&
		     !(ISTIME == iArg1Type && ISDATE == iArg2Type)) {
			fprintf(stderr,"%s: A date and a time are required for Addition\n", progname);
			exit(EX_DATAERR);
		}
		if (fVerbose) {
			printf("Adding %ld to %ld\n", (long)ttTime, (long)ttDate1);
		}
		ttTotal = ttDate1 + ttTime;
		Output(ttTotal);
		exit(EX_OK);
	}

	if (fSubtract) {	/* -s */
		if ( (ISDATE == iArg1Type && ISTIME == iArg2Type) ||
		     (ISTIME == iArg1Type && ISDATE == iArg2Type)) {
			if (fVerbose)
				printf("Subtracting %ld from %ld\n", (long)ttTime, (long)ttDate1);
			ttTotal = ttDate1 - ttTime;
			Output(ttTotal);
		} else if ((ISDATE == iArg1Type && ISDATE == iArg2Type)) {
			if (fVerbose)
				printf("Subtracting %d from %d\n", (int)ttDate2, (int)ttDate1);
			ttTotal = ttDate1 - ttDate2;
			printf("%s\n", SecondsToTime(ttTotal));
		} else {
			fprintf(stderr,"%s: A date and a time, or two dates are required for Subtraction\n", progname);
			exit(EX_DATAERR);
		}
		exit(EX_OK);
	}

	if (fMultipy) {	/* -m */
		if ( !(ISTIME == iArg1Type && ISSCALAR == iArg2Type) &&
		     !(ISSCALAR == iArg1Type && ISTIME == iArg2Type)) {
			fprintf(stderr,"%s: Arguments must be a time and a scalar\n", progname);
			exit(EX_DATAERR);
		}
		if (fVerbose) {
			printf("Multiplying %ld and %ld\n", (long)ttTime, (long)iScalar);
		}
		ttTotal = ttTime * iScalar;
		printf("%s\n", SecondsToTime(ttTotal));
		exit(EX_OK);
	}

	if (fDivide) {
		if ( !(ISTIME == iArg1Type && ISSCALAR == iArg2Type) &&
		     !(ISSCALAR == iArg1Type && ISTIME == iArg2Type)) {
			fprintf(stderr,"%s: Arguments must be a time and a scalar\n", progname);
			exit(EX_DATAERR);
		}
		if (fVerbose) {
			printf("Dividing %ld and %ld\n", (long)ttTime, (long)iScalar);
		}
		ttTotal = ttTime / iScalar;
		printf("%s\n", SecondsToTime(ttTotal));
		exit(EX_OK);
	}

	if (fCompare) {
		if (ISDATE != iArg1Type || ISDATE != iArg2Type) {
			fprintf(stderr,"%s: Two dates are required for comparison\n", progname);
			exit(EX_DATAERR);
		}
		if (fVerbose) {
			printf("Comparing %d and %d\n", (int)ttDate1, (int)ttDate2);
		}
		if (ttDate1 == ttDate2) {
			printf("0\n");
		} else if (ttDate1 < ttDate2) {
			printf("-1\n");
		} else /* ttDate1 > ttDate2 */ {
			printf("1\n");
		}
		exit(EX_OK);
	}

	if (fUnEpoch) {		/* -e */
		if (ISSCALAR == iArg1Type) {
			Output((time_t)iScalar);
		} else if (ISDATE == iArg1Type) {
			Output((time_t)ttDate1);
		} else if (ISTIME == iArg1Type) {
			Output((time_t)ttTime);
		} else {
			fprintf(stderr,"%s: Operation requires a scalar or a time\n", progname);
			exit(EX_DATAERR);
		}
		exit(EX_OK);
	}

	if (fEpoch) {	/* -p */
		if (ISDATE != iArg1Type) {
			fprintf(stderr,"%s: Operation requires a date\n", progname);
			exit(EX_DATAERR);
		}
		printf("%d\n", (int)ttDate1);
		exit(EX_OK);
	}

	if (fFixedFormat) {	/* -f */
		if (ISDATE != iArg1Type) {
			fprintf(stderr, "%s: Operation requires a date\n", progname);
			exit(EX_DATAERR);
		}
		Output(ttDate1);
		exit(EX_OK);
	}

	if (fSeconds) {	/* -n */
		if (ISTIME != iArg1Type) {
			fprintf(stderr,"%s: Operation requires a time\n", progname);
			exit(EX_DATAERR);
		}
		printf("%d seconds\n", (int)ttTime);
		exit(EX_OK);
	}

	if (fYears) {		/* -y */
		if (ISTIME != iArg1Type) {
			fprintf(stderr,"%s: Operation requires a time\n", progname);
			exit(EX_DATAERR);
		}
		printf("%s\n", SecondsToTime(ttTime));
		exit(EX_OK);
	}
	/* no output -- let the called decide (exit failure) */
}
