<?php
//-----------------------------------------------------------------------------
// libLocale:	LTDT - LibTools DateTime
// File:		./classes/LTDT.php
// Version:		1.0, $Revision: 1.1 $
// Author:		V. Puttrich, MindArray
// Copyright:	© 2007 V. Puttrich, MindArray. All rights reserved.
//-----------------------------------------------------------------------------
// $Id: bug209768.php,v 1.1 2007/11/18 13:26:13 rganor Exp $
//-----------------------------------------------------------------------------


class LTDT
{
	// Calendar Types (CAL_)
	const CAL_GREGORIAN		= 1;
	const CAL_JULIAN		= 2;
	const CAL_HEBREW		= 3;
	const CAL_ISLAMIC		= 4;
	const CAL_BUDDHIST		= 5;
	const CAL_INDIAN		= 6;
	const CAL_ETHIOPIAN		= 7;
	const CAL_PERSIAN		= 8;
	const CAL_CHINESE		= 9;
	const CAL_JAPANESE		= 10;

	// Calendar mapping (type to string)
	static $CALENDARS = array(
		1  => 'gregorian',
		2  => 'julian',
		3  => 'hebrew',
		4  => 'islamic',
		5  => 'buddhist',
		6  => 'indian',
		7  => 'ethiopic',
		8  => 'persian',
		9  => 'chinese',
		10 => 'japanese'
	);

	// The following constants are for use with the methods below

	// Return Types (RT_) and Format/Output Modifiers (MF_)
	const RT_STRING	= 0x0001;	// Return a specific, formatted string
	const RT_VALUE	= 0x0002;	// Return a specific value instead of an array
	const RT_NUM	= 0x0010;	// Return an indexed array
	const RT_ASSOC	= 0x0020;	// Return an associative array
	const RT_BOTH	= 0x0030;	// Return both, an indexed and associative array
	const MF_UTC	= 0x0100;	// Use UTC as timezone instead of the local TZ
	const MF_ISO	= 0x0200;	// If RT_STRING, modifies returned string: blank to T, +0000 to Z
	const MF_UTO	= 0x0400;	// If RT_STRING, append UTC offset to time
	const MF_TMS	= 0x1000;	// If RT_STRING, append milliseconds to time
	const MF_DYDW	= 0x2000;	// If RT_STRING, modifies the date: YYYY-MM-DD -> YYYY-MM or YYYY-Www-DDD -> YYYY-Www
	const MF_THM	= 0x4000;	// If RT_STRING, no seconds and ignore MF_TMS
	const MF_TH		= 0x8000;	// If RT_STRING, hours only and ignore MF_TMS


	//-------------------------------------------------------------------------
	// Date/Time Methods based on Unix Timestamp and Microtime
	//-------------------------------------------------------------------------

	/**
	 * LTDT::getDate()
	 *
	 * Returns an ISO-8601 conformant date as a string or an array, depending on
	 * which flag is set. The default is to return an indexed array. The date is
	 * based on the local timezone unless the MF_UTC flag is set.
	 *
	 * Definition:
	 * mixed getDate([{int time | float microtime}[, int flags]])
	 *
	 * Parameters:
	 * All parameters of the method are optional from left to right.
	 * - time: A Unix timestamp as it is returned by 'time()'.
	 * - microtime: A float as it is returned by 'microtime(true)'.
	 * - flags: Possible flags are: RT_STRING, RT_NUM, RT_ASSOC, RT_BOTH,
	 *          MF_DYDW and MF_UTC. RT_NUM is set by default.
	 *
	 * Return value:
	 * Depending on the return type, the following will be returned:
	 * RT_STRING: "YYYY-MM-DD" or, combined with MF_DYDW "YYYY-MM.
	 * RT_NUM:
	 * array(
	 *     [0]   => int year,
	 *     [1]   => int month,
	 *     [2]   => int day,
	 *     [3]   => bool isLeapYear,
	 *     [4]   => string UTCOffset
	 * ).
	 * RT_ASSOC:
	 * array(
	 *     'Y'   => int year,
	 *     'M'   => int month,
	 *     'D'   => int day,
	 *     'ly'  => bool isLeapYear,
	 *     'uto' => string UTCOffset
	 * ).
	 * RT_BOTH is a combination of RT_NUM and RT_ASSOC.
	 *
	 * @params mixed 	[{int time | float microtime}]
	 * @params integer 	[int flags]
	 * @return mixed	{string | array}
	 * @access public static
	 */
	public static function getDate($mTime = null, $nFlags = 0)
	{
		if($mTime === null)
			$mTime = time();
		if(is_float($mTime))
			$mTime = ceil($mTime);

		if(($nFlags & 0x00FF) == 0x0)
			$nFlags |= self::RT_NUM;

		$tm = ($nFlags & self::MF_UTC)? gmdate("Y-m-d-L-O", $mTime): date("Y-m-d-L-O", $mTime);

		if(($nFlags & 0x00FF) == self::RT_STRING){
			$len = ($nFlags & self::MF_DYDW)? 7: 10;
			return substr($tm, 0, $len);
		}

		$atm = explode('-', $tm);
		$atm[0] = (int) $atm[0];
		$atm[1] = (int) $atm[1];
		$atm[2] = (int) $atm[2];
		$atm[3] = (bool) $atm[3];

		if(($nFlags & 0x00FF) == self::RT_BOTH){
			return array_merge($atm, array(
				'Y'   => $atm[0],
				'M'   => $atm[1],
				'D'   => $atm[2],
				'ly'  => $atm[3],
				'uto' => $atm[4]
			));
		}
		else if(($nFlags & 0x00FF) == self::RT_ASSOC){
			return array(
				'Y'   => $atm[0],
				'M'   => $atm[1],
				'D'   => $atm[2],
				'ly'  => $atm[3],
				'uto' => $atm[4]
			);
		}

		return $atm;
	}

	/**
	 * LTDT::getTime()
	 *
	 * Returns an ISO-8601 conformant time as a string or an array, depending on
	 * which flags are set. The default is to return an indexed array. The time
	 * is based on the local timezone unless the MF_UTC flag is set.
	 *
	 * Definition:
	 * mixed getTime([{int time | float microtime}[, int flags]])
	 *
	 * Parameters:
	 * All parameters of the method are optional from left to right.
	 * - time: A Unix timestamp as it is returned by 'time()'.
	 * - microtime: A float as it is returned by 'microtime(true)'.
	 * - flags: Possible flags are: RT_STRING, RT_NUM, RT_ASSOC, RT_BOTH,
	 * 			MF_TMS, MF_TH, MF_THM, MF_UTO and MF_UTC. RT_NUM is the
	 *          default.
	 *
	 * Return value:
	 * Depending on the return type, the following will be returned:
	 * RT_STRING: "hh:mm:ss", combined with MF_TH "hh", with MF_THM "hh:mm",
	 * MF_TMS ".nnn" is appended and, with MF_UTO, it is "+hhmm".
	 * RT_NUM:
	 * array(
	 *     [0]   => int hours,
	 *     [1]   => int minutes,
	 *     [2]   => int seconds,
	 *     [3]   => float milliseconds,
	 *     [4]   => string UTCOffset
	 * ).
	 * RT_ASSOC:
	 * array(
	 *     'h'   => int hours,
	 *     'm'   => int minutes,
	 *     's'   => int seconds,
	 *     'f'   => int milliseconds,
	 *     'uto' => string UTCOffset
	 * ).
	 * RT_BOTH a combination of RT_NUM and RT_ASSOC
	 *
	 * @params mixed 	[{int time | float microtime}]
	 * @params integer 	[int flags]
	 * @return mixed	{string | array}
	 * @access public static
	 */
	public static function getTime($mTime = null, $nFlags = 0)
	{
		$ftm = $mTime;
		if($ftm === null)
			$ftm = microtime(true);
		if(!is_float($ftm))
			$ftm = (float) $ftm + 0.0;

		$tm = floor($ftm);

		if(($nFlags & 0x00FF) == 0x0)
			$nFlags |= self::RT_NUM;

		$atm = explode('-', ($nFlags & self::MF_UTC)? gmdate("H-i-s--O", $tm): date("H-i-s--O", $tm));
		$atm[0] = (int) $atm[0];
		$atm[1] = (int) $atm[1];
		$atm[2] = (int) $atm[2];
		$atm[3] = round($ftm - $tm, 3);

		if(($nFlags & 0x00FF) == self::RT_STRING){
			$ms = ($nFlags & self::MF_TMS)? sprintf(".%03d", floor($atm[3] * 1000)): '';
			$tz = ($nFlags & self::MF_UTO)? (($nFlags & self::MF_UTC) && ($nFlags & self::MF_ISO))? 'Z': $atm[4]: '';
			$ft = '%02d:%02d:%02d%s%s';
			if($nFlags & self::MF_THM)
				$ft = '%1$02d:%2$02d%5$s';
			else if($nFlags & self::MF_TH)
				$ft = '%1$02d%5$s';
			return sprintf($ft, $atm[0], $atm[1], $atm[2], $ms, $tz);
		}
		else if(($nFlags & 0x00FF) == self::RT_BOTH){
			return array_merge($atm, array(
				'h'   => $atm[0],
				'm'   => $atm[1],
				's'   => $atm[2],
				'f'   => $atm[3],
				'uto' => $atm[4]
			));
		}
		else if(($nFlags & 0x00FF) == self::RT_ASSOC){
			return array(
				'h'   => $atm[0],
				'm'   => $atm[1],
				's'   => $atm[2],
				'f'   => $atm[3],
				'uto' => $atm[4]
			);
		}

		return $atm;
	}

	/**
	 * LTDT::getDateTime()
	 *
	 * Returns an ISO-8601 conformant datetime as a string or an array,
	 * depending on which flags are set. The default is to return an indexed
	 * array. Date and time are based on the local timezone unless the MF_UTC
	 * flag is set.
	 *
	 * Definition:
	 * mixed getDateTime([{int time | float microtime}[, int flags]])
	 *
	 * Parameters:
	 * All parameters of the method are optional from left to right.
	 * - time: A Unix timestamp as it is returned by 'time()'.
	 * - microtime: A float as it is returned by 'microtime(true)'.
	 * - flags: Possible flags are: RT_STRING, RT_NUM, RT_ASSOC, RT_BOTH,
	 *          MF_DYDW, MF_TMS, MF_TH, MF_THM, MF_ISO, MF_UTO and MF_UTC.
	 *          RT_NUM is the default.
	 *
	 * Return value:
	 * Depending on the return type, the following will be returned:
	 * RT_STRING: The default is "YYYY-MM-DD hh:mm:ss". MF_DYDW modifies the
	 * date like "YYYY-MM". MF_TH makes the time "hh", MF_THM "hh:mm".
	 * With MF_TMS ".nnn" is appended and, with MF_UTO, it is "+hhmm". MF_ISO
	 * replaces the blank between date and time by 'T' and in combination with
	 * MF_UTC '+0000' is replaced by 'Z'.
	 * RT_NUM:
	 * array(
	 *     [0]   => int year,
	 *     [1]   => int month,
	 *     [2]   => int day,
	 *     [3]   => int hours,
	 *     [4]   => int minutes,
	 *     [5]   => int seconds,
	 *     [6]   => float milliseconds,
	 *     [7]   => bool isLeapYear,
	 *     [8]   => string UTCOffset,
	 * ).
	 * RT_ASSOC:
	 * array(
	 *     'Y'   => int year,
	 *     'M'   => int month,
	 *     'D'   => int day,
	 *     'h'   => int hours,
	 *     'm'   => int minutes,
	 *     's'   => int seconds,
	 *     'f'   => int milliseconds,
	 *     'ly'  => bool isLeapYear,
	 *     'uto' => string UTCOffset
	 * ).
	 * RT_BOTH a combination of RT_NUM and RT_ASSOC
	 *
	 * @params mixed 	[{int time | float microtime}]
	 * @params integer 	[int flags]
	 * @return mixed	{string | array}
	 * @access public static
	 */
	public static function getDateTime($mTime = null, $nFlags = 0)
	{
		$ftm = $mTime;
		if($ftm === null)
			$ftm = microtime(true);
		if(!is_float($ftm))
			$ftm = (float) $ftm + 0.0;

		$tm = floor($ftm);

		if(($nFlags & 0x00FF) == 0x0)
			$nFlags |= self::RT_NUM;

		$atm = explode('-', ($nFlags & self::MF_UTC)? gmdate("Y-m-d-H-i-s--L-O", $tm): date("Y-m-d-H-i-s--L-O", $tm));
		$atm[0] = (int) $atm[0];
		$atm[1] = (int) $atm[1];
		$atm[2] = (int) $atm[2];
		$atm[3] = (int) $atm[3];
		$atm[4] = (int) $atm[4];
		$atm[5] = (int) $atm[5];
		$atm[6] = round($ftm - $tm, 3);
		$atm[7] = (bool) $atm[7];

		if(($nFlags & 0x00FF) == self::RT_STRING){
			$ms = ($nFlags & self::MF_TMS)? sprintf(".%03d", floor($atm[6] * 1000)): '';
			$tz = ($nFlags & self::MF_UTO)? (($nFlags & self::MF_UTC) && ($nFlags & self::MF_ISO))? 'Z': $atm[8]: '';
			$t  = ($nFlags & self::MF_ISO)? 'T': ' ';
			$fd = ($nFlags & self::MF_DYDW)? '%1$04d-%2$02d': '%1$04d-%2$02d-%3$02d';
			$ft = '%5$02d:%6$02d:%7$02d%8$s%9$s';
			if($nFlags & self::MF_THM)
				$ft = '%5$02d:%6$02d%9$s';
			else if($nFlags & self::MF_TH)
				$ft = '%5$02d%9$s';

			return sprintf("{$fd}%4\$s{$ft}", $atm[0], $atm[1], $atm[2], $t, $atm[3], $atm[4], $atm[5], $ms, $tz);
		}
		else if(($nFlags & 0x00FF) == self::RT_BOTH){
			return array_merge($atm, array(
				'Y'   => $atm[0],
				'M'   => $atm[1],
				'D'   => $atm[2],
				'h'   => $atm[3],
				'm'   => $atm[4],
				's'   => $atm[5],
				'f'   => $atm[6],
				'ly'  => $atm[7],
				'uto' => $atm[8]
			));
		}
		else if(($nFlags & 0x00FF) == self::RT_ASSOC){
			return array(
				'Y'   => $atm[0],
				'M'   => $atm[1],
				'D'   => $atm[2],
				'h'   => $atm[3],
				'm'   => $atm[4],
				's'   => $atm[5],
				'f'   => $atm[6],
				'ly'  => $atm[7],
				'uto' => $atm[8]
			);
		}

		return $atm;
	}

	/**
	 * LTDT::getISODaysDate()
	 *
	 * Returns an ISO-8601 days date as a string or an array, depending on
	 * which flag is set. The default is to return an indexed array. The date is
	 * based on the local timezone unless the MF_UTC flag is set.
	 *
	 * Definition:
	 * mixed getISODaysDate([{int time | float microtime}[, int flags]])
	 *
	 * Parameters:
	 * All parameters of the method are optional from left to right.
	 * - time: A Unix timestamp as it is returned by 'time()'.
	 * - microtime: A float as it is returned by 'microtime(true)'.
	 * - flags: Possible flags are: RT_STRING, RT_NUM, RT_ASSOC, RT_BOTH and
	 *          MF_UTC. MT_NUM is the default.
	 *
	 * Return value:
	 * Depending on the return type, the following will be returned:
	 * RT_STRING: "YYYY-DDD".
	 * RT_NUM:
	 * array(
	 *     [0]   => int year,
	 *     [1]   => int dayOfYear,
	 *     [2]   => bool isLeapYear,
	 *     [3]   => string UTCOffset
	 * ).
	 * RT_ASSOC:
	 * array(
	 *     'Y'   => int year,
	 *     'doy' => int dayOfYear,
	 *     'ly'  => bool isLeapYear,
	 *     'uto' => string UTCOffset
	 * ).
	 * RT_BOTH is a combination of RT_NUM and RT_ASSOC.
	 *
	 * @params mixed 	[{int time | float microtime}]
	 * @params integer 	[int flags]
	 * @return mixed	{string | array}
	 * @access public static
	 */
	public static function getISODaysDate($mTime = null, $nFlags = 0)
	{
		if($mTime === null)
			$mTime = time();
		if(is_float($mTime))
			$mTime = ceil($mTime);

		if(($nFlags & 0x00FF) == 0x0)
			$nFlags |= self::RT_NUM;

		$atm = explode('-', ($nFlags & self::MF_UTC)? gmdate("Y-z-L-O", $mTime): date("Y-z-L-O", $mTime));
		$atm[1]++;
		$atm[2] = (bool) $atm[2];

		if(($nFlags & 0x00FF) == self::RT_STRING){
			return sprintf("%04d-%03d", $atm[0], $atm[1]);
		}
		else if(($nFlags & 0x00FF) == self::RT_BOTH){
			return array_merge($atm, array(
				'Y'   => $atm[0],
				'doy' => $atm[1],
				'ly'  => $atm[2],
				'uto' => $atm[3]
			));
		}
		else if(($nFlags & 0x00FF) == self::RT_ASSOC){
			return array(
				'Y'   => $atm[0],
				'doy' => $atm[1],
				'ly'  => $atm[2],
				'uto' => $atm[3]
			);
		}

		return $atm;
	}

	/**
	 * LTDT::getISOWeekDate()
	 *
	 * Returns an ISO-8601 days date as a string or an array, depending on
	 * which flag is set. The default is to return an indexed array. The date is
	 * based on the local timezone unless the MF_UTC flag is set.
	 *
	 * Definition:
	 * mixed getISODaysDate([{int time | float microtime}[, int flags]])
	 *
	 * Parameters:
	 * All parameters of the method are optional from left to right.
	 * - time: A Unix timestamp as it is returned by 'time()'.
	 * - microtime: A float as it is returned by 'microtime(true)'.
	 * - flags: Possible flags are: RT_STRING, RT_NUM, RT_ASSOC, RT_BOTH and
	 *          MF_UTC. The latter can be combined with ONE of the others.
	 *          MF_UTC is used to change the timezone from local to UTC.
	 *
	 * Return value:
	 * Depending on the return type, the following will be returned:
	 * RT_STRING: "YYYY-Www-D" or, combined with MF_DYDW "YYYY-Www.
	 * RT_NUM
	 * array(
	 *     [0]   => int year,
	 *     [1]   => int week,
	 *     [3]   => int dayOfWeek,
	 *     [4]   => bool isLeapYear,
	 *     [5]   => string UTCOffset
	 * ).
	 * RT_ASSOC
	 * array(
	 *     'Y'   => int year,
	 *     'W'   => int week,
	 *     'dow' => int dayOfWeek,
	 *     'ly'  => bool isLeapYear,
	 *     'uto' => string UTCOffset
	 * ).
	 * RT_BOTH is a combination of RT_NUM and RT_ASSOC.
	 *
	 * @params mixed 	[{int time | float microtime}]
	 * @params integer 	[int flags]
	 * @return mixed	{string | array}
	 * @access public static
	 */
	public static function getISOWeekDate($mTime = null, $nFlags = 0)
	{
		if($mTime === null)
			$mTime = time();
		if(is_float($mTime))
			$mTime = ceil($mTime);

		if(($nFlags & 0x00FF) == 0x0)
			$nFlags |= self::RT_NUM;

		$atm = explode('-', ($nFlags & self::MF_UTC)? gmdate("Y-W-N-L-O", $mTime): date("Y-W-N-L-O", $mTime));
		$atm[3] = (bool) $atm[3];

		if(($nFlags & 0x00FF) == self::RT_STRING){
			$f = ($nFlags & self::MF_DYDW)? "%04d-W%02d": "%04d-W%02d-%d";
			return sprintf($f, $atm[0], $atm[1], $atm[2]);
		}
		else if(($nFlags & 0x00FF) == self::RT_BOTH){
			return array_merge($atm, array(
				'Y'   => $atm[0],
				'W'   => $atm[1],
				'dow' => $atm[2],
				'ly'  => $atm[3],
				'uto' => $atm[4]
			));
		}
		else if(($nFlags & 0x00FF) == self::RT_ASSOC){
			return array(
				'Y'   => $atm[0],
				'W'   => $atm[1],
				'dow' => $atm[2],
				'ly'  => $atm[3],
				'uto' => $atm[4]
			);
		}

		return $atm;
	}

	/**
	 * LTDT::getRuntimeInfo
	 *
	 * Definition:
	 * array getRuntimeInfo({string microtime | float microtime}[, {string microtime | float microtime}[, int flags]])
	 *
	 * Parameters:
	 * - microtime: A float as it is returned by 'microtime(true)', or a string from 'microtime()'.
	 * - flags: Possible flags are: RT_STRING, RT_NUM, RT_ASSOC, RT_BOTH and MF_UTC. 
	 *          RT_STRING is the default.
	 *
	 * Return value:
	 * Depending on the return type, the following will be returned:
	 * RT_STRING: "DAYS:hh:mm:ss.nnnnn".
	 * RT_NUM:
	 * array(
	 *     [0] => int days,
	 *     [1] => int hours,
	 *     [2] => int minutes,
	 *     [3] => float seconds.mseconds,
	 *     [4] => array(int year, int month, int day, int hours, int minutes, int seconds, float mseconds, bool isLeapYear, string UTC-Offset),
	 *     [5] => array(int year, int month, int day, int hours, int minutes, int seconds, float mseconds, bool isLeapYear, string UTC-Offset)
	 * ).
	 * RT_ASSOC:
	 * array(
	 *     'd' => int days,
	 *     'h' => int hours,
	 *     'm' => int minutes,
	 *     's' => float seconds.mseconds,
	 *     'start'   => array(
	 *         'Y'   => int year, 
	 *         'M'   => int month, 
	 *         'D'   => int day, 
	 *         'h'   => int hours, 
	 *         'm'   => int minutes, 
	 *         's'   => int seconds, 
	 *         'f'   => float mseconds, 
	 *         'ly'  => bool isLeapYear, 
	 *         'uto' => string UTC-Offset
	 *     ),
	 *     'end'     => array(
	 *         'Y'   => int year, 
	 *         'M'   => int month, 
	 *         'D'   => int day, 
	 *         'h'   => int hours, 
	 *         'm'   => int minutes, 
	 *         's'   => int seconds, 
	 *         'f'   => float mseconds, 
	 *         'ly'  => bool isLeapYear, 
	 *         'uto' => string UTC-Offset
	 *     )
	 * ).
	 * RT_BOTH is a combination of RT_NUM and RT_ASSOC.
	 *  
	 * @param mixed   {string microtime | float microtime}
	 * @param mixed   [{string microtime | float microtime}]
	 * @param integer [int flags]
	 * @return mixed  {string | array}
	 * @access public static
	*/
	public static function getRuntimeInfo($mStart, $mEnd = null, $nFlags = 0)
	{
		$fS = $mStart;
		if(is_string($mStart)){
			list($ms, $s) = explode(' ', $mStart);
			$fS = (float) $s + (float) $ms;
		}

		$fE = ($mEnd === null)? microtime(true): $mEnd;
		if(is_string($mEnd)){
			list($ms, $s) = explode(' ', $mEnd);
			$fE = (float) $s + (float) $ms;
		}

		$fD = $fE - $fS;
		$nD = floor($fD);
		$d = floor($fD / 86400);
		$t = $fD - $d * 86400;
		$h = floor($t / 3600);
		$t -= $h * 3600;
		$m = floor($t / 60);
		$s = $t - $m * 60;
		$f = $fD - $nD;

		if(($nFlags & 0x00FF) == 0x0)
			$nFlags |= self::RT_STRING;
		
		if(($nFlags & 0x00FF) == self::RT_STRING)
			return sprintf("%d:%02d:%02d:%02.05F", $d, $h, $m, (float) $s + $f);

		$atmS = ($nFlags & self::MF_UTC)? gmdate("Y-m-d-H-i-s--L-O", floor($fS)): date("Y-m-d-H-i-s--L-O", floor($fS));
		$atmS = explode('-', $atmS);
		$atmS[6] = $fS - (float) floor($fS);
		$atmS[7] = (bool) $atmS[7];
		$atmE = ($nFlags & self::MF_UTC)? gmdate("Y-m-d-H-i-s--L-O", floor($fE)): date("Y-m-d-H-i-s--L-O", floor($fE));
		$atmE = explode('-', $atmE);
		$atmE[6] = $fE - (float) floor($fE);
		$atmE[7] = (bool) $atmE[7];
		
		$atm = array(
			$d, $h, $m, (float) $s + $f,
			$atmS, $atmE
		);
		$atma = array( 
			'd'	=> $d, 'h' => $h, 'm' => $m, 's' => (float) $s + $f,
			'start'	=> array(
				'Y' => $atmS[0], 'M' => $atmS[1], 'D' => $atmS[2],
				'h' => $atmS[3], 'm' => $atmS[4], 's' => $atmS[5], 'f' => $atmS[6],
				'ly' => $atmS[7], 'uto' => $atmS[8]
			),
			'end'	=> array(
				'Y' => $atmE[0], 'M' => $atmE[1], 'D' => $atmE[2],
				'h' => $atmE[3], 'm' => $atmE[4], 's' => $atmE[5], 'f' => $atmE[6],
				'ly' => $atmE[7], 'uto' => $atmE[8]
			)
		);
			
		if(($nFlags & 0x00FF) == self::RT_BOTH)
			return array_merge($atm, $atma);
		else if(($nFlags & 0x00FF) == self::RT_ASSOC)
			return $atma;

		return $atm;
	}
	

	//-------------------------------------------------------------------------
	// Date/Time Conversion based on the Gregorian Calendar
	//-------------------------------------------------------------------------
	
	/**
	 * LTDT::getJDN()
	 *
	 * Returns Julian Day for the given date.
	 *
	 * Definition:
	 * int getJDN(int year, int month, int day)
	 * int getJDN(array(int year, int month, int day))
	 *
	 * Parameters:
	 * - array contains the elements year, month, day.
	 * - year is a year between -4800 and 99999.
	 * - month is a numeric representation of the month between 1 and 12.
	 * - day is the day of the month.
	 *
	 * Return value:
	 * The return value is the Julian Day for the given date as an integer.
	 *
	 * @param  mixed 	{array date | int year}
	 * @param  mixed 	[int month]
	 * @param  integer 	[int day]
	 * @return integer  JDN
	 * @access public static
	 */
	public static function getJDN()
	{
		if(func_num_args() > 1){
			@list($y, $m, $d) = func_get_args();
		}
		else {
			$p = func_get_arg(0);
			if(is_array($p))
				@list($y, $m, $d) = $p;
			else
				$y = $p;
		}

		if(!$m)
			$m = 1;
		if(!$d)
			$d = 1;

		$a = floor((14 - $m) / 12);
		$y = $y + 4800 - $a;
		$m = $m + 12 * $a - 3;

		$JDN = $d + floor((153 * $m + 2) / 5) + 365 * $y + floor($y / 4) - floor($y / 100) + floor($y / 400) - 32045;

		return $JDN;
	}

	/**
	 * LTDT::getJD()
	 *
	 * Returns Julian Date for the given date and time.
	 *
	 * Definition:
	 * int getJD(int year[, int month[, int day[, int hours[, int minutes[, int seconds[, float milliseconds]]]]]])
	 * int getJD(array(int year[, int month[, int day[, int hours[, int minutes[, int seconds[, float milliseconds]]]]]]))
	 *
	 * Parameters:
	 * - array contains the elements year, month, day, hours, minutes, seconds and milliseconds.
	 * - year is a year between -4800 and 99999.
	 * - month is a numeric representation of the month between 1 and 12.
	 * - day is the day of the month.
	 * - hours are the hours of the day between 0 and 23.
	 * - minutes are the minutes of the hour between 0 and 59.
	 * - seconds are the seconds of the minute between 0 and 59.
	 * - milliseconds are the fraction of the second as float from 0.0 to 0.999
	 *
	 * Return value:
	 * The return value is the Julian Date for the given date as a float.
	 *
	 * @param  mixed 	{array date | int year}
	 * @param  integer 	[int month]
	 * @param  integer 	[int day]
	 * @param  integer 	[int hours]
	 * @param  integer 	[int minutes]
	 * @param  integer 	[int seconds]
	 * @param  float 	[float milliseconds]
	 * @return float    JD
	 * @access public static
	 */
	public static function getJD()
	{
		if(func_num_args() > 1){
			@list($Y, $M, $D, $h, $m, $s, $f) = func_get_args();
		}
		else {
			$p = func_get_arg(0);
			if(is_array($p))
				@list($Y, $M, $D, $h, $m, $s, $f) = $p;
			else
				$Y = $p;
		}

		if(!$M)
			$M = 1;
		if(!$D)
			$D = 1;
		if(!$h)
			$h = 0;
		if(!$m)
			$m = 0;
		if(!$s)
			$s = 0;
		if(!$f)
			$f = 0.0;

		$JDN = self::getJDN($Y, $M, $D);
		$JD = $JDN + (($h - 12) / 24) + ($m / 1440) + (round($s + $f) / 86400);

		return $JD;
	}

	/**
	 * LTDT::getMJD()
	 *
	 * Returns the Modified Julian Day for the given date.
	 *
	 * Definition:
	 * int getMJD(int year[, int month[, int day]])
	 * int getMJD(array(int year[, int month[, int day]]))
	 *
	 * Parameters:
	 * - array contains the elements year, month, and day.
	 * - year is a year between -4800 and 99999.
	 * - month is a numeric representation of the month between 1 and 12.
	 * - day is the day of the month.
	 *
	 * Return value:
	 * The return value is the Modified Julian Day for the given date as a float.
	 *
	 * @param  mixed 	{array date | int year}
	 * @param  integer 	[int month]
	 * @param  integer 	[int day]
	 * @return float    MJD
	 * @access public static
	 */
	public static function getMJD()
	{
		@list($args) = func_get_args();
		$MJD = self::getJDN($args);
		$MJD -= 2400000.5;

		return $MJD;
	}

	/**
	 * LTDT::getJDToDateTime()
	 *
	 * Returns an array containing the year, month, day, hours, minutes, seconds,
	 * milliseconds and wether it's a leap year or not.
	 *
	 * Definition:
	 * mixed getJDToDateTime(float JD[, int flags]])
	 *
	 * Parameters:
	 * - JD: A Julian Date or Julian Day.
	 * - flags: Possible flags are: RT_NUM, RT_ASSOC, RT_BOTH. RT_NUM is the
	 *          default.
	 *
	 * Return value:
	 * Depending on the return type, the following will be returned:
	 * RT_NUM:
	 * array(
	 *     [0]   => int year,
	 *     [1]   => int month,
	 *     [2]   => int day,
	 *     [3]   => int hours,
	 *     [4]   => int minutes,
	 *     [5]   => int seconds,
	 *     [6]   => float milliseconds,
	 *     [7]   => bool isLeapYear
	 * ).
	 * RT_ASSOC:
	 * array(
	 *     'Y'   => int year,
	 *     'M'   => int month,
	 *     'D'   => int day,
	 *     'h'   => int hours,
	 *     'm'   => int minutes,
	 *     's'   => int seconds,
	 *     'f'   => int milliseconds,
	 *     'ly'  => bool isLeapYear
	 * ).
	 * RT_BOTH a combination of RT_NUM and RT_ASSOC
	 *
	 * @params float 	JD
	 * @params integer 	[flags]
	 * @return mixec    {array | string}
	 * @access public static
	 */
	public static function getJDToDateTime($JD, $nFlags = 0)
	{
		if(($nFlags & 0x00FF) == 0x0)
			$nFlags |= self::RT_NUM;

		if(($nFlags & 0x00FF) == self::RT_STRING){
			$tm = self::getJDToTime($JD, $nFlags);
			$dt = self::getJDToDate($JD, $nFlags);

			return "{$dt} {$tm}";
		}

		$atm = self::getJDToTime($JD, self::RT_NUM);
		$adt = self::getJDToDate($JD, self::RT_NUM);
		$ly  = array_pop($adt);
		$adt = array_merge($adt, $atm);
		$adt[] = $ly;

		if($nFlags & self::RT_ASSOC)
			$adta = array(
				'Y' => $adt[0], 'M' => $adt[1], 'D' => $adt[2],
				'h' => $adt[3], 'm' => $adt[4], 's' => $adt[5], 'f' => $adt[6],
				'ly' => $adt[7]
			);

		if(($nFlags & 0x00FF) == self::RT_BOTH)
			return array_merge($adt, $adta);
		else if(($nFlags & 0x00FF) == self::RT_ASSOC)
			return $adta;

		return $adt;
	}

	/**
	 * LTDT::getJDToDate()
	 *
	 * Returns an ISO-8601 formatted string, or an array containing the year,
	 * the month, the day and wether it's a leap year or not, depending on which
	 * flags are set.
	 *
	 * Definition:
	 * mixed getJDToDate(float JD[, int flags])
	 *
	 * Parameters:
	 * - JD: A Julian Date or Julian Day.
	 * - flags: Possible flags are: RT_STRING, RT_NUM, RT_ASSOC, RT_BOTH and
	 *                              MF_DYDW.
	 *
	 * Return value:
	 * Depending on the return type, the following will be returned:
	 * RT_STRING: "YYYY-MM-DD" or, combined with MF_DYDW, "YYYY-MM".
	 * RT_NUM:
	 * array(
	 *     [0]  => int year,
	 *     [1]  => int month,
	 *     [2]  => int day,
	 *     [3]  => bool isLeapYear
	 * ).
	 * RT_ASSOC
	 * array(
	 *     'Y'  => int year,
	 *     'W'  => int month,
	 *     'D'  => int day,
	 *     'ly' => bool isLeapYear
	 * ).
	 * RT_BOTH is a combination of RT_NUM and RT_ASSOC.
	 *
	 * @param  float 	JD
	 * @param  integer 	[int flags]
	 * @return mixed	{array | string}
	 * @access public static
	 */
	public static function getJDToDate($JD, $nFlags = 0)
	{
		if(($nFlags & 0x00FF) == 0x0)
			$nFlags |= self::RT_NUM;

		$JDN = floor($JD - 0.5 + 1);

		$j  = $JDN + 32044;
		$g  = floor($j / 146097);
		$dg = floor($j % 146097);
		$c  = floor((floor($dg / 36524) + 1) * 3 / 4);
		$dc = $dg - $c * 36524;
		$b  = floor($dc / 1461);
		$db = floor($dc % 1461);
		$a  = floor((floor($db / 365) + 1) * 3 / 4);
		$da = $db - $a * 365;
		$y  = $g * 400 + $c * 100 + $b * 4 + $a;
		$m  = floor(($da * 5 + 308) / 153) - 2;
		$d  = $da - floor(($m + 4) * 153 / 5) + 122;
		$Y  = $y - 4800 + floor(($m + 2) / 12);
		$M  = floor(($m + 2) % 12) + 1;
		$D  = $d + 1;

		if(($nFlags & 0x00FF) == self::RT_STRING)
			return sprintf(($nFlags & self::MF_DYDW)? "%04d-%02d": "%04d-%02d-%02d", $Y, $M, $D);

		$adt = array($Y, $M, $D, self::isLeapYear($Y));

		if(($nFlags & 0x00FF) == self::RT_BOTH)
			return array_merge($adt, array('Y' => $adt[0], 'M' => $adt[1], 'D' => $adt[2], 'ly' => $adt[3]));
		else if(($nFlags & 0x00FF) == self::RT_ASSOC)
			return array('Y' => $adt[0], 'M' => $adt[1], 'D' => $adt[2], 'ly' => $adt[3]);

		return $adt;
	}

	/**
	 * LTDT::getJDToTime()
	 *
	 * Returns the time of the given Julian Date as an ISO-8601 formatted string,
	 * or an array containing the hours, minutes, seconds and milliseconds,
	 * depending on the flags that are set.
	 *
	 * Definition:
	 * array getJDToTime(float JD[, int flags]])
	 *
	 * Parameters:
	 * - JD: A Julian Date (not Julian Day!).
	 * - flags: Possible flags are: RT_STRING, RT_NUM, RT_ASSOC, RT_BOTH,
	 * 			MF_TMS, MF_TH and MF_THM. RT_NUM is the default.
	 *
	 * Return value:
	 * Depending on the return type, the following will be returned:
	 * RT_STRING: "hh:mm:ss", combined with MF_TH "hh", with MF_THM "hh:mm" and
	 * with MF_TMS ".nnn" is appended.
	 * RT_NUM:
	 * array(
	 *     [0]   => int hours,
	 *     [1]   => int minutes,
	 *     [2]   => int seconds,
	 *     [3]   => float milliseconds
	 * ).
	 * RT_ASSOC:
	 * array(
	 *     'h'   => int hours,
	 *     'm'   => int minutes,
	 *     's'   => int seconds,
	 *     'f'   => int milliseconds
	 * ).
	 * RT_BOTH a combination of RT_NUM and RT_ASSOC
	 *
	 * @params float 	JD
	 * @params integer 	[flags]
	 * @return mixed    {array | string}
	 * @access public static
	 */
	public static function getJDToTime($JD, $nFlags = 0)
	{
		if(($nFlags & 0x00FF) == 0x0)
			$nFlags |= self::RT_NUM;

		$t = $JD + 0.5 - floor($JD + 0.5001);
		$h = floor(24 * $t);
		$m = floor(1440 * ($t - $h / 24));
		$f = round(86400 * ($t - $h / 24 - $m / 1440), 3);
		$s = round($f);
		$f -= $s;

		if(($nFlags & 0x00FF) == self::RT_STRING){
			$ms = ($nFlags & self::MF_TMS)? sprintf(".%03d", floor($f * 1000)): '';
			$ft = '%02d:%02d:%02d%s';
			if($nFlags & self::MF_THM)
				$ft = '%02d:%02d';
			else if($nFlags & self::MF_TH)
				$ft = '%02d';

			return sprintf($ft, $h, $m, $s, $ms);
		}

		$atm = array($h, $m, $s, $f);

		if(($nFlags & 0x00FF) == self::RT_BOTH)
			return array_merge($atm, array('h' => $h, 'm' => $m, 's' => $s, 'f' => $f));
		else if(($nFlags & 0x00FF) == self::RT_ASSOC)
			return array('h' => $h, 'm' => $m, 's' => $s, 'f' => $f);

		return $atm;
	}

	/**
	 * LTDT::getWeekday()
	 *
	 * Returns the 'day of the week' as an integer.
	 * 0 = Sunday to 6 = Saturday. If ISOMode is set to TRUE, 1 = Monday to
	 * 7 = Sunday.
	 *
	 * Definition:
	 * int getWeekday(int year, int month, int day[, bool ISOMode])
	 * int getWeekday(array(int year, int month, int day)[, bool ISOMode])
	 *
	 * Parameters:
	 * - array contains the elements year, month, day.
	 * - year is a year between -4800 and 99999.
	 * - month is a numeric representation of the month between 1 and 12.
	 * - day is the day of the month.
	 * - ISOMode specifies wether 0 (FALSE) or 7 (TRUE) is returned as Sunday.
	 * 1 is Monday. ISOMode defaults to FALSE.
	 *
	 * Return value:
	 * The return value is an integer, representing the day of the week.
	 * 0 = Sunday to 6 = Saturday. If ISOMode is set to TRUE, 1 = Monday to
	 * 7 = Sunday.
	 *
	 * @param  mixed 	{array date | int year}
	 * @param  mixed 	{[bool ISOMode] | int month}
	 * @param  integer 	[int day]
	 * @param  mixed 	[bool ISOMode]
	 * @return integer 	weekday (weekday -> ISOMode = FALSE: 0 - 6; ISOMode = TRUE 1 - 7)
	 * @access public static
	 */
	public static function getWeekday()
	{
		if(func_num_args() >= 3){
			@list($y, $m, $d, $bISO) = func_get_args();
		}
		else {
			@list($a, $bISO) = func_get_args();
			@list($y, $m, $d) = $a;
		}
		$bISO = (bool) $bISO;

		if($m < 3){
			$m += 12;
			$y--;
		}

		$wd = (2 + $d + floor((13 * $m - 2) / 5) + floor(5 * $y >> 2) - floor($y / 100) + floor($y / 400)) % 7;

		if($wd < 0)
			$wd *= -1;

		if($bISO && $wd == 0)
			$wd = 7;

		return $wd;
	}

 	/**
 	 * LTDT::getDateToWeek()
	 *
	 * Returns the week number for the given date, an ISO-8601 formatted string,
	 * or an array containing the year, the week number and the day of the week
	 * (1-based), depending on which flags are set. All rules of the ISO
	 * standard are applied.
	 *
	 * Definition:
	 * mixed getDateToWeek(int year, int month, int day[, int flags])
	 * mixed getDateToWeek(array(int year, int month, int day)[, int flags])
	 *
	 * Parameters:
	 * - array contains the elements year, month, day.
	 * - year is a year between -4800 and 99999.
	 * - month is a numeric representation of the month between 1 and 12.
	 * - day is the day of the month.
	 * - flags: Possible flags are: RT_STRING, TR_VALUE, RT_NUM, RT_ASSOC,
	 *          RT_BOTH and MF_DYDW.
	 *
	 * Return value:
	 * Depending on the return type, the following will be returned:
	 * RT_VALUE: the week number as an integer.
	 * RT_STRING: "YYYY-Www-D" or, combined with MF_DYDW, "YYYY-Www.
	 * RT_NUM:
	 * array(
	 *     [0]  => int year,
	 *     [1]  => int week,
	 *     [2]  => int weekday,
	 *     [3]  => bool isLeapYear
	 * ).
	 * RT_ASSOC
	 * array(
	 *     'Y'  => int year,
	 *     'W'  => int week,
	 *     'wd' => int weekday,
	 *     'ly' => bool isLeapYear
	 * ).
	 * RT_BOTH is a combination of RT_NUM and RT_ASSOC.
	 *
	 * @param  mixed 	{array date | int year}
	 * @param  mixed 	{[int flags] | int month}
	 * @param  integer 	[int day]
	 * @param  integer 	[int flags]
	 * @return mixed	{array | string | int week}
	 * @access public static
	 */
	public static function getDateToWeek()
	{
		if(func_num_args() >= 3){
			@list($y, $m, $d, $nFlags) = func_get_args();
		}
		else {
			@list($a, $nFlags) = func_get_args();
			@list($y, $m, $d) = $a;
		}

		if(($nFlags & 0x00FF) == 0x0)
			$nFlags |= self::RT_NUM;

		$wd = self::getWeekday($y, $m, $d, true);
		$thu = $d + 4 - $wd;
		if($m == 12 && $thu > 31){
			$y++;

			if(($nFlags & 0x00FF) == self::RT_VALUE)
				return 1;
			else if(($nFlags & 0x00FF) == self::RT_STRING)
				return sprintf(($nFlags & self::MF_DYDW)? "%04d-W%02d": "%04d-W%02d-%d", $y, 1, $wd);

			$adt = array($y, 1, $wd, self::isLeapYear($y));

			if(($nFlags & 0x00FF) == self::RT_BOTH)
				return array_merge($adt, array('Y' => $y, 'W' => 1, 'wd' => $wd, 'ly' => $adt[3]));
			else if(($nFlags & 0x00FF) == self::RT_ASSOC)
				return array('Y' => $y, 'W' => 1, 'wd' => $wd, 'ly' => $adt[3]);

			return $adt;
		}
		else if($m == 1 && $thu < 1){
			$y--;
			$m = 12;
			$thu += 31;
		}
		$doy = floor(275 * $m / 9) + $thu - 31;
		if($m > 2)
			$doy += self::isLeapYear($y) - 2;

		$w = floor($doy / 7) + 1;

		if(($nFlags & 0x00FF) == self::RT_VALUE)
			return $w;
		else if(($nFlags & 0x00FF) == self::RT_STRING)
			return sprintf(($nFlags & self::MF_DYDW)? "%04d-W%02d": "%04d-W%02d-%d", $y, $w, $wd);

		$adt = array($y, $w, $wd, self::isLeapYear($y));

		if(($nFlags & 0x00FF) == self::RT_BOTH)
			return array_merge($adt, array('Y' => $y, 'W' => $w, 'wd' => $wd, 'ly' => $adt[3]));
		else if(($nFlags & 0x00FF) == self::RT_ASSOC)
			return array('Y' => $y, 'W' => $w, 'wd' => $wd, 'ly' => $adt[3]);

		return $adt;
	}

	/**
	 * LTDT::getWeekToDate()
	 *
	 * Returns an ISO-8601 formatted string, or an array containing the year,
	 * the month, the day and wether it's a leap year or not, depending on which
	 * flags are set.
	 *
	 * Definition:
	 * mixed getWeekToDate(int year, int week, int weekday[, int flags])
	 * mixed getWeekToDate(array(int year, int week, int weekday)[, int flags])
	 *
	 * Parameters:
	 * - array contains the elements year, week, dayOfWeek.
	 * - year is a year between -4800 and 99999.
	 * - week is the number of weeks since the year has begun (1 to 52/53).
	 * - weekday is the 1-based day within the week; from 1, monday to 7, sunday.
	 * - flags: Possible flags are: RT_STRING, RT_NUM, RT_ASSOC, RT_BOTH and
	 *                              MF_DYDW.
	 *
	 * Return value:
	 * Depending on the return type, the following will be returned:
	 * RT_STRING: "YYYY-MM-DD" or, combined with MF_DYDW, "YYYY-MM".
	 * RT_NUM:
	 * array(
	 *     [0]  => int year,
	 *     [1]  => int month,
	 *     [2]  => int day,
	 *     [3]  => bool isLeapYear
	 * ).
	 * RT_ASSOC
	 * array(
	 *     'Y'  => int year,
	 *     'W'  => int month,
	 *     'D'  => int day,
	 *     'ly' => bool isLeapYear
	 * ).
	 * RT_BOTH is a combination of RT_NUM and RT_ASSOC.
	 *
	 * @param  mixed 	{array date | int year}
	 * @param  mixed 	{[int flags] | int week}
	 * @param  integer 	[int weekday]
	 * @param  integer 	[int flags]
	 * @return mixed	{array | string}
	 * @access public static
	 */ 
	public static function getWeekToDate()
	{
		if(func_num_args() >= 3){
			@list($y, $w, $wd, $nFlags) = func_get_args();
		}
		else {
			@list($a, $nFlags) = func_get_args();
			@list($y, $w, $wd) = $a;
		}

		if(($nFlags & 0x00FF) == 0x0)
			$nFlags |= self::RT_NUM;

		$doy = ($w - 1) * 7 + $wd;
		if($w == 1 && $doy < 4){
			$y--;
			$doy = 362 + $wd + (int) self::isLeapYear($y);
		}

		$adt = self::getDaysToDate($y, $doy);

		if(($nFlags & 0x00FF) == self::RT_STRING)
			return sprintf(($nFlags & self::MF_DYDW)? "%04d-%02d": "%04d-%02d-%02d", $adt[0], $adt[1], $adt[2]);
		else if(($nFlags & 0x00FF) == self::RT_BOTH)
			return array_merge($adt, array('Y' => $adt[0], 'M' => $adt[1], 'D' => $adt[2], 'ly' => $adt[3]));
		else if(($nFlags & 0x00FF) == self::RT_ASSOC)
			return array('Y' => $adt[0], 'M' => $adt[1], 'D' => $adt[2], 'ly' => $adt[3]);

		return $adt;
	}

	/**
	 * LTDT::getDaysOfYear()
	 *
	 * Returns the days of the year for the given date.
	 *
	 * Definition:
	 * int getDaysOfYear(int year, int month, int day)
	 * int getDaysOfYear(array(int year, int month, int day)
	 *
	 * Parameters:
	 * - array contains the elements year, month, day.
	 * - year is a year between -4800 and 99999.
	 * - month is a numeric representation of the month between 1 and 12.
	 * - day is the day of the month.
	 *
	 * Return value:
	 * The return value is an integer, representing the day of the year.
	 *
	 * @param  mixed 	{array date | int year}
	 * @param  mixed 	[int month]
	 * @param  integer 	[int day]
	 * @return integer 	days	1 - 365/366
	 * @access public static
	 */
	public static function getDaysOfYear()
	{
		if(func_num_args() >= 3)
			@list($y, $m, $d) = func_get_args();
		else
			@list($y, $m, $d) = func_get_arg(0);

		$am = array(0, 31, (self::isLeapYear($y))? 29: 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);

		for($i = 1; $i < $m; $i++){
			$d += $am[$i];
		}

		return $d;
	}

	/**
	 * LTDT::getDaysToDate()
	 *
	 * Returns an ISO-8601 formatted string, or an array containing the year,
	 * the month, the day and wether it's a leap year or not, depending on which
	 * flags are set.
	 *
	 * Definition:
	 * mixed getDaysToDate(int year, int days[, int flags])
	 * mixed getDaysToDate(array(int year, int days)[, int flags])
	 *
	 * Parameters:
	 * - array contains the elements year, days.
	 * - year is a year between -4800 and 99999.
	 * - days is the number of days elapsed since the year has begun (1 to 365/366).
	 * - flags: Possible flags are: RT_STRING, RT_NUM, RT_ASSOC, RT_BOTH and
	 *                              MF_DYDW.
	 *
	 * Return value:
	 * Depending on the return type, the following will be returned:
	 * RT_STRING: "YYYY-MM-DD" or, combined with MF_DYDW, "YYYY-MM".
	 * RT_NUM:
	 * array(
	 *     [0]   => int year,
	 *     [1]   => int month,
	 *     [2]   => int day,
	 *     [3]   => bool isLeapYear
	 * ).
	 * RT_ASSOC
	 * array(
	 *     'Y'   => int year,
	 *     'W'   => int month,
	 *     'D'   => int day,
	 *     'ly'  => bool isLeapYear
	 * ).
	 * RT_BOTH is a combination of RT_NUM and RT_ASSOC.
	 *
	 * @param  mixed 	{array date | int year}
	 * @param  mixed 	{[int flags] | int days}
	 * @param  integer 	[int flags]
	 * @return mixed	{array | string}
	 * @access public static
	 */
	public static function getDaysToDate()
	{
		$args = func_get_args();
		if(is_array($args[0])){
			@list($y, $d) = $args[0];
			$nFlags = (isset($args[1]))? $args[1]: 0;
		}
		else {
			$y = $args[0];
			$d = $args[1];
			$nFlags = (isset($args[2]))? $args[2]: 0;
		}

		if(($nFlags & 0x00FF) == 0x0)
			$nFlags |= self::RT_NUM;

		$bLY = (bool) self::isLeapYear($y);
		$am = array(0, 31, ($bLY)? 29: 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);

		$c = 0;
		for($m = 1; $m <= 12; $m++){
			if($d - $c <= $am[$m])
				break;
			$c += $am[$m];
		}
		$d -= $c;

		if(($nFlags & 0x00FF) == self::RT_STRING)
			return sprintf(($nFlags & self::MF_DYDW)? "%04d-%02d": "%04d-%02d-%02d", $y, $m, $d);

		$adt = array($y, $m, $d, $bLY);

		if(($nFlags & 0x00FF) == self::RT_BOTH)
			return array_merge($adt, array('Y' => $y, 'M' => $m, 'D' => $d, 'ly' => $bLY));
		else if(($nFlags & 0x00FF) == self::RT_ASSOC)
			return array('Y' => $y, 'M' => $m, 'D' => $d, 'ly' => $bLY);

		return $adt;
	}

	/**
	 * LTDT::isLeapYear()
	 *
	 * Returns TRUE if year is a leap year or FALSE otherwise.
	 *
	 * Definition:
	 * bool isLeapYear(int year)
	 *
	 * Parameter:
	 * - year is a year between -4800 and 99999.
	 *
	 * Return value:
	 * Boolean TRUE if year is a leap year or FALSE otherwise.
	 *
	 * @param  integer year
	 * @return bool
	 * @access public static
	 */
	public static function isLeapYear($y)
	{
		return (($y % 4) == 0) && (!((($y % 100) == 0) && (($y % 400) != 0)));
	}
	
	public static function toUTC()
	{
		// TODO: implement this method
	}
	
	public static function toTZ()
	{
		// TODO: implement this method
	}
	
	/**
	 * LTDT::parseStdDateTime()
	 *
	 * Returns an array containing the year, month, day, hours, minutes, seconds,
	 * milliseconds, UTC offset (if found in the time string) and wether it's a
	 * leap year or not.
	 *
	 * Definition:
	 * array parseStdDateTime(string datetime[, int flags]])
	 *
	 * Parameters:
	 * - datetime: Any valid ISO-8601 format.
	 * - flags: Possible flags are: RT_NUM, RT_ASSOC, RT_BOTH. RT_NUM is the
	 *          default.
	 *
	 * Return value:
	 * Depending on the return type, the following will be returned:
	 * RT_NUM:
	 * array(
	 *     [0]   => int year,
	 *     [1]   => int month,
	 *     [2]   => int day,
	 *     [3]   => int hours,
	 *     [4]   => int minutes,
	 *     [5]   => int seconds,
	 *     [6]   => float milliseconds,
	 *     [7]   => bool isLeapYear,
	 *     [8]   => string UTCOffset
	 * ).
	 * RT_ASSOC:
	 * array(
	 *     'Y'   => int year,
	 *     'M'   => int month,
	 *     'D'   => int day,
	 *     'h'   => int hours,
	 *     'm'   => int minutes,
	 *     's'   => int seconds,
	 *     'f'   => int milliseconds,
	 *     'ly'  => bool isLeapYear,
	 *     'uto' => string UTCOffset
	 * ).
	 * RT_BOTH a combination of RT_NUM and RT_ASSOC
	 *
	 * @params string 	datetime
	 * @params integer 	[flags]
	 * @return array
	 * @access public static
	 */
	public static function parseStdDateTime($sDate, $nFlags = 0)
	{
		if(($nFlags & 0x00FF) == 0x0)
			$nFlags |= self::RT_NUM;

		$adt = preg_split('/[ T]/', $sDate);

		$atm = self::parseStdTime($adt[1], self::RT_NUM);
		$uto  = array_pop($atm);
		$adt = self::parseStdDate($adt[0], self::RT_NUM);
		$ly  = array_pop($adt);
		$adt = array_merge($adt, $atm);
		$adt[7] = $ly;
		if(!empty($uto))
			$adt[8] = $uto;
		else {
			$tm = mktime($adt[3], $adt[4], $adt[5], $adt[1], $adt[2]);
			$adt[8] = ($nFlags & self::MF_UTC)? '+0000': date("O", $tm);
		}

		if($nFlags & self::RT_ASSOC)
			$adta = array(
				'Y' => $adt[0], 'M' => $adt[1], 'D' => $adt[2],
				'h' => $adt[3], 'm' => $adt[4], 's' => $adt[5], 'f' => $adt[6],
				'ly' => $adt[7], 'uto' => $adt[8]
			);

		if(($nFlags & 0x00FF) == self::RT_BOTH)
			return array_merge($adt, $adta);
		else if(($nFlags & 0x00FF) == self::RT_ASSOC)
			return $adta;

		return $adt;
	}

	/**
	 * LTDT::parseStdDate()
	 *
	 * Returns an array containing the year, the month, the day and wether it's
	 * a leap year or not.
	 *
	 * Definition:
	 * array parseStdDate(string date[, int flags])
	 *
	 * Parameters:
	 * - date is a date string in any valid ISO-8601 format without time parts.
	 * - flags: Possible flags are: RT_NUM, RT_ASSOC and RT_BOTH. RT_NUM is the
	 *          default.
	 *
	 * Return value:
	 * Depending on the return type, the following will be returned:
	 * RT_NUM:
	 * array(
	 *     [0]   => int year,
	 *     [1]   => int month,
	 *     [2]   => int day,
	 *     [3]   => bool isLeapYear
	 * ).
	 * RT_ASSOC
	 * array(
	 *     'Y'   => int year,
	 *     'W'   => int month,
	 *     'D'   => int day,
	 *     'ly'  => bool isLeapYear
	 * ).
	 * RT_BOTH is a combination of RT_NUM and RT_ASSOC.
	 *
	 * @param  string 	date
	 * @param  integer 	[flags]
	 * @return array
	 * @access public static
	 */
	public static function parseStdDate($sDate, $nFlags = 0)
	{
		if(($nFlags & 0x00FF) == 0x0)
			$nFlags |= self::RT_NUM;

		// Save negative year
		if($sDate[0] == '-')
			$sDate[0] = '=';

		$sDate = str_replace('-', '', $sDate);
		preg_match("/(\=?\d{4})(\d{3}$|W\d\d\d?$|\d\d)*(\d\d$)*$/iU", $sDate, $matches);

		$y = (int) str_replace('=', '-', $matches[1]);
		$adt = array($y, 1, 1, self::isLeapYear($y));

		if(count($matches) == 4){
			if(strlen($matches[0]) == 8){
				$adt[1] = (int) $matches[2];
				$adt[2] = (int) $matches[3];
			}
			else {
				$adt[1] = (int) $matches[3];
				$adt[2] = 1;
			}
		}
		else if(count($matches) == 3){
			if(strlen($matches[2]) == 2){
				$adt[1] = (int) $matches[2];
				$adt[2] = 1;
			}
			else if(strlen($matches[2]) == 3 && $matches[2][0] != 'W'){
				$adt = self::getDaysToDate($y, (int) $matches[2]);
			}
			else {
				$a = str_split(substr($matches[2], 1), 2);
				$adt = self::getWeekToDate($y, (int) $a[0], (isset($a[1]))? (int) $a[1]: 1);
			}
		}

		if(($nFlags & 0x00FF) == self::RT_BOTH)
			return array_merge($adt, array('Y' => $adt[0], 'M' => $adt[1], 'D' => $adt[2], 'ly' => $adt[3]));
		else if(($nFlags & 0x00FF) == self::RT_ASSOC)
			return array('Y' => $adt[0], 'M' => $adt[1], 'D' => $adt[2], 'ly' => $adt[3]);

		return $adt;
	}

	/**
	 * LTDT::parseStdTime()
	 *
	 * Returns an array containing the hours, the minutes, the seconds, the
	 * milliseconds and UTC offset if found in the time string.
	 *
	 * Definition:
	 * array parseStdTime(string time[, int flags])
	 *
	 * Parameters:
	 * - time is a time string in a valid ISO-8601 format without date parts.
	 * - flags: Possible flags are: RT_NUM, RT_ASSOC and RT_BOTH. RT_NUM is the
	 *          default.
	 *
	 * Return value:
	 * Depending on the return type, the following will be returned:
	 * RT_NUM:
	 * array(
	 *     [0]  => int hours,
	 *     [1]  => int minutes,
	 *     [2]  => int seconds,
	 *     [3]  => float mseconds,
	 *     [4]  => string UTCOffset
	 * ).
	 * RT_ASSOC
	 * array(
	 *     'h'  => int hours,
	 *     'm'  => int minutes,
	 *     's'  => int seconds,
	 *     'f'  => float mseconds,
	 *     'uto' => string UTCOffset
	 * ).
	 * RT_BOTH is a combination of RT_NUM and RT_ASSOC.
	 *
	 * @param  string 	time
	 * @param  integer 	[flags]
	 * @return array
	 * @access public static
	 */
	public static function parseStdTime($sTime, $nFlags = 0)
	{
		if(($nFlags & 0x00FF) == 0x0)
			$nFlags |= self::RT_NUM;

		$sTime = str_replace(':', '', $sTime);
		$sTime = str_replace(' ', '', $sTime);
		preg_match("/(\d{2,6})(\.\d{1,})*(Z$|[A-Z]{3,}$|[+\-]\d{2,4}$)*$/", $sTime, $matches);

		@list($h, $m, $s) = str_split($matches[1], 2);
		$m = (!isset($m))? 0: (int) $m;
		$s = (!isset($s))? 0: (int) $s;
		$f = (isset($matches[2]))? round((float) '0'.$matches[2], 3): 0.0;
		$uto = (isset($matches[3]))? $matches[3]: '';

		$atm = array((int) $h, $m, $s, $f, $uto);

		if(($nFlags & 0x00FF) == self::RT_BOTH)
			return array_merge($atm, array('h' => $h, 'm' => $m, 's' => $s, 'f' => $f, 'uto' => $uto));
		else if(($nFlags & 0x00FF) == self::RT_ASSOC)
			return array('h' => $h, 'm' => $m, 's' => $s, 'f' => $f, 'uto' => $uto);

		return $atm;
	}

}

?>
