/***
*mktime.c - Convert struct tm value to time_t value.
*
*	Copyright (c) 1987-1993, Microsoft Corporation. All rights reserved.
*
*Purpose:
*	Defines mktime() and _mkgmtime(), routines to converts a time value
*	in a tm structure (possibly incomplete) into a time_t value, then
*	update (all) the structure fields with "normalized" values.
*
*Revision History:
*	01-14-87  JCR	Module created
*	12-11-87  JCR	Added "_LOAD_DS" to declaration
*	06-15-89  PHG	Now allows negative values and does DST by ANSI rules
*	11-06-89  KRS	Added (unsigned) to handle years 2040-2099 correctly.
*	03-20-90  GJF	Replaced _LOAD_DS with _CALLTYPE1, added #include
*			<cruntime.h> and fixed the copyright. Also, cleaned
*			up the formatting a bit.
*	10-04-90  GJF	New-style function declarator. Also, rewrote expr.
*			to avoid using casts as lvalues.
*	10-26-90  GJF	Added ulscount to avoid overflows. Ugly, temporary
*			hack (whole function needs to be revised for ANSI
*			conformance).
*	01-22-91  GJF	ANSI naming.
*	03-24-93  GJF	Propagated changes from 16-bit tree. Modified to
*			expose _mkgmtime() routine.
*	07-15-93  GJF	Replaced _tzset() call with __tzset() call.
*
*******************************************************************************/

#include <cruntime.h>
#include <stddef.h>
#include <ctime.h>
#include <time.h>
#include <internal.h>

/*
 * ChkAdd evaluates to TRUE if dest = src1 + src2 has overflowed
 */
#define ChkAdd(dest, src1, src2)   ( ((src1 >= 0L) && (src2 >= 0L) \
    && (dest < 0L)) || ((src1 < 0L) && (src2 < 0L) && (dest >= 0L)) )

/*
 * ChkMul evaluates to TRUE if dest = src1 * src2 has overflowed
 */
#define ChkMul(dest, src1, src2)   ( src1 ? (dest/src1 != src2) : 0 )


/*
 * Core function for both mktime() and _mkgmtime()
 */
static time_t _CRTAPI3 _make_time_t( struct tm *, int);


/***
*time_t mktime(tb) - Normalize user time block structure
*
*Purpose:
*	Mktime converts a time structure, passed in as an argument, into a
*	calendar time value in internal format (time_t). It also completes
*	and updates the fields the of the passed in structure with 'normalized'
*	values. There are three practical uses for this routine:
*
*	(1) Convert broken-down time to internal time format (time_t).
*	(2) To have mktime fill in the tm_wday, tm_yday, or tm_isdst fields.
*	(3) To pass in a time structure with 'out of range' values for some
*	    fields and have mktime "normalize" them (e.g., pass in 1/35/87 and
*	    get back 2/4/87).
*Entry:
*	struct tm *tb - pointer to a tm time structure to convert and
*			normalize
*
*Exit:
*	If successful, mktime returns the specified calender time encoded as
*	a time_t value. Otherwise, (time_t)(-1) is returned to indicate an
*	error.
*
*Exceptions:
*	None.
*
*******************************************************************************/


time_t _CRTAPI1 mktime (
	struct tm *tb
	)
{
	return( _make_time_t(tb, 1) );
}


/***
*time_t _mkgmtime(tb) - Convert broken down UTC time to time_t
*
*Purpose:
*	Convert a tm structure, passed in as an argument, containing a UTC
*	time value to internal format (time_t). It also completes and updates
*	the fields the of the passed in structure with 'normalized' values.

*Entry:
*	struct tm *tb - pointer to a tm time structure to convert and
*			normalize
*
*Exit:
*	If successful, _mkgmtime returns the calender time encoded as time_t
*	Otherwise, (time_t)(-1) is returned to indicate an error.
*
*Exceptions:
*	None.
*
*******************************************************************************/

time_t _CRTAPI1 _mkgmtime (
	struct tm *tb
	)
{
	return( _make_time_t(tb, 0) );
}


/***
*static time_t make_time_t(tb, ultflag) -
*
*Purpose:
*	Converts a struct tm value to a time_t value, then updates the struct
*	tm value. Either local time or UTC is supported, based on ultflag.
*	This is the routine that actually does the work for both mktime() and
*	_mkgmtime().
*
*Entry:
*	struct tm *tb - pointer to a tm time structure to convert and
*			normalize
*	int ultflag   - use local time flag. the tb structure is assumed
*			to represent a local date/time if ultflag > 0.
*			otherwise, UTC is assumed.
*
*Exit:
*	If successful, mktime returns the specified calender time encoded as
*	a time_t value. Otherwise, (time_t)(-1) is returned to indicate an
*	error.
*
*Exceptions:
*	None.
*
*******************************************************************************/

static time_t _CRTAPI3 _make_time_t (
	struct tm *tb,
	int ultflag
	)
{
	long tmptm1, tmptm2, tmptm3;
	struct tm *tbtemp;

	/*
	 * First, make sure tm_year is reasonably close to being in range.
	 */
	if ( ((tmptm1 = tb->tm_year) < _BASE_YEAR - 1) || (tmptm1 > _MAX_YEAR
	  + 1) )
	    goto err_mktime;


	/*
	 * Adjust month value so it is in the range 0 - 11.  This is because
	 * we don't know how many days are in months 12, 13, 14, etc.
	 */

	if ( (tmptm2 = tb->tm_mon/12) != 0L ) {
	    /*
	     * no danger of overflow because the range check above.
	     */
	    tmptm1 += tmptm2;

	    if ( (tb->tm_mon %= 12) < 0 ) {
		tb->tm_mon += 12;
		tmptm1--;
	    }

	    /*
	     * Make sure year count is still in range.
	     */
	    if ( (tmptm1 < _BASE_YEAR - 1) || (tmptm1 > _MAX_YEAR + 1) )
		goto err_mktime;
	}

	/***** HERE: tmptm1 holds number of elapsed years *****/

	/*
	 * Calculate days elapsed minus one, in the given year, to the given
	 * month. Check for leap year and adjust if necessary.
	 */
	tmptm2 = _days[tb->tm_mon];
	if ( !(tmptm1 & 3) && (tb->tm_mon > 1) )
		tmptm2++;

	/*
	 * Calculate elapsed days since base date (midnight, 1/1/70, UTC)
	 *
	 *
	 * 365 days for each elapsed year since 1970, plus one more day for
	 * each elapsed leap year. no danger of overflow because of the range
	 * check (above) on tmptm1.
	 */
	tmptm3 = (tmptm1 - _BASE_YEAR) * 365L + ((tmptm1 - 1L) >> 2)
	  - _LEAP_YEAR_ADJUST;

	/*
	 * elapsed days to current month (still no possible overflow)
	 */
	tmptm3 += tmptm2;

	/*
	 * elapsed days to current date. overflow is now possible.
	 */
	tmptm1 = tmptm3 + (tmptm2 = (long)(tb->tm_mday));
	if ( ChkAdd(tmptm1, tmptm3, tmptm2) )
	    goto err_mktime;

	/***** HERE: tmptm1 holds number of elapsed days *****/

	/*
	 * Calculate elapsed hours since base date
	 */
	tmptm2 = tmptm1 * 24L;
	if ( ChkMul(tmptm2, tmptm1, 24L) )
	    goto err_mktime;

	tmptm1 = tmptm2 + (tmptm3 = (long)tb->tm_hour);
	if ( ChkAdd(tmptm1, tmptm2, tmptm3) )
	    goto err_mktime;

	/***** HERE: tmptm1 holds number of elapsed hours *****/

	/*
	 * Calculate elapsed minutes since base date
	 */

	tmptm2 = tmptm1 * 60L;
	if ( ChkMul(tmptm2, tmptm1, 60L) )
	    goto err_mktime;

	tmptm1 = tmptm2 + (tmptm3 = (long)tb->tm_min);
	if ( ChkAdd(tmptm1, tmptm2, tmptm3) )
	    goto err_mktime;

	/***** HERE: tmptm1 holds number of elapsed minutes *****/

	/*
	 * Calculate elapsed seconds since base date
	 */

	tmptm2 = tmptm1 * 60L;
	if ( ChkMul(tmptm2, tmptm1, 60L) )
	    goto err_mktime;

	tmptm1 = tmptm2 + (tmptm3 = (long)tb->tm_sec);
	if ( ChkAdd(tmptm1, tmptm2, tmptm3) )
	    goto err_mktime;

	/***** HERE: tmptm1 holds number of elapsed seconds *****/

	if  ( ultflag ) {
	    /*
	     * Adjust for timezone. No need to check for overflow since
	     * localtime() will check its arg value
	     */

#ifdef _POSIX_
	    tzset();
#else
	    __tzset();
#endif
	    tmptm1 += _timezone;

	    /*
	     * Convert this second count back into a time block structure.
	     * If localtime returns NULL, return an error.
	     */
	    if ( (tbtemp = localtime(&tmptm1)) == NULL )
		goto err_mktime;

	    /*
	     * Now must compensate for DST. The ANSI rules are to use the
	     * passed-in tm_isdst flag if it is non-negative. Otherwise,
	     * compute if DST applies. Recall that tbtemp has the time without
	     * DST compensation, but has set tm_isdst correctly.
	     */
	    if ( (tb->tm_isdst > 0) || ((tb->tm_isdst < 0) &&
	      (tbtemp->tm_isdst > 0)) ) {
#ifdef _POSIX_
		tmptm1 -= _timezone;
		tmptm1 += _dstoffset;
#else
		tmptm1 -= 3600L;
#endif
		tbtemp = localtime(&tmptm1);	/* reconvert, can't get NULL */
	    }
	} 
	else {
	    if ( (tbtemp = gmtime(&tmptm1)) == NULL )
		goto err_mktime;
	}

	/***** HERE: tmptm1 holds number of elapsed seconds, adjusted *****/
	/*****	     for local time if requested		      *****/

	*tb = *tbtemp;
	return (time_t)tmptm1;

err_mktime:
	/*
	 * All errors come to here
	 */
	return (time_t)(-1);
}
