<?php

	// These Aid with the PHP 5.2 DateTime error handling
	class QDateTimeNullException extends QCallerException {}
	function QDateTimeErrorHandler() {}

	/**
	 * QDateTime
	 * This DateTime class provides a nice wrapper around the PHP DateTime class,
	 * which is included with all versions of PHP >= 5.2.0. It includes many enhancements,
	 * including the ability to specify a null date or time portion to represent a date only or
	 * time only object.
	 * Inherits from the php DateTime object, and the built-in methods are available for you to call
	 * as well. In particular, note that the built-in format, and the qFormat routines here take different
	 * specifiers. Feel free to use either.
	 *
	 * @property null|integer   $Month
	 * @property null|integer   $Day
	 * @property null|integer   $Year
	 * @property null|integer   $Hour
	 * @property null|integer   $Minute
	 * @property null|integer   $Second
	 * @property integer        $Timestamp
	 * @property-read string    $Age                A string representation of the age relative to now.
	 * @property-read QDateTime $LastDayOfTheMonth  A new QDateTime representing the last day of this date's month.
	 * @property-read QDateTime $FirstDayOfTheMonth A new QDateTime representing the first day of this date's month.
	 */
	class QDateTime extends DateTime implements JsonSerializable, Serializable {
		/** Used to specify the time right now (used when creating new instances of this class) */
		const Now = 'now';
		/** Date and time in ISO format */
		const FormatIso = 'YYYY-MM-DD hhhh:mm:ss';
		/** Date and time in ISO compressed format */
		const FormatIsoCompressed = 'YYYYMMDDhhhhmmss';
		/** Format used for displaying short date */
		const FormatDisplayDate = 'MMM DD YYYY';
		/** Format used for displaying the full date */
		const FormatDisplayDateFull = 'DDD, MMMM D, YYYY';
		/** Format used for displaying the short date and time */
		const FormatDisplayDateTime = 'MMM DD YYYY hh:mm zz';
		/** Format used for displaying the full date and time */
		const FormatDisplayDateTimeFull = 'DDDD, MMMM D, YYYY, h:mm:ss zz';
		/** Format to display only the time */
		const FormatDisplayTime = 'hh:mm:ss zz';
		/** Date and time format according to RFC 822 */
		const FormatRfc822 = 'DDD, DD MMM YYYY hhhh:mm:ss ttt';
		/** Date and time format according to RFC 5322 */
		const FormatRfc5322 = 'DDD, DD MMM YYYY hhhh:mm:ss ttttt';

		/** Format used to represent date for SOAP */
		const FormatSoap = 'YYYY-MM-DDThhhh:mm:ss';

		/* Type in which the date and time has to be interpreted */
		/** Unknown type interpretation */
		const UnknownType = 0;
		/** Interpret as only date (used in QDateTimePicket and MySqli database type) */
		const DateOnlyType = 1;
		/** Interpret as only time (used in QDateTimePicket and MySqli database type) */
		const TimeOnlyType = 2;
		/** Interpret as both date and type (used in QDateTimePicket and MySqli database type) */
		const DateAndTimeType = 3;

		/** @var bool true if date is null */
		protected $blnDateNull = true;
		/** @var bool  true if time is null, rather than just zero (beginning of day) */
		protected $blnTimeNull = true;


		/**
		 * The "Default" Display Format
		 * @var string $DefaultFormat
		 */
		public static $DefaultFormat = QDateTime::FormatDisplayDateTime;
		
		/**
		 * The "Default" Display Format for Times
		 * @var string $DefaultTimeFormat
		 */
		public static $DefaultTimeFormat = QDateTime::FormatDisplayTime;

		/**
		 * The "Default" Display Format for Dates with null times
		 * @var string $DefaultDateOnlyFormat
		 */
		public static $DefaultDateOnlyFormat = QDateTime::FormatDisplayDate;


		/**
		 * Returns a new QDateTime object that's set to "Now"
		 * Set blnTimeValue to true (default) for a DateTime, and set blnTimeValue to false for just a Date
		 *
		 * @param boolean $blnTimeValue whether or not to include the time value
		 * @return QDateTime the current date and/or time
		 */
		public static function Now($blnTimeValue = true) {
			$dttToReturn = new QDateTime(QDateTime::Now);
			if (!$blnTimeValue) {
				$dttToReturn->blnTimeNull = true;
				$dttToReturn->ReinforceNullProperties();
			}
			return $dttToReturn;
		}

		/**
		 * Return Now as a string. Uses the default datetime format if none speicifed.
		 * @param string|null $strFormat
		 * @return string
		 */
		public static function NowToString($strFormat = null) {
			$dttNow = new QDateTime(QDateTime::Now);
			return $dttNow->qFormat($strFormat);
		}

		/**
		 * @return bool
		 */
		public function IsDateNull() {
			return $this->blnDateNull;
		}

		/**
		 * @return bool
		 */
		public function IsNull() {
			return ($this->blnDateNull && $this->blnTimeNull);
		}

		/**
		 * @return bool
		 */
		public function IsTimeNull() {
			return $this->blnTimeNull;
		}

		/**
		 * @param $strFormat
		 * @return string
		 */
		public function PhpDate($strFormat) {
			// This just makes a call to format
			return parent::format($strFormat);
		}

		/**
		 * @param QDateTime[] $dttArray
		 * @return array
		 */
		public function GetSoapDateTimeArray($dttArray) {
			if (!$dttArray)
				return null;

			$strArrayToReturn = array();
			foreach ($dttArray as $dttItem)
				array_push($strArrayToReturn, $dttItem->qFormat(QDateTime::FormatSoap));
			return $strArrayToReturn;
		}

		/**
		 * Create from a unix timestamp. Improves over php by taking into consideration the
		 * timezone, so that the internal format is automatically converted to the internal timezone,
		 * or the default timezone.
		 *
		 * @param integer $intTimestamp
		 * @param DateTimeZone $objTimeZone
		 * @return QDateTime
		 */
		public static function FromTimestamp($intTimestamp, DateTimeZone $objTimeZone = null) {
			return new QDateTime(date('Y-m-d H:i:s', $intTimestamp), $objTimeZone);
		}

		/**
		 * Construct a QDateTime. Does a few things differently than the php version:
		 * - Always stores timestamps in local or given timezone, so time extraction is easy
		 * - Has settings to determine if you want a date only or time only type
		 * - Will NOT throw exceptions. Errors simply result in a null datetime.
		 *
		 * @param null|integer|string|QDateTime|DateTime $mixValue
		 * @param DateTimeZone                           $objTimeZone
		 * @param int                                    $intType
		 *
		 * @throws QCallerException
		 */
		public function __construct($mixValue = null, DateTimeZone $objTimeZone = null, $intType = QDateTime::UnknownType) {
			if ($mixValue instanceof QDateTime) {
				// Cloning from another QDateTime object
				if ($objTimeZone)
					throw new QCallerException('QDateTime cloning cannot take in a DateTimeZone parameter');
				parent::__construct($mixValue->format('Y-m-d H:i:s'), $mixValue->GetTimeZone());
				$this->blnDateNull = $mixValue->IsDateNull();
				$this->blnTimeNull = $mixValue->IsTimeNull();
				$this->ReinforceNullProperties();

			} else if ($mixValue instanceof DateTime) {
				// Subclassing from a PHP DateTime object
				if ($objTimeZone)
					throw new QCallerException('QDateTime subclassing of a DateTime object cannot take in a DateTimeZone parameter');
				parent::__construct($mixValue->format('Y-m-d H:i:s'), $mixValue->getTimezone());

				// By definition, a DateTime object doesn't have anything nulled
				$this->blnDateNull = false;
				$this->blnTimeNull = false;
			} else if (!$mixValue) {
				// Set to "null date"
				// And Do Nothing Else -- Default Values are already set to Nulled out
				parent::__construct('2000-01-01 00:00:00', $objTimeZone);
			} else if (strtolower($mixValue) == QDateTime::Now) {
				// very common, so quickly deal with now string
				parent::__construct('now', $objTimeZone);
				$this->blnDateNull = false;
				$this->blnTimeNull = false;
			} else if (substr($mixValue, 0, 1) == '@') {
				// unix timestamp. PHP superclass will always store ts in UTC. Our class will store in given timezone, or local tz
				parent::__construct(date('Y-m-d H:i:s', substr($mixValue, 1)), $objTimeZone);
				$this->blnDateNull = false;
				$this->blnTimeNull = false;
			}
			else {
				// string relative date or time
				if ($intTime = strtotime($mixValue)) {
					// The documentation states that:
					// The valid range of a timestamp is typically from 
					// Fri, 13 Dec 1901 20:45:54 GMT to Tue, 19 Jan 2038 03:14:07 GMT. 
					// (These are the dates that correspond to the minimum and maximum values 
					// for a 32-bit signed integer).
					// 
					// But experimentally, 0000-01-01 00:00:00 is the least date displayed correctly
					if ($intTime < -62167241486) {
						// Set to "null date"
						// And Do Nothing Else -- Default Values are already set to Nulled out
						parent::__construct('2000-01-01 00:00:00', $objTimeZone);
					} else {
						parent::__construct(date('Y-m-d H:i:s', $intTime), $objTimeZone);
						$this->blnDateNull = false;
						$this->blnTimeNull = false;
					}
				} else { // error
					parent::__construct();
					$this->blnDateNull = true;
					$this->blnTimeNull = true;
				}
			}

			// User is requesting to force a particular type.
			switch ($intType) {
				case QDateTime::DateOnlyType:
					$this->blnTimeNull = true;
					$this->ReinforceNullProperties();
					return;
				case QDateTime::TimeOnlyType:
					$this->blnDateNull = true;
					$this->ReinforceNullProperties();
					return;
				case QDateTime::DateAndTimeType:	// forcing both a date and time type to not be null
					$this->blnDateNull = false;
					$this->blnTimeNull = false;
					break;
				default:
					break;
			}
		}
		
		/**
		* Returns a new QDateTime object set to the last day of the specified month.
		* 
		* @param int $intMonth
		* @param int $intYear
		* @return QDateTime the last day to a month in a year
		*/
		public static function LastDayOfTheMonth($intMonth, $intYear) {
			$temp = date('Y-m-t',mktime(0,0,0,$intMonth,1,$intYear));
			return new QDateTime($temp);
		}
		
		/**
		* Returns a new QDateTime object set to the first day of the specified month.
		* 
		* @param int $intMonth
		* @param int $intYear
		* @return QDateTime the first day of the month
		*/
		public static function FirstDayOfTheMonth($intMonth, $intYear) {
			$temp = date('Y-m-d',mktime(0,0,0,$intMonth,1,$intYear));
			return new QDateTime($temp);
		}

		/**
		 * Formats a date as a string using the default format type.
		 * @return string
		 */
		public function __toString() {
			return $this->qFormat();
		}

		/**
		 * The following code is a workaround for a PHP bug in 5.2 and greater (at least to 5.4).
		 */
		//protected $strSerializedData;
		//protected $strSerializedTZ;
		public function serialize() {
			$tz = $this->getTimezone();
			if ($tz && in_array ($tz->getName(), timezone_identifiers_list())) {
				$strTz = $tz->getName();
				$strDate = parent::format('Y-m-d H:i:s');
			} else {
				$strTz = null;
				$strDate = parent::format (DateTime::ISO8601);
			}
			return serialize([
				1, // version number of serialized data, in case format changes
				$this->blnDateNull,
				$this->blnTimeNull,
				$strDate,
				$strTz
			]);
		}
		public function unserialize($s) {
			$a = unserialize($s);
			$this->blnDateNull = $a[1];
			$this->blnTimeNull = $a[2];
			$tz = $a[4];
			if ($tz) {
				$tz = new DateTimeZone($tz);
			} else {
				$tz = null;
			}
			parent::__construct($a[3], $tz);
		}

		/**
		 * Outputs the date as a string given the format strFormat.  Will use
		 * the static defaults if none given.
		 *
		 * Properties of strFormat are (using Sunday, March 2, 1977 at 1:15:35 pm
		 * in the following examples):
		 *
		 *	M - Month as an integer (e.g., 3)
		 *	MM - Month as an integer with leading zero (e.g., 03)
		 *	MMM - Month as three-letters (e.g., Mar)
		 *	MMMM - Month as full name (e.g., March)
		 *
		 *	D - Day as an integer (e.g., 2)
		 *	DD - Day as an integer with leading zero (e.g., 02)
		 *	DDD - Day of week as three-letters (e.g., Wed)
		 *	DDDD - Day of week as full name (e.g., Wednesday)
		 *
		 *	YY - Year as a two-digit integer (e.g., 77)
		 *	YYYY - Year as a four-digit integer (e.g., 1977)
		 *
		 *	h - Hour as an integer in 12-hour format (e.g., 1)
		 *	hh - Hour as an integer in 12-hour format with leading zero (e.g., 01)
		 *	hhh - Hour as an integer in 24-hour format (e.g., 13)
		 *	hhhh - Hour as an integer in 24-hour format with leading zero (e.g., 13)
		 *
		 *	mm - Minute as a two-digit integer
		 *
		 *	ss - Second as a two-digit integer
		 *
		 *	z - "pm" or "am"
		 *	zz - "PM" or "AM"
		 *	zzz - "p.m." or "a.m."
		 *	zzzz - "P.M." or "A.M."
		 * 
		 *  ttt - Timezone Abbreviation as a three-letter code (e.g. PDT, GMT)
		 *  tttt - Timezone Identifier (e.g. America/Los_Angeles)
		 *
		 * @param string $strFormat the format of the date
		 * @return string the formatted date as a string
		 */
		public function qFormat($strFormat = null) {
			if ($this->blnDateNull && $this->blnTimeNull) {
				return '';
			}

			if (is_null($strFormat)) {
				if ($this->blnDateNull && !$this->blnTimeNull) {
					$strFormat = QDateTime::$DefaultTimeFormat;
				} elseif (!$this->blnDateNull && $this->blnTimeNull) {
					$strFormat = QDateTime::$DefaultDateOnlyFormat;
				} else {
					$strFormat = QDateTime::$DefaultFormat;
				}
			}

			/*
				(?(?=D)([D]+)|
					(?(?=M)([M]+)|
						(?(?=Y)([Y]+)|
							(?(?=h)([h]+)|
								(?(?=m)([m]+)|
									(?(?=s)([s]+)|
										(?(?=z)([z]+)|
											(?(?=t)([t]+)|
				))))))))
			*/

//			$strArray = preg_split('/([^D^M^Y^h^m^s^z^t])+/', $strFormat);
			preg_match_all('/(?(?=D)([D]+)|(?(?=M)([M]+)|(?(?=Y)([Y]+)|(?(?=h)([h]+)|(?(?=m)([m]+)|(?(?=s)([s]+)|(?(?=z)([z]+)|(?(?=t)([t]+)|))))))))/', $strFormat, $strArray);
			$strArray = $strArray[0];
			$strToReturn = '';

			$intStartPosition = 0;
			for ($intIndex = 0; $intIndex < count($strArray); $intIndex++) {
				$strToken = trim($strArray[$intIndex]);
				if ($strToken) {
					$intEndPosition = strpos($strFormat, $strArray[$intIndex], $intStartPosition);
					$strToReturn .= substr($strFormat, $intStartPosition, $intEndPosition - $intStartPosition);
					$intStartPosition = $intEndPosition + strlen($strArray[$intIndex]);

					switch ($strArray[$intIndex]) {
						case 'M':
							$strToReturn .= parent::format('n');
							break;
						case 'MM':
							$strToReturn .= parent::format('m');
							break;
						case 'MMM':
							$strToReturn .= parent::format('M');
							break;
						case 'MMMM':
							$strToReturn .= parent::format('F');
							break;
			
						case 'D':
							$strToReturn .= parent::format('j');
							break;
						case 'DD':
							$strToReturn .= parent::format('d');
							break;
						case 'DDD':
							$strToReturn .= parent::format('D');
							break;
						case 'DDDD':
							$strToReturn .= parent::format('l');
							break;
			
						case 'YY':
							$strToReturn .= parent::format('y');
							break;
						case 'YYYY':
							$strToReturn .= parent::format('Y');
							break;
			
						case 'h':
							$strToReturn .= parent::format('g');
							break;
						case 'hh':
							$strToReturn .= parent::format('h');
							break;
						case 'hhh':
							$strToReturn .= parent::format('G');
							break;
						case 'hhhh':
							$strToReturn .= parent::format('H');
							break;

						case 'mm':
							$strToReturn .= parent::format('i');
							break;
			
						case 'ss':
							$strToReturn .= parent::format('s');
							break;
			
						case 'z':
							$strToReturn .= parent::format('a');
							break;
						case 'zz':
							$strToReturn .= parent::format('A');
							break;
						case 'zzz':
							$strToReturn .= sprintf('%s.m.', substr(parent::format('a'), 0, 1));
							break;
						case 'zzzz':
							$strToReturn .= sprintf('%s.M.', substr(parent::format('A'), 0, 1));
							break;

						case 'ttt':
							$strToReturn .= parent::format('T');
							break;
						case 'tttt':
							$strToReturn .= parent::format('e');
							break;
						case 'ttttt':
							$strToReturn .= parent::format('O');
							break;

						default:
							$strToReturn .= $strArray[$intIndex];
					}
				}
			}

			if ($intStartPosition < strlen($strFormat))
				$strToReturn .= substr($strFormat, $intStartPosition);

			return $strToReturn;
		}

		/**
		 * Sets the time portion to the given time. If a QDateTime is given, will use the time portion of that object.
		 * Works around a problem in php that if you set the time across a daylight savings time boundary, the timezone
		 * does not advance. This version will detect that and advance the timezone.
		 *
		 * @param int|QDateTime $mixValue
		 * @param int|null $intMinute
		 * @param int|null $intSecond
		 * @return QDateTime
		 */
		public function setTime($mixValue, $intMinute = null, $intSecond = null, $intMicroSeconds = null) {
			if ($mixValue instanceof QDateTime) {
				if ($mixValue->IsTimeNull()) {
					$this->blnTimeNull = true;
					$this->ReinforceNullProperties();
					return $this;
				}
				// normalize the timezones
				$tz = $this->getTimezone();
				if ($tz && in_array ($tz->getName(), timezone_identifiers_list())) {
					// php limits you to ID only timezones here, so make sure we have one of those
					$mixValue->setTimezone ($tz);
				}
				$intHour = $mixValue->Hour;
				$intMinute = $mixValue->Minute;
				$intSecond = $mixValue->Second;
			} else {
				$intHour = $mixValue;
			}
			// If HOUR or MINUTE is NULL...
			if (is_null($intHour) || is_null($intMinute)) {
				if (version_compare(PHP_VERSION, '7.2.0', '>=')) {
					parent::setTime($intHour, $intMinute, $intSecond, $intMicroSeconds);
				} else {
					parent::setTime($intHour, $intMinute, $intSecond);
				}
				$this->blnTimeNull = true;
				$this->ReinforceNullProperties();
				return $this;
			}

			$intHour = QType::Cast($intHour, QType::Integer);
			$intMinute = QType::Cast($intMinute, QType::Integer);
			$intSecond = QType::Cast($intSecond, QType::Integer);
			$this->blnTimeNull = false;

			/*
			// Possible fix for a PHP problem. Can't reproduce, so leaving code here just in case it comes back.
			// The problem is with setting times across dst barriers
			if ($this->Hour == 0 && preg_match('/[0-9]+/', $this->getTimezone()->getName())) {
				// fix a php problem with GMT and relative timezones
				$s = 'PT' . $intHour . 'H' . $intMinute . 'M' . $intSecond . 'S';
				$this->add (new DateInterval ($s));
				// will continue and set again to make sure, because boundary crossing will change the time
			}*/

			if (version_compare(PHP_VERSION, '7.2.0', '>=')) {
				parent::setTime($intHour, $intMinute, $intSecond, $intMicroSeconds);
			} else {
				parent::setTime($intHour, $intMinute, $intSecond);
			}

			return $this;
		}

		/**
		 * Set the date.
		 *
		 * @param int $intYear
		 * @param int $intMonth
		 * @param int $intDay
		 * @return $this|DateTime
		 */
		public function setDate($intYear, $intMonth, $intDay) {
			$intYear = QType::Cast($intYear, QType::Integer);
			$intMonth = QType::Cast($intMonth, QType::Integer);
			$intDay = QType::Cast($intDay, QType::Integer);
			$this->blnDateNull = false;
			parent::setDate($intYear, $intMonth, $intDay);
			return $this;
		}

		protected function ReinforceNullProperties() {
			if ($this->blnDateNull)
				parent::setDate(2000, 1, 1);
			if ($this->blnTimeNull) {
				parent::setTime(0, 0, 0);
			}

		}
		
		/**
		 * Converts the current QDateTime object to a different TimeZone.
		 * 
		 * TimeZone should be passed in as a string-based identifier.
		 * 
		 * Note that this is different than the built-in DateTime::SetTimezone() method which expicitly
		 * takes in a DateTimeZone object.  QDateTime::ConvertToTimezone allows you to specify any
		 * string-based Timezone identifier.  If none is specified and/or if the specified timezone
		 * is not a valid identifier, it will simply remain unchanged as opposed to throwing an exeception
		 * or error.
		 * 
		 * @param string $strTimezoneIdentifier a string-based parameter specifying a timezone identifier (e.g. America/Los_Angeles)
		 * @return void
		 */
		public function ConvertToTimezone($strTimezoneIdentifier) {
			try {
				$dtzNewTimezone = new DateTimeZone($strTimezoneIdentifier);
				$this->SetTimezone($dtzNewTimezone);
			} catch (Exception $objExc) {}
		}

		/**
		 * Returns true if give QDateTime is the same.
		 *
		 * @param QDateTime $dttCompare
		 * @return bool
		 */
		public function IsEqualTo(QDateTime $dttCompare) {
			// All comparison operations MUST have operands with matching Date Nullstates
			if ($this->blnDateNull != $dttCompare->blnDateNull)
				return false;

			// If mismatched Time nullstates, then only compare the Date portions
			if ($this->blnTimeNull != $dttCompare->blnTimeNull) {
				// Let's "Null Out" the Time
				$dttThis = new QDateTime($this);
				$dttThat = new QDateTime($dttCompare);
				$dttThis->Hour = null;
				$dttThat->Hour = null;

				// Return the Result
				return ($dttThis->Timestamp == $dttThat->Timestamp);
			} else {
				// Return the Result for the both Date and Time components
				return ($this->Timestamp == $dttCompare->Timestamp);
			}
		}

		/**
		 * Returns true if current date time is earlier than the given one.
		 * @param QDateTime $dttCompare
		 * @return bool
		 */
		public function IsEarlierThan(QDateTime $dttCompare) {
			// All comparison operations MUST have operands with matching Date Nullstates
			if ($this->blnDateNull != $dttCompare->blnDateNull)
				return false;

			// If mismatched Time nullstates, then only compare the Date portions
			if ($this->blnTimeNull != $dttCompare->blnTimeNull) {
				// Let's "Null Out" the Time
				$dttThis = new QDateTime($this);
				$dttThat = new QDateTime($dttCompare);
				$dttThis->Hour = null;
				$dttThat->Hour = null;

				// Return the Result
				return ($dttThis->Timestamp < $dttThat->Timestamp);
			} else {
				// Return the Result for the both Date and Time components
				return ($this->Timestamp < $dttCompare->Timestamp);
			}
		}

		/**
		 * Returns true if current date time is earlier than the given one.
		 * @param QDateTime $dttCompare
		 * @return bool
		 */
		public function IsEarlierOrEqualTo(QDateTime $dttCompare) {
			// All comparison operations MUST have operands with matching Date Nullstates
			if ($this->blnDateNull != $dttCompare->blnDateNull)
				return false;

			// If mismatched Time nullstates, then only compare the Date portions
			if ($this->blnTimeNull != $dttCompare->blnTimeNull) {
				// Let's "Null Out" the Time
				$dttThis = new QDateTime($this);
				$dttThat = new QDateTime($dttCompare);
				$dttThis->Hour = null;
				$dttThat->Hour = null;

				// Return the Result
				return ($dttThis->Timestamp <= $dttThat->Timestamp);
			} else {
				// Return the Result for the both Date and Time components
				return ($this->Timestamp <= $dttCompare->Timestamp);
			}
		}

		/**
		 * Returns true if current date time is later than the given one.
		 * @param QDateTime $dttCompare
		 * @return bool
		 */
		public function IsLaterThan(QDateTime $dttCompare) {
			// All comparison operations MUST have operands with matching Date Nullstates
			if ($this->blnDateNull != $dttCompare->blnDateNull)
				return false;

			// If mismatched Time nullstates, then only compare the Date portions
			if ($this->blnTimeNull != $dttCompare->blnTimeNull) {
				// Let's "Null Out" the Time
				$dttThis = new QDateTime($this);
				$dttThat = new QDateTime($dttCompare);
				$dttThis->Hour = null;
				$dttThat->Hour = null;

				// Return the Result
				return ($dttThis->Timestamp > $dttThat->Timestamp);
			} else {
				// Return the Result for the both Date and Time components
				return ($this->Timestamp > $dttCompare->Timestamp);
			}
		}

		/**
		 * Returns true if current date time is later than or equal to the given one.
		 * @param QDateTime $dttCompare
		 * @return bool
		 */
		public function IsLaterOrEqualTo(QDateTime $dttCompare) {
			// All comparison operations MUST have operands with matching Date Nullstates
			if ($this->blnDateNull != $dttCompare->blnDateNull)
				return false;

			// If mismatched Time nullstates, then only compare the Date portions
			if ($this->blnTimeNull != $dttCompare->blnTimeNull) {
				// Let's "Null Out" the Time
				$dttThis = new QDateTime($this);
				$dttThat = new QDateTime($dttCompare);
				$dttThis->Hour = null;
				$dttThat->Hour = null;

				// Return the Result
				return ($dttThis->Timestamp >= $dttThat->Timestamp);
			} else {
				// Return the Result for the both Date and Time components
				return ($this->Timestamp >= $dttCompare->Timestamp);
			}
		}

		/**
		 * Compare the current date with the given date. Return -1 if current date is less than given date, 0 if equal,
		 * and 1 if greater. Null dates are considered the earliest possible date.
		 *
		 * @param QDateTime $dttCompare
		 * @return int
		 */
		public function Compare(QDateTime $dttCompare) {
			// All comparison operations MUST have operands with matching Date Nullstates
			if ($this->blnDateNull && !$dttCompare->blnDateNull) {
				return -1;
			} elseif (!$this->blnDateNull && $dttCompare->blnDateNull) {
				return 1;
			}

			// If mismatched Time nullstates, then only compare the Date portions
			if ($this->blnTimeNull != $dttCompare->blnTimeNull) {
				// Let's "Null Out" the Time
				$dttThis = new QDateTime($this);
				$dttThat = new QDateTime($dttCompare);
				$dttThis->Hour = null;
				$dttThat->Hour = null;
			} else {
				$dttThis = $this;
				$dttThat = $dttCompare;
			}
			return ($dttThis->Timestamp < $dttThat->Timestamp ? -1 : ($dttThis->Timestamp == $dttThat->Timestamp ? 0 : 1));
		}

		/**
		 * Returns the difference as a QDateSpan, which is easier to work with and more full featured than
		 * the php DateTimeInterval class.
		 *
		 * @param QDateTime $dttDateTime
		 * @return QDateTimeSpan
		 */
		public function Difference(QDateTime $dttDateTime) {
			$intDifference = $this->Timestamp - $dttDateTime->Timestamp;
			return new QDateTimeSpan($intDifference);
		}

		/**
		 * Add a datespan or interval to the current date.
		 *
		 * @param DateInterval|QDateTimeSpan $dtsSpan
		 * @return QDateTime
		 * @throws QCallerException
		 */
		public function Add($dtsSpan){
			if ($dtsSpan instanceof DateInterval) {
				parent::add($dtsSpan);
				return $this;
			}
			elseif (!$dtsSpan instanceof QDateTimeSpan) {
				throw new QCallerException("Can only add DateTimeInterval or QDateTimeSpan objects");
			}

			// And add the Span Second count to it
			$this->Timestamp = $this->Timestamp + $dtsSpan->Seconds;
			return $this;
		}

		/**
		 * Add a number of seconds. Use negative value to go earlier in time.
		 *
		 * @param integer $intSeconds
		 * @return QDateTime
		 */
		public function AddSeconds($intSeconds){
			$this->Second += $intSeconds;
			return $this;
		}

		/**
		 * Add minutes to the time.
		 *
		 * @param integer $intMinutes
		 * @return QDateTime
		 */
		public function AddMinutes($intMinutes){
			$this->Minute += $intMinutes;
			return $this;
		}

		/**
		 * Add hours to the time.
		 *
		 * @param integer $intHours
		 * @return QDateTime
		 */
		public function AddHours($intHours){
			$this->Hour += $intHours;
			return $this;
		}

		/**
		 * Add days to the time.
		 *
		 * @param integer $intDays
		 * @return QDateTIme
		 */
		public function AddDays($intDays){
			$this->Day += $intDays;
			return $this;
		}

		/**
		 * Add months to the time. If the day on the new month is greater than the month will allow, the day is adjusted
		 * to be the last day of that month.
		 *
		 * @param integer $intMonths
		 * @return QDateTime
		 */
		public function AddMonths($intMonths){
			$prevDay = $this->Day;
			$this->Month += $intMonths;
			if ($this->Day != $prevDay) {
				$this->Day = 1;
				$this->AddDays (-1);
			}
			return $this;
		}

		/**
		 * Add years to the time.
		 *
		 * @param integer $intYears
		 * @return QDateTime
		 */
		public function AddYears($intYears){
			$this->Year += $intYears;
			return $this;
		}

		/**
		 * Modifies the date or time based on values found int a string.
		 *
		 * @see DateTime::modify()
		 * @param string $mixValue
		 * @return QDateTime
		 */
		public function Modify($mixValue) {
			parent::modify($mixValue);
			return $this;
		}

		/**
		 * Convert the object to a javascript object. This is code that if executed in javascript would produce a Date
		 * javascript object. Note that the date will be created in the browser's local timezone, so convert to the
		 * browser's timezone first if that is important for you.
		 *
		 * @return string
		 */
		public function toJsObject() {
			if ($this->blnDateNull) {
				$dt = self::Now();	// time only will use today's date.
				$dt->setTime($this);
			} else {
				$dt = clone $this;
			}

			if ($this->blnTimeNull) {
				return sprintf ('new Date(%s, %s, %s)', $dt->Year, $dt->Month - 1, $dt->Day);
			} else {
				return sprintf ('new Date(%s, %s, %s, %s, %s, %s)', $dt->Year, $dt->Month - 1, $dt->Day, $dt->Hour, $dt->Minute, $dt->Second);
			}
		}

		/**
		 * Returns a datetime in a way that it will pass through a json_encode and be decodalbe in qcubed.js.
		 * qcubed.unpackParams looks for this.
		 *
		 * @return array|mixed;
		 */
		public function jsonSerialize() {
			if ($this->blnDateNull) {
				$dt = self::Now();	// time only will use today's date.
				$dt->setTime($this);
			} else {
				$dt = clone $this;
			}

			if ($this->blnTimeNull) {
				return [JavaScriptHelper::ObjectType=>'qDateTime', 'year'=>$dt->Year, 'month'=>$dt->Month - 1,
					'day'=>$dt->Day];

			} else {
				return [JavaScriptHelper::ObjectType=>'qDateTime', 'year'=>$dt->Year, 'month'=>$dt->Month - 1,
					'day'=>$dt->Day, 'hour'=>$dt->Hour, 'minute'=>$dt->Minute, 'second'=>$dt->Second];
			}
		}

		/**
		 * PHP magic method
		 * @param $strName
		 *
		 * @return QDateTime|string
		 * @throws QUndefinedPropertyException
		 */
		public function __get($strName) {
			switch ($strName) {
				case 'Month':
					if ($this->blnDateNull)
						return null;
					else
						return (int) parent::format('m');

				case 'Day':
					if ($this->blnDateNull)
						return null;
					else
						return (int) parent::format('d');

				case 'Year':
					if ($this->blnDateNull)
						return null;
					else
						return (int) parent::format('Y');

				case 'Hour':
					if ($this->blnTimeNull)
						return null;
					else
						return (int) parent::format('H');

				case 'Minute':
					if ($this->blnTimeNull)
						return null;
					else
						return (int) parent::format('i');

				case 'Second':
					if ($this->blnTimeNull)
						return null;
					else
						return (int) parent::format('s');

				case 'Timestamp':
					return (int) parent::format('U'); // range depends on the platform's max and min integer values

				case 'Age':
					// Figure out the Difference from "Now"
					$dtsFromCurrent = $this->Difference(QDateTime::Now());
					
					// It's in the future ('about 2 hours from now')
					if ($dtsFromCurrent->IsPositive())
						return $dtsFromCurrent->SimpleDisplay() . QApplication::Translate(' from now');

					// It's in the past ('about 5 hours ago')
					else if ($dtsFromCurrent->IsNegative()) {
						$dtsFromCurrent->Seconds = abs($dtsFromCurrent->Seconds);
						return $dtsFromCurrent->SimpleDisplay() . QApplication::Translate(' ago');

					// It's current
					} else
						return QApplication::Translate('right now');

				case 'LastDayOfTheMonth':
					return self::LastDayOfTheMonth($this->Month, $this->Year);
				case 'FirstDayOfTheMonth':
					return self::FirstDayOfTheMonth($this->Month, $this->Year);
				default:
					throw new QUndefinedPropertyException('GET', 'QDateTime', $strName);
			}
		}

		/**
		 * PHP magic method
		 *
		 * @param $strName
		 * @param $mixValue
		 *
		 * @return mixed
		 * @throws Exception|QCallerException|QDateTimeNullException|QInvalidCastException|QUndefinedPropertyException
		 */
		public function __set($strName, $mixValue) {
			try {
				switch ($strName) {
					case 'Month':
						if ($this->blnDateNull && (!is_null($mixValue)))
							throw new QDateTimeNullException('Cannot set the Month property on a null date.  Use SetDate().');
						if (is_null($mixValue)) {
							$this->blnDateNull = true;
							$this->ReinforceNullProperties();
							return null;
						}
						$mixValue = QType::Cast($mixValue, QType::Integer);
						parent::setDate(parent::format('Y'), $mixValue, parent::format('d'));
						return $mixValue;

					case 'Day':
						if ($this->blnDateNull && (!is_null($mixValue)))
							throw new QDateTimeNullException('Cannot set the Day property on a null date.  Use SetDate().');
						if (is_null($mixValue)) {
							$this->blnDateNull = true;
							$this->ReinforceNullProperties();
							return null;
						}
						$mixValue = QType::Cast($mixValue, QType::Integer);
						parent::setDate(parent::format('Y'), parent::format('m'), $mixValue);
						return $mixValue;

					case 'Year':
						if ($this->blnDateNull && (!is_null($mixValue)))
							throw new QDateTimeNullException('Cannot set the Year property on a null date.  Use SetDate().');
						if (is_null($mixValue)) {
							$this->blnDateNull = true;
							$this->ReinforceNullProperties();
							return null;
						}
						$mixValue = QType::Cast($mixValue, QType::Integer);
						parent::setDate($mixValue, parent::format('m'), parent::format('d'));
						return $mixValue;

					case 'Hour':
						if ($this->blnTimeNull && (!is_null($mixValue)))
							throw new QDateTimeNullException('Cannot set the Hour property on a null time.  Use SetTime().');
						if (is_null($mixValue)) {
							$this->blnTimeNull = true;
							$this->ReinforceNullProperties();
							return null;
						}
						$mixValue = QType::Cast($mixValue, QType::Integer);
						parent::setTime($mixValue, parent::format('i'), parent::format('s'));
						return $mixValue;

					case 'Minute':
						if ($this->blnTimeNull && (!is_null($mixValue)))
							throw new QDateTimeNullException('Cannot set the Minute property on a null time.  Use SetTime().');
						if (is_null($mixValue)) {
							$this->blnTimeNull = true;
							$this->ReinforceNullProperties();
							return null;
						}
						$mixValue = QType::Cast($mixValue, QType::Integer);
						parent::setTime(parent::format('H'), $mixValue, parent::format('s'));
						return $mixValue;

					case 'Second':
						if ($this->blnTimeNull && (!is_null($mixValue)))
							throw new QDateTimeNullException('Cannot set the Second property on a null time.  Use SetTime().');
						if (is_null($mixValue)) {
							$this->blnTimeNull = true;
							$this->ReinforceNullProperties();
							return null;
						}
						$mixValue = QType::Cast($mixValue, QType::Integer);
						parent::setTime(parent::format('H'), parent::format('i'), $mixValue);
						return $mixValue;

					case 'Timestamp':
						$mixValue = QType::Cast($mixValue, QType::Integer);
						$this->setTimestamp($mixValue);
						$this->blnDateNull = false;
						$this->blnTimeNull = false;
						return $mixValue;

					default:
						throw new QUndefinedPropertyException('SET', 'QDateTime', $strName);
				}
			} catch (QInvalidCastException $objExc) {
				$objExc->IncrementOffset();
				throw $objExc;
			}
		}
	}

/*
	This is a reference to the documentation for hte PHP DateTime classes (as of PHP 5.2)

      DateTime::ATOM
      DateTime::COOKIE
      DateTime::ISO8601
      DateTime::RFC822
      DateTime::RFC850
      DateTime::RFC1036
      DateTime::RFC1123
      DateTime::RFC2822
      DateTime::RFC3339
      DateTime::RSS
      DateTime::W3C

      DateTime::__construct([string time[, DateTimeZone object]])
      - Returns new DateTime object
      
      string DateTime::format(string format)
      - Returns date formatted according to given format
      
      long DateTime::getOffset()
      - Returns the DST offset
      
      DateTimeZone DateTime::getTimezone()
      - Return new DateTimeZone object relative to give DateTime
      
      void DateTime::modify(string modify)
      - Alters the timestamp
      
      array DateTime::parse(string date)
      - Returns associative array with detailed info about given date
      
      void DateTime::setDate(long year, long month, long day)
      - Sets the date
      
      void DateTime::setISODate(long year, long week[, long day])
      - Sets the ISO date
      
      void DateTime::setTime(long hour, long minute[, long second])
      - Sets the time
      
      void DateTime::setTimezone(DateTimeZone object)
      - Sets the timezone for the DateTime object
*/

/* Some quick and dirty test harnesses
	$dtt1 = new QDateTime();
	$dtt2 = new QDateTime();
	printTable($dtt1, $dtt2);
	$dtt2->setDate(2000, 1, 1);
	$dtt1->setTime(0,0,3);
	$dtt2->setTime(0,0,2);
//	$dtt2->Month++;
	printTable($dtt1, $dtt2);

	function printTable($dtt1, $dtt2) {
		print('<table border="1" cellpadding="2"><tr><td>');
		printDate($dtt1);
		print('</td><td>');
		printDate($dtt2);
		print ('</td></tr>');
		
		print ('<tr><td colspan="2" align="center">IsEqualTo: <b>' . (($dtt1->IsEqualTo($dtt2)) ? 'Yes' : 'No') . '</b></td></tr>');
		print ('<tr><td colspan="2" align="center">IsEarlierThan: <b>' . (($dtt1->IsEarlierThan($dtt2)) ? 'Yes' : 'No') . '</b></td></tr>');
		print ('<tr><td colspan="2" align="center">IsLaterThan: <b>' . (($dtt1->IsLaterThan($dtt2)) ? 'Yes' : 'No') . '</b></td></tr>');
		print ('<tr><td colspan="2" align="center">IsEarlierOrEqualTo: <b>' . (($dtt1->IsEarlierOrEqualTo($dtt2)) ? 'Yes' : 'No') . '</b></td></tr>');
		print ('<tr><td colspan="2" align="center">IsLaterOrEqualTo: <b>' . (($dtt1->IsLaterOrEqualTo($dtt2)) ? 'Yes' : 'No') . '</b></td></tr>');
		print('</table>');
	}
	
	function printDate($dtt) {
		print ('Time Null: ' . (($dtt->IsTimeNull()) ? 'Yes' : 'No'));
		print ('<br/>');
		print ('Date Null: ' . (($dtt->IsDateNull()) ? 'Yes' : 'No'));
		print ('<br/>');
		print ('Date: ' . $dtt->qFormat(QDateTime::FormatDisplayDateTimeFull));
		print ('<br/>');
		print ('Month: ' . $dtt->Month . '<br/>');
		print ('Day: ' . $dtt->Day . '<br/>');
		print ('Year: ' . $dtt->Year . '<br/>');
		print ('Hour: ' . $dtt->Hour . '<br/>');
		print ('Minute: ' . $dtt->Minute . '<br/>');
		print ('Second: ' . $dtt->Second . '<br/>');
	}*/
