/*
* pgpTimeDate.c -- conversions between a word32 timestamp and year,
* month, day, and time. A bunch of convenience functions.
*
* Copyright (C) 1996,1997 Pretty Good Privacy, Inc. All rights reserved.
*
* $Id: pgpTimeDate.c,v 1.4.2.2 1997/06/07 09:50:13 mhw Exp $
*/

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

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

#include "pgpTimeDate.h"
#include "pgpUsuals.h"
#include "pgpTypes.h"
#include "pgpMem.h"

static Boolean	offsetsComputed = FALSE;

/*
* Offset in seconds from UTC
*/
static long 	tzDiff;

/*
* Compute local time_t representation of
* Midnight Jan 1 1970 local time.
*/
static time_t	stdEpochLocal;

	static void
pgpComputeOffsets(void)
	{
			static struct tm		stdEpochStruct = { 0, 0, 0, 1, 0, 70, 0, 0, 0 };
			time_t		 				testTime = 10000000;
			struct tm *					utcTime;
			struct tm					timeStruct;
	
			if (!offsetsComputed)
			{
					stdEpochLocal = mktime(&stdEpochStruct);
				
					utcTime = gmtime(&testTime);
					if (utcTime == NULL)
					{
							/*
							* Time zone information not available,
							* so just pretend we're in UTC.
							*/
							tzDiff = 0;
					}
					else
					{
							pgpCopyMemory(utcTime, &timeStruct, sizeof(timeStruct));
	tzDiff = testTime - mktime(&timeStruct);
}
offsetsComputed = TRUE;
}
}

		static time_t
pgpStdEpochLocal(void)
	{
			pgpComputeOffsets();
			return stdEpochLocal;
	}

			static time_t
pgpStdEpoch(void)
	{
			pgpComputeOffsets();
			return stdEpochLocal + tzDiff;
	}

			static long
pgpTZDiff(void)
	{
			pgpComputeOffsets();
			return tzDiff;
	}

			time_t
pgpFromPGPTime(
			PGPTime					theTime)
	{
			return theTime + pgpStdEpoch();
	}

			PGPTime
pgpToPGPTime(
			time_t					theTime)
	{
			return theTime - pgpStdEpoch();
	}

			PGPTime
pgpGetTime(void)
{
	return pgpToPGPTime(time((time_t *) NULL));
}

	struct tm *
pgpLocalTime(
			PGPTime const * theTime)
	{
			time_t		 stdTime = pgpFromPGPTime(*theTime);
		
			return localtime(&stdTime);
	}

			char *
pgpCTime(
PGPTime const *	theTime)
{
	time_t 	stdTime = pgpFromPGPTime(*theTime);

	return ctime(&stdTime);
}

#if MACINTOSH	/* [ */

/*
* Number of seconds between Midnight Jan 1, 1904 (Mac time base)
* and Midnight Jan 1, 1970 (Un*x time base)
*/
#define kMacTimeDiff	2082844800

	ulong
pgpTimeToMacTime(PGPTime theTime)
{
	return theTime + kMacTimeDiff + pgpTZDiff();
}

	PGPTime
pgpTimeFromMacTime(ulong theTime)
{
return theTime - kMacTimeDiff - pgpTZDiff();
}

#endif	/* ] MACINTOSH */

/* System clock must be broken if it isn't past this date: */
#define REASONABLE_DATE ((unsigned long) 0x27804180L) /* 91 Jan 01 00:00:00 */

/*
* Convert a year-month-day to a number of days since Jan 1, 1970
* Month and day are 1-based.
*
* To compute the day of the week, note that Jan 1, 1970 is a Thursday.
*/
unsigned
pgpDateFromYMD (int y, int m, int d)
	{
			unsigned t;
			static const unsigned startday[12] = {
				 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334 };
			/*	J F M A M J	J	A	S	O	N	D */

/* Number of 4-years since 1968 */
t = (y - 1968)/4;
/* Number of days in the 4-years */
t *= 365*4+1;
/* Add days in years since */
t += 365 * (y%4);
/* Add leap day unless before march in year 0 of cycle */
if (y % 4 != 0 || m > 2)
	t++;
/* Adjust to 1970 base */
t -= 365*2+1;
/* Add month and day offset */
	t += startday[m-1] + d-1;

	return t;
}

/*
* Given timestamp as days elapsed since 1970 Jan 1,
* returns year (1970-2106), month (1-12), and day (1-31).
* Not valid for dates after 2100 Feb 28 (no leap day that year).
*/
void
pgpDateToYMD (unsigned days, int *year, int *month, int *day)
	{
			int y, m;
			static const unsigned mdays[12] =	/* March..February */
				 {31,30,31,30,31,31,30,31,30,31,31,29};

			days += 365+365-31-28;	/* Days since Fri 1 Mar 1968 */

/* Locate to within a leap-year cycle */
y = days / 1461;
days %= 1461;
y *= 4;

/* Find the year within the cycle */
if (days >= 730) {
	y += 2;
	days -= 730;
}
if (days >= 365) {
	y++;
	days -= 365;
}

*year = y;

			/* days is now in the range [0..365] */
			/* Compute month, based on March=0 */
			m = 0;
			while (days >= mdays[m])
				 days -= mdays[m++];

			/* Now, fix the month so March = 2, with carry into the year */
			m += 2;
			if (m >= 12) {	/* January or February of the next year */
				 m -= 12;
				 y++;
			}

			/* Save the final (1-based) results */
			*year = y + 1968;
			*month = m+1;
			*day = days+1;
	}

word32
pgpTimeStamp (int tzFix)
{
return (word32)(pgpGetTime() + (60 * 60 * tzFix));
}

/* XXX: The routine below fails on Mac's when the timezone isn't set */
#if 0	/* [ */
/*
* It's a bit annoying that we have to go through these shenanigans
* to get seconds since Jan 1, 1970 0:00:00 GMT (okay, okay, UTC),
* when time() on half the platforms in the world returns this value
* already, but some platforms (e.g. Microsoft C, version 7.0 - but
* not earlier versions!) return different values.
*
* So this uses the ANSI C functions. tm_yday could be used to reduce the
* work, but this is hardly a time-critical function and tm_yday is
* infrequently used, so it might be buggy.
*
* ANSI allows the time() and gmtime() to fail, so this returns 0 in that
* case.
*/
word32
pgpTimeStamp (int tzFix)
	{
				time_t const t = time((time_t *)0);
				struct tm const *g;
				unsigned days;

			if (t == (time_t)-1)
					return 0;
				g = gmtime(&t);
				if (!g)
					return 0;

				/* tm_year is 1900-based, tm_mon is 0-based, tm_day is 1-based */
				/* Some systems do tm_year oddly, though... */
				days = pgpDateFromYMD(g->tm_year + (g->tm_year > 1900 ? 0 : 1900),
				g->tm_mon+1, g->tm_mday);
				return g->tm_sec+60*(g->tm_min+60*(g->tm_hour+tzFix+24*(word32)days));
	}
#endif	/* ] 0 */

/*
* Create a date string, given a 32-bit timestamp. Returns characters
* printed. Format: "yyyy-mm-dd"
* 1234567890
* This format is specified by some international ISO standard, but I
* forget which one.
*/
int
pgpDateString (word32 tstamp, char buf[PGPDATESTRINGLEN+1])
{
		int month, day, year;

	if (tstamp == 0) {
		memset (buf, ' ', 10);
		buf[10] = '\0';
		return 10;
}

	pgpDateToYMD((unsigned)(tstamp/86400), &year, &month, &day);
	sprintf (buf, "%4d-%02d-%02d", year, month, day);
 return PGPDATESTRINGLEN;
}

/*
* Create a "date and time" string, given a 32-bit timestamp.
* Returns number of characters printed.
* Format: "yyyy-mm-dd hh:mm GMT"
* 12345678901234567890
*/
int
pgpTimeString (word32 tstamp, char buf[PGPTIMESTRINGLEN+1])
	{
			unsigned min;

		pgpDateString (tstamp, buf);
			tstamp /= 60;	/* Minutes since 00:00 Jan 1, 1970 */
			min = (unsigned)(tstamp % 1440);	 /* Minutes since 0:00 today */
			sprintf (buf+PGPDATESTRINGLEN, " %02d:%02d GMT", min/60, min%60);
			return PGPTIMESTRINGLEN;
	}

#ifdef TESTMAIN
#include <stdlib.h>

int
main(void)
{
			unsigned i, t;
			int y, m, d;

			puts("Starting test...");
			for (i = 0; i < 65535; i++) {
					if (i % 1000 == 0)
						printf("% 5u\r", i);
					pgpDateToYMD(i, &y, &m, &d);
					t = pgpDateFromYMD(y, m, d);
					if (i != t) {
					printf("\ni = %u != t = %u   y/m/d = %04d/%02d/%02d\n",
							i, t, y, m, d);
					}
			}
			puts("\nDone!");
			return 0;
	}

#endif
