/****************************************************************************/
/*	Copyright (c) 2009, Bendor Research Pty. Ltd. All rights reserved.		*/
/*	This software is released under the GNU General Public License, version	*/
/*	3, with the exemptions that 1) the header files installed by this 	 	*/
/*	package can be included in closed-source code and 2) the libraries		*/
/*	built by this package can be statically linked into a closed-source 	*/
/*	binary. See the file LICENSE for the details.							*/
/*	This software is provided "AS IS", without any warranty or guarantee.	*/
/*	By using this software you acknowledge that neither Bendor Research nor	*/
/*	any of its personnel or affiliates can be held liable for any damages,	*/
/*	including, but not limited to, loss of business, loss of money, loss of */
/*	life, loss of reputation, arising out of this software being faulty or	*/
/*	not performing as advertised, even if Bendor Research was aware of such	*/
/*	faults in the software. You use this software at your own risk, any and */
/*	all liabilities that arise out of your using this software are strictly	*/
/*	yours.																	*/
/****************************************************************************/

#include <time.h>

/****************************************************************************/
/*						Local data											*/
/****************************************************************************/

/*
*	Accumulated number of days at the end of the month (1=jan),
*	when it's not a leapyear.
*/

static	const unsigned short 	dim[] = {

	  0, 31,  59,  90, 120, 151, 181, 212, 243, 273, 304, 334, 365
};

/****************************************************************************/
/*						API functions										*/
/****************************************************************************/

/** @addtogroup time */
/** @{ */

/**
*	Converts a broken-down representation to unsigned UNIX time.
*
*	The function returns the number of seconds between 1970-01-01 00:00:00
*	UTC and the local time represented in the structure. The tm_wday and
*	tm_yday fields are ignored. The tm_year, tm_mon, tm_mday, tm_hour, tm_min,
*	tm_sec and tm_gmtoff fields are used to calculate a time. The fields are
*	not checked for validity, except:
*	- the tm_mon field must be between 0 and 11, since there's no way to
*	  determine the length of a month beyond December
*	- the tm_gmtoff field is checked to be between +/- 24 hours.
*	- the tm_year field is checked to be at least 70
*
*	Furthermore, the calculated time must fit in time_t. If the structure
*	contains invalid or unrepresentable time, then TIME_INVALID is returned.
*
*	@param	tm		Pointer to the broken-down time structure.
*	@return			The UNIX time corresponding to the structure
*
*	@attention
*		The C standard specifies that the function, after calculating the
*		UNIX time, converts it back to the structure, so that the structure
*		becomes normalised. This implementation deviates from the standard
*		because it does not convert the UNIX time back. If you want to do
*		that, all it takes is a call to localtime(). However, if you do not
*		want to do that, there's no reason to waste CPU time with unnecessary
*		calculations.
*/

time_t	mktime( const struct tm *tm )
{
time_t	y, n;

	// If the structure is completely out of whack, we don't try to
	// convert it.

	if ( tm->tm_year < 70 || tm->tm_year > 206 ) return( TIME_INVALID );
	if ( tm->tm_mon > 11 ) return( TIME_INVALID );
	if ( tm->tm_gmtoff < -86399 ) return( TIME_INVALID );
	if ( tm->tm_gmtoff > +86399 ) return( TIME_INVALID );

	y = tm->tm_year - 70;
	n = y * 365;
	if ( y > 2 ) n += ( y + 1 ) / 4;

	// Now n contains the days until 1st Jan of the given year

	n += dim[ tm->tm_mon ] + ((( y & 3 ) == 2 && tm->tm_mon > 1 ) ? 1 : 0 );

	// Now n contains the days until 1st of the current month

	n += tm->tm_mday - 1;
	y =  ( tm->tm_hour * 60 + tm->tm_min ) * 60 + tm->tm_sec;

	// Now n contains the days until the current day's 00:00:00
	// and y contains the number of seconds of the day

	if ( (long) y - tm->tm_gmtoff < 0 ) {

		// When we apply the timezone offset, we will get
		// an underflow. It would be OK, but to see if the
		// result fits in 32 bit we must avoid negative
		// numbers, so we compensate for it here.

		if ( ! n-- ) return( TIME_INVALID );
		y += 86400 - tm->tm_gmtoff;
	}
	else {

		y -= tm->tm_gmtoff;
	}

	// Now we have to check if the resulting time can be represented
	// in 32 bits

	if ( n > 49710 ) return( TIME_INVALID );
	n *= 24*60*60;
	if ( ((time_t) -2) - n < y ) return( TIME_INVALID );

	// The time can be represented on 32 bits, everything is fine.
	// As per the standard, we should now convert the such calculated
	// UTC time to broken-down format, so that the structure is normalised.
	// We do not do that. If the user wants to do that, (s)he can, all it
	// takes is a function call. On the other hand, why waste time when
	// the normalisation conversion is not really needed?

	return( y + n );
}

/** @} */

