/****************************************************************************/
/*	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>
#include <misc/multiply.h>

/****************************************************************************/
/*						Local definitions									*/
/****************************************************************************/

/*
*	Precalculated values for the multiply instead of divide trick
*/

#define	DIV_7		0x92492492u, 2
#define	DIV_24		0xaaaaaaaau, 4
#define	DIV_60		0x88888888u, 5
#define	DIV_365		0xb38cf9b0u, 8
#define	DIV_1461	0xb36d8397u, 10

/*
*	This macro calculates b = a % c, a /= c. It uses a variable called
*	'tmp' that must be defined. C must be a mumeric constant for which
*	a DIV_nn macro defines a 2-element tuple.
*/

#define	DIVMOD(a,b,c) (tmp=divbyconst((a),DIV_##c),(b)=(a)-tmp*c,(a)=tmp)

/****************************************************************************/
/*						Local definitions									*/
/****************************************************************************/

/*
*	Number of days in the months when it is not a leapyear.
*/

static	const unsigned char	dim[] = {

	31, 28, 31, 30, 31, 30,
	31, 31, 30, 31, 30, 31
};

/****************************************************************************/
/*						Inline functions									*/
/****************************************************************************/

/*
*	The multiplication that is used in stead of a division by constant.
*	It is invoked by the DIVMOD() macro.
*	Although gcc knows the division by constant using multiplies trick,
*	when it is compiling for THUMB, it doesn't have the 32x32->64
*	multiply instruction in its disposal, thus it generates a call to
*	the software division routine in the gcc support library. In order
*	to avoid that, we use this inline. In ARM mode the multiplication
*	will be inlined, in THUMB the call to umul() is orders of magnitude
*	faster than a call to the division routine.
*/

static inline unsigned int divbyconst( unsigned int num,
									   unsigned int mul,
									   unsigned int sft )
{
	return( umul32x32_64( num + 1, mul ) >> ( 32 + sft ) );
}

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

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

/**
*	Converts the unix time to broken-down representation.
*
*	The function receives a UTC value and breaks it down to the
*	individual fields of the \p tm structure. The function sets the
*	structure's timezone field to 0, that is, to GMT.
*	If the utc value is (time_t) -1, then the conversion will fail,
*	all other values will succeed.
*
*	@param	utc		The unix time to convert
*	@param	tm		Pointer to the \p tm structure where the
*					result will be stored
*	@return			The pointer to the \p tm structure if the conversion
*					was successful, NULL otherwise.
*
*	@attention
*		The C standard defines this function with the prototype <br>
*		<i> struct tm *gmtime( const time_t *timep );</i><br>
*		The function should fetch the time value from the location
*		pointed by the \p timep pointer and return a pointer to a
*		statically allocated structure, therefore it is not thread
*		safe. As a band-aid solution a new function has been defined
*		in the standard:<br>
*		<i> struct tm *gmtime_r( const time_t *timep, struct tm *tm );</i><br>
*		This implementation uses the gmtime_r() semantics but calls the
*		function gmtime(). Furthermore, instead of passing a pointer to
*		the UNIX time, the implementation passes it directly. If you want
*		a really standard-compliant function you can write one easily:
*	@code
*		struct tm *gmtime_r( const time_t *timep, struct tm *tm )
*		{
*			return gmtime( *timep, tm );
*		}
*	@endcode
*/

struct tm	*gmtime( time_t utc, struct tm *tm )
{
	tm->tm_gmtoff = 0;
	return( localtime( utc, tm ) );
}

/**
*	Converts the unix time to the broken-down time structure.
*
*	This function has the same semantics as gmtime(), with one exception.
*	The tm structure's timezone field is assumed to be valid and the
*	UTC time argument is offseted by \p tm->tm_gmtoff before conversion.
*	If the timezone offset is more than +/- 1 day or if the offseted
*	value is less than 0 or more than (time_t) -2, then the conversion
*	will fail.
*
*	@param	utc		The unix time to convert
*	@param	tm		Pointer to the \p tm structure where the
*					result will be stored
*	@return			The pointer to the \p tm structure if the conversion
*					was successful, NULL otherwise.
*
*	@attention
*		The same differences between the C standard and this implementation
*		apply as with the gmtime() function.
*/

struct tm	*localtime( time_t utc, struct tm *tm )
{
unsigned int	hms, day, tmp;

	// Check if the timezone info still makes the time representable
	// If not, we do nothing and return NULL.

	if ( utc == (time_t) -1 )

		return( (void *) 0 );

	if ( tm->tm_gmtoff < -86399 || tm->tm_gmtoff > +86399 )

		return( (void *) 0 );

	if ( ( tm->tm_gmtoff > 0 &&
		   ((time_t) -2) - utc < (time_t) tm->tm_gmtoff ) ||
		 ( tm->tm_gmtoff < 0 && utc < (time_t) -tm->tm_gmtoff ) )

		return( (void *) 0 );

	// Time is representable, let's break it down.

	utc += tm->tm_gmtoff;

	// Seconds, minutes and days are just mod and div

	DIVMOD( utc, tm->tm_sec,  60 );
	DIVMOD( utc, tm->tm_min,  60 );
	DIVMOD( utc, tm->tm_hour, 24 );

	// For the day of week we don't need the div, so we make a
	// copy of utc in hms, as DIVMOD() changes its first argument.
	// 1970 Jan 1 was a Thursday, we offset utc accordingly.

	hms  = utc;
	utc += 4;
	DIVMOD( utc, tm->tm_wday, 7 );

	// Now hms contains the number of days since the Epoch.
	// The first leapyear is 1972, so the first leapday happens
	// when the number of days is 790 (2 years + 31 (Jan) + 29 (Feb)).

	if ( hms > 790 ) {

		// Calculate the # of days since the first leapday

		day = hms - 790;
		utc = day;

		// Work out how many of them occured since, 1461 is 4 * 365 + 1

		DIVMOD( day, utc, 1461 );			//1461:4*365+1

		// Now utc contains the number of days sice the last leapday and
		// day contains the number of leapdays that have completely elapsed.
		// Note that utc is 0 iff the current day is a leapday.
		// Get rid of the leapdays from hms. The extra 1 is there because
		// we start with a leapday (1972-02-29).

		day = hms - day - 1;
	}
	else {

		utc = 1;
		day = hms;
	}

	// Now day contains the number of days but all leapdays (except the
	// current one if we are on 29th Feb) removed and utc is 0 iff the
	// current day is a leapday. Calculate the year and the day of the
	// year.

	DIVMOD( day, hms, 365 );
	tm->tm_year = day + 70;
	tm->tm_yday = hms;

	// Now day contains the year since 1970 and hms is the day of the year,
	// starting with 0. We know that utc is 0 iff we're on a leapday.

	if ( ! utc ) {

		tm->tm_mon  = 1;
		tm->tm_mday = 29;
	}
	else {

		// We're not on the 29th of February. Since the conversion table
		// assumes a 28-day long February, if the current year is a leapyear
		// and the number of days in the year is greater than 60 (i.e. 29th
		// of February) we have to deduce the leapday from the daycount.

		if ( ( day & 3 ) == 2 && hms > 59 ) hms--;
	}

	// Now just work out the month and the day.

	for ( day = 0 ; hms >= dim[ day ] ; day++ ) hms -= dim[ day ];
	tm->tm_mon  = day;
	tm->tm_mday = hms + 1;
	return( tm );
}

/** @} */

