Location: PHPKode > projects > PhpiCalLib > datatypes.php
<?php
/**
 * datatypes.php iCalendar data type classes
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * @copyright Copyright (C) 2008 Nigel Swinson, hide@address.com 
 * @author  Nigel Swinson
 * @package PhpiCalLib
 * @version 1.0
 */

require_once 'exceptions.php';

$iPhpiCalLib_DataTypeIndex = 1;
define('PHPICALLIB_DATATYPE_XTYPE', $iPhpiCalLib_DataTypeIndex++);
define('PHPICALLIB_DATATYPE_IANATYPE', $iPhpiCalLib_DataTypeIndex++);
//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08
//     3.3.  Property Value Data Types . . . . . . . . . . . . . . . .  30
//       3.3.1.  Binary  . . . . . . . . . . . . . . . . . . . . . . .  31
define('PHPICALLIB_DATATYPE_BINARY', $iPhpiCalLib_DataTypeIndex++);
//       3.3.2.  Boolean . . . . . . . . . . . . . . . . . . . . . . .  31
define('PHPICALLIB_DATATYPE_BOOLEAN', $iPhpiCalLib_DataTypeIndex++);
//       3.3.3.  Calendar User Address . . . . . . . . . . . . . . . .  32
define('PHPICALLIB_DATATYPE_CAL_ADDRESS', $iPhpiCalLib_DataTypeIndex++);
//       3.3.4.  Date  . . . . . . . . . . . . . . . . . . . . . . . .  32
define('PHPICALLIB_DATATYPE_DATE', $iPhpiCalLib_DataTypeIndex++);
//       3.3.5.  Date-Time . . . . . . . . . . . . . . . . . . . . . .  33
define('PHPICALLIB_DATATYPE_DATE_TIME', $iPhpiCalLib_DataTypeIndex++);
//       3.3.6.  Duration  . . . . . . . . . . . . . . . . . . . . . .  36
define('PHPICALLIB_DATATYPE_DURATION', $iPhpiCalLib_DataTypeIndex++);
//       3.3.7.  Float . . . . . . . . . . . . . . . . . . . . . . . .  37
define('PHPICALLIB_DATATYPE_FLOAT', $iPhpiCalLib_DataTypeIndex++);
//       3.3.8.  Integer . . . . . . . . . . . . . . . . . . . . . . .  37
define('PHPICALLIB_DATATYPE_INTEGER', $iPhpiCalLib_DataTypeIndex++);
//       3.3.9.  Period of Time  . . . . . . . . . . . . . . . . . . .  38
define('PHPICALLIB_DATATYPE_PERIOD', $iPhpiCalLib_DataTypeIndex++);
//       3.3.10. Recurrence Rule . . . . . . . . . . . . . . . . . . .  39
define('PHPICALLIB_DATATYPE_RECUR', $iPhpiCalLib_DataTypeIndex++);
//       3.3.11. Text  . . . . . . . . . . . . . . . . . . . . . . . .  46
define('PHPICALLIB_DATATYPE_TEXT', $iPhpiCalLib_DataTypeIndex++);
//       3.3.12. Time  . . . . . . . . . . . . . . . . . . . . . . . .  48
define('PHPICALLIB_DATATYPE_TIME', $iPhpiCalLib_DataTypeIndex++);
//       3.3.13. URI . . . . . . . . . . . . . . . . . . . . . . . . .  50
define('PHPICALLIB_DATATYPE_URI', $iPhpiCalLib_DataTypeIndex++);
//       3.3.14. UTC Offset  . . . . . . . . . . . . . . . . . . . . .  50
define('PHPICALLIB_DATATYPE_UTC_OFFSET', $iPhpiCalLib_DataTypeIndex++);

/**
 * Base class for all PhpiCalLib data types
 *
 */
class PhpiCalLib_DataType {
	protected $DataType = 0;
	
	/**
	 * Regex to capture validate an iana-token
	 * @link http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.1
	 *      iana-token    = 1*(ALPHA / DIGIT / "-")
	 * 
	 * @var string
	 */
	public static $IanaTokenRegex = '[a-zA-Z0-9-]+';
	
	/**
	 * Map a data type id to it's string name.
	 *
	 * @var array Keyed by PHPICALLIB_DATATYPE_?, value is a string
	 */
	public static $TypeMap = array(
				PHPICALLIB_DATATYPE_BINARY => 'BINARY',
				PHPICALLIB_DATATYPE_BOOLEAN => 'BOOLEAN',
				PHPICALLIB_DATATYPE_CAL_ADDRESS => 'CAL-ADDRESS',
				PHPICALLIB_DATATYPE_DATE => 'DATE',
				PHPICALLIB_DATATYPE_DATE_TIME => 'DATE-TIME',
				PHPICALLIB_DATATYPE_DURATION => 'DURATION',
				PHPICALLIB_DATATYPE_FLOAT => 'FLOAT',
				PHPICALLIB_DATATYPE_INTEGER => 'INTEGER',
				PHPICALLIB_DATATYPE_PERIOD => 'PERIOD',
				PHPICALLIB_DATATYPE_RECUR => 'RECUR',
				PHPICALLIB_DATATYPE_TEXT => 'TEXT',
				PHPICALLIB_DATATYPE_TIME => 'TIME',
				PHPICALLIB_DATATYPE_URI => 'URI',
				PHPICALLIB_DATATYPE_UTC_OFFSET => 'UTC-OFFSET');
	// array_flip() on $TypeMap, built on first use.
	private static $ReverseTypeMap = null;
	
	/**
	 * Convert a data type name to it's defined type
	 *
	 * @param string $Name a "name" RFC2445 token
	 * @return int one of the PHPICALLIB_DATATYPE_* values
	 */
	static public function ToDataType($Name) {
		// Build the type map on first access
		if (!self::$ReverseTypeMap) {
			self::$ReverseTypeMap = array_flip(self::$TypeMap);
		}
		// If we have a key for this name, return the unique type
		if (isset(self::$ReverseTypeMap[$Name])) {
			return self::$ReverseTypeMap[$Name];
		}
		// It must be an iana defined extension or x-prop that we don't really recognize.
		if (preg_match('/^X-.*$/', $Name)) { 
			return PHPICALLIB_DATATYPE_XTYPE;
		}
		return PHPICALLIB_PROPERTY_IANATYPE;
	}
	
	/**
	 * Convert a data type to it's defined name
	 *
	 * @param integer $Type one of the PHPICALLIB_DATATYPE_* values
	 * @return string A "name" RFC2445 token
	 */
	static public function FromDataType($Type) {
		if (empty(self::$TypeMap[$Type])) return '';
		return self::$TypeMap[$Type];
	}
}

/**
 * The date data type.
 * <pre>
 *        date               = date-value
 *
 *        date-value         = date-fullyear date-month date-mday
 *        date-fullyear      = 4DIGIT
 *        date-month         = 2DIGIT        ;01-12
 *        date-mday          = 2DIGIT        ;01-28, 01-29, 01-30, 01-31
 * </pre>
 * @link http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.3.4
 */
class PhpiCalLib_DataTypes_Date extends PhpiCalLib_DataType {
	// The data
	private $Data = array(
				'Year' => 0,
				'Month' => 1,
				'MonthDay' => 1);

	/**
	 * Constructor
	 *
	 */
	public function __construct() {
		$this->DataType = PHPICALLIB_DATATYPE_DATE;
	}
	
	/**
	 * Set the date-fullyear component of the date-value
	 *
	 * @param number $Number
	 */
	public function SetFullYear($Number) {
		// Do a string check
		$Pattern = '/^\d?\d?\d?\d$/D';
		if (!preg_match($Pattern, $Number)) {
			throw new PhpiCalLib_ParameterException(sprintf("Invalid date-fullyear (%s) value: %d", $Pattern, $Number));
		}
		$this->Data['Year'] = $Number;
	}
	
	/**
	 * Access the date-fullyear
	 *
	 * @return integer
	 */
	public function GetFullYear() {
		return $this->Data['Year'];
	}

	/**
	 * Set the date-fullyear component of the date-value
	 *
	 * @param number $Number A number of years since 1900
	 */
	public function SetYear($Number) {
		if (!is_numeric($Number)) {
			throw new PhpiCalLib_ParameterException(sprintf("Invalid non integer date-fullyear value: %d", $Number));
		}
		$this->SetFullYear($Number + 1900);
	}
	
	/**
	 * Set the date-month component of the date-value
	 *
	 * @param number $Number
	 */
	public function SetMonth($Number) {
		// Must be numeric
		$this->DigitCheck($Number, 'date-month', 1, 12);
		$this->Data['Month'] = $Number;
	}
	
	/**
	 * Access the date-month
	 *
	 * @return integer
	 */
	public function GetMonth() {
		return $this->Data['Month'];
	}
	
	/**
	 * Set the date-mday component of the date-value
	 *
	 * @param number $Number
	 */
	public function SetMonthDay($Number) {
		// Must be numeric
		$this->DigitCheck($Number, 'date-mday', 1, 31);
		$this->Data['MonthDay'] = $Number;
	}
	
	/**
	 * Access the date-mday
	 *
	 * @return integer
	 */
	public function GetMonthDay() {
		return $this->Data['MonthDay'];
	}

	/**
	 * Decode a timestamp to an array of values like localtime() does, but with support for UTC
	 * 
	 * @param integer $TimeStamp A unix timestamp
	 * @param bool $Utc true to extract the date WRT to utc, false to extract the "local time"
	 */
	static public function DecodeTimeStamp($TimeStamp, $Utc) {
		// If we have to extract the local date
		if (!$Utc) {
			// Then it's easy
			return localtime($TimeStamp);
		}
		$aTime = array();
		{
			// Else as timestamps are always WRT to UTC, in order to use the localtime() to decode, 
			// we temporarily set the local timezone to UTC
			$TimeZone = date_default_timezone_get();
			try {
				date_default_timezone_set('UTC');
				$aTime = localtime($TimeStamp);
			} catch (Exception $E) {
				date_default_timezone_set($TimeZone);
				throw $E;
			}
			date_default_timezone_set($TimeZone);
		}
		return $aTime;
	}

	/**
	 * Extract the date from the given timestamp
	 *
	 * @param integer $TimeStamp Unix epoch WRT to UTC
	 * @param bool $Utc true to extract the date WRT to utc, false to extract the "local time"
	 * 	 */
	public function FromTimeStamp($TimeStamp, $Utc) {
		$aTime = self::DecodeTimeStamp($TimeStamp, $Utc);
		// Set the attributes
		$this->SetMonthDay($aTime[3]);
		$this->SetMonth($aTime[4] + 1);
		$this->SetYear($aTime[5]);
	}

	/**
	 * Return the date WRT to midnight, UTC
	 * 
	 * @return integer Seconds since January 1 1970 00:00:00 GMT
	 */
	public function ToTimeStamp() {
		// int gmmktime ( [int hour [, int minute [, int second [, int month [, int day [, int year [, int is_dst]]]]]]] )
		return gmmktime(0,0,0,$this->Data['Month'], $this->Data['MonthDay'], $this->Data['Year']);
	}
	
	/**
	 * Check that the Number matches 2DIGIT and is in range
	 *
	 * @param string $Number The proposed number
	 * @param string $TokenName
	 * @param integer $MinValue The minimum legitimate value
	 * @param integer $MaxValue The maximum legitimate value
	 */
	public static function DigitCheck($Number, $TokenName, $MinValue, $MaxValue) {
		// Do a string check
		$Pattern = '/^\d?\d$/D';
		if (!preg_match($Pattern, $Number)) {
			throw new PhpiCalLib_ParameterException(sprintf("Invalid %s (%s) value: %d", $TokenName, $Pattern, $Number));
		}
		if ($Number < $MinValue) {
			throw new PhpiCalLib_ParameterException(sprintf("Invalid %s value, must be larger than %d: %d", $TokenName, $MinValue, $Number));
		}
		if ($Number > $MaxValue) {
			throw new PhpiCalLib_ParameterException(sprintf("Invalid %s value, must be less than %d: %d", $TokenName, $MaxValue, $Number));
		}
	}

	/**
	 * Extract the date value from the encoded string
	 *
	 * @param string $EncodedString
	 */
	public function Parse($EncodedString) {
		//        date               = date-value
		//
		//        date-value         = date-fullyear date-month date-mday
		//        date-fullyear      = 4DIGIT
		//        date-month         = 2DIGIT        ;01-12
		//        date-mday          = 2DIGIT        ;01-28, 01-29, 01-30, 01-31
		$Pattern = '/^(\d\d\d\d)(\d\d)(\d\d)$/';
		$aMatches = array();
		if (!preg_match($Pattern, $EncodedString, $aMatches)) {
			throw new PhpiCalLib_ParameterException(sprintf("Invalid date (%s) in: %s", $Pattern, $EncodedString));
		}
		$this->SetFullYear($aMatches[1]);
		$this->SetMonth($aMatches[2]);
		$this->SetMonthDay($aMatches[3]);
	}
	
	/**
	 * Convert to string
	 *
	 * @return string
	 */
	public function ToString() {
		return sprintf("%04d%02d%02d", $this->Data['Year'], $this->Data['Month'], $this->Data['MonthDay']);
	}
}

/**
 * The date-time data type.
 * <pre>
 *        date-time  = date "T" time ;As specified in the date and time
 *                                   ;value definitions
 * </pre>
 * @link http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.3.5
 */
class PhpiCalLib_DataTypes_DateTime extends PhpiCalLib_DataType {
	private $Time = null;
	private $Date = null;
	
	public function __construct() {
		$this->Type = PHPICALLIB_DATATYPE_DATE_TIME;
		$this->Time = new PhpiCalLib_DataTypes_Time();
		$this->Date = new PhpiCalLib_DataTypes_Date();
	}

	/**
	 * @return PhpiCalLib_DataTypes_Date
	 */
	public function GetDate() {
		return $this->Date;
	}

	/**
	 * @param PhpiCalLib_DataTypes_Date $Date
	 */
	public function SetDate($Date) {
		$this->Date = $Date;
	}

	/**
	 * @return PhpiCalLib_DataTypes_Time
	 */
	public function GetTime() {
		return $this->Time;
	}
	
	/**
	 * @param PhpiCalLib_DataTypes_Time $Time
	 */
	public function SetTime($Time) {
		$this->Time = $Time;
	}

	/**
	 * Return the time WRT to midnight Jan 1st 1970, UTC
	 * 
	 * @return integer Seconds since January 1 1970 00:00:00 GMT
	 */
	public function ToTimeStamp() {
		return $this->Time->ToTimeStamp() + $this->Date->ToTimeStamp();
	}
	
	/**
	 * Set the time from a unix timestamp
	 *
	 * @param number $Time
	 * @param bool $Utc true to store in utc, false to store in "floating time", TZ specified by the TZID parameter
	 */
	public function FromTimeStamp($Time, $Utc) {
		$aTime = PhpiCalLib_DataTypes_Date::DecodeTimeStamp($Time, $Utc);
		$this->Time->SetSeconds($aTime[0]);
		$this->Time->SetMinutes($aTime[1]);
		$this->Time->SetHours($aTime[2]);
		$this->Time->SetUtc($Utc);
		$this->Date->SetMonthDay($aTime[3]);
		$this->Date->SetMonth($aTime[4] + 1);
		$this->Date->SetYear($aTime[5]);
	}
	
	/**
	 * Extract the date-time value from the encoded string
	 *
	 * @param string $EncodedString
	 */
	public function Parse($EncodedString) {
		//        date-time  = date "T" time ;As specified in the date and time
		//                                   ;value definitions		
		$Pattern = '/^(.*)T(.*)$/';
		$aMatches = array();
		if (!preg_match($Pattern, $EncodedString, $aMatches)) {
			throw new PhpiCalLib_ParameterException(sprintf("Invalid date-time (%s) in: %s", $Pattern, $EncodedString));
		}
		$this->Date->Parse($aMatches[1]);
		$this->Time->Parse($aMatches[2]);
	}
	
	/**
	 * Convert to string
	 *
	 * @return string
	 */
	public function ToString() {
		return sprintf("%sT%s", $this->Date->ToString(), $this->Time->ToString());
	}
}

/**
 * The dur-value data type.
 * <pre>
 *        dur-value  = (["+"] / "-") "P" (dur-date / dur-time / dur-week)
 *
 *        dur-date   = dur-day [dur-time]
 *        dur-time   = "T" (dur-hour / dur-minute / dur-second)
 *        dur-week   = 1*DIGIT "W"
 *        dur-hour   = 1*DIGIT "H" [dur-minute]
 *        dur-minute = 1*DIGIT "M" [dur-second]
 *        dur-second = 1*DIGIT "S"
 *        dur-day    = 1*DIGIT "D"
 * </pre>
 * @link http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.3.6
 */
class PhpiCalLib_DataTypes_Duration extends PhpiCalLib_DataType {
	private $Polarity = 1;
	private $Data = array();

	/**
	 * Constructor
	 *
	 */
	public function __construct() {
		$this->DataType = PHPICALLIB_DATATYPE_DURATION;
	}
	
	/**
	 * Set the dur-second component of the dur-value
	 *
	 * @param number $Number
	 */
	public function SetSeconds($Number) {
		// Must be numeric
		$this->DigitCheck($Number, 'dur-second');
		$this->Data['Second'] = $Number;
		// This also unsets the weeks
		unset($this->Data['Week']);
	}
	
	/**
	 * Access the dur-second
	 *
	 * @return integer
	 */
	public function GetSeconds() {
		if (!isset($this->Data['Second'])) return 0;
		return $this->Data['Second'];
	}
	
	/**
	 * Set the dur-minute component of the dur-value
	 *
	 * @param number $Minutes
	 */
	public function SetMinutes($Number) {
		// Must be numeric
		$this->DigitCheck($Number, 'dur-minute');
		$this->Data['Minute'] = $Number;
		// This also unsets the weeks
		unset($this->Data['Week']);
	}

	/**
	 * Access the dur-minute
	 *
	 * @return integer
	 */
	public function GetMinutes() {
		if (!isset($this->Data['Minute'])) return 0;
		return $this->Data['Minute'];
	}
	
	/**
	 * Set the dur-hour component of the dur-value
	 *
	 * @param number $Hours
	 */
	public function SetHours($Number) {
		// Must be numeric
		$this->DigitCheck($Number, 'dur-hour');
		$this->Data['Hour'] = $Number;
		// This also unsets the weeks
		unset($this->Data['Week']);
	}
	
	/**
	 * Access the dur-hour
	 *
	 * @return integer
	 */
	public function GetHours() {
		if (!isset($this->Data['Hour'])) return 0;
		return $this->Data['Hour'];
	}

	/**
	 * Set the dur-day component of the dur-value
	 *
	 * @param number $Days
	 */
	public function SetDays($Number) {
		// Must be numeric
		$this->DigitCheck($Number, 'dur-day');
		$this->Data['Day'] = $Number;
		// This also unsets the weeks
		unset($this->Data['Week']);
	}
	
	/**
	 * Access the dur-day
	 *
	 * @return integer
	 */
	public function GetDays() {
		if (!isset($this->Data['Day'])) return 0;
		return $this->Data['Day'];
	}
	
	/**
	 * Set the dur-week component of the dur-value
	 *
	 * @param number $Week
	 */
	public function SetWeeks($Number) {
		// Must be numeric
		$this->DigitCheck($Number, 'dur-week');
		$this->Data['Week'] = $Number;
		// This also unsets the other attributes
		unset($this->Data['Day']);
		unset($this->Data['Hour']);
		unset($this->Data['Minute']);
		unset($this->Data['Second']);
	}
	
	/**
	 * Access the dur-week
	 *
	 * @return integer
	 */
	public function GetWeeks() {
		if (!isset($this->Data['Week'])) return 0;
		return $this->Data['Week'];
	}

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

	/**
	 * @param bool $Polarity
	 */
	public function SetPolarity($Polarity) {
		$this->Polarity = ($Polarity != false);
	}

	
	/**
	 * Check that the Number matches 1*DIGIT
	 *
	 * @param string $Number The proposed number
	 * @param string $TokenName
	 */
	private function DigitCheck($Number, $TokenName) {
		// If it's an integer, it's automatically safe
		if (is_int($Number)) {
			if ($Number < 0) {
				throw new PhpiCalLib_ParameterException(sprintf("Invalid %s (negative) value: %d", $TokenName, $Number));
			}
			return;
		}
		// Do a string check
		$Pattern = '/^\d+$/D';
		if (!preg_match($Pattern, $Number)) {
			throw new PhpiCalLib_ParameterException(sprintf("Invalid %s (%s) value: %d", $TokenName, $Pattern, $Number));
		}
	}
	
	/**
	 * Convert to string
	 *
	 * @return string
	 */
	public function ToString() {
		$Result = '';
		// Start with polarity
		$Result .= ($this->Polarity ? '' : '-');
		$Result .= 'P';
		// First preference is weeks
		if (!empty($this->Data['Week'])) {
			return sprintf("%s%dW", $Result, $this->Data['Week']);
		}
		
		// Next add days
		if (!empty($this->Data['Day'])) {
			$Result .= sprintf("%dD",$this->Data['Day']);
			// If there's no time, we're done.
			if (empty($this->Data['Hour'])
					&& empty($this->Data['Minute'])
					&& empty($this->Data['Second'])) {
				// Return the duration in days
				return $Result;
			}
		}
		
		// Next add time
		$Result .= 'T';
		if (!empty($this->Data['Hour'])) {
			$Result .= sprintf("%dH",$this->Data['Hour']);
		}
		if (!empty($this->Data['Minute'])) {
			$Result .= sprintf("%dM",$this->Data['Minute']);
		}
		if (!empty($this->Data['Second'])) {
			$Result .= sprintf("%dS",$this->Data['Second']);
		}
		// Check we aren't about to return an empty string
		if ($Result == 'PT') $Result .= '0S';
		
		// All done!
		return $Result;
	}
}

/**
 * The text data type.
 * <pre>
 *         text       = *(TSAFE-CHAR / ":" / DQUOTE / ESCAPED-CHAR)
 *           ; Folded according to description above
 *
 *        ESCAPED-CHAR = ("\\" / "\;" / "\," / "\N" / "\n")
 *           ; \\ encodes \, \N or \n encodes newline
 *           ; \; encodes ;, \, encodes ,
 *
 *        TSAFE-CHAR = %x20-21 / %x23-2B / %x2D-39 / %x3C-5B /
 *                     %x5D-7E / NON-US-ASCII
 *           ; Any character except CTLs not needed by the current
 *           ; character set, DQUOTE, ";", ":", "\", ","
 * </pre>
 * @link http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.3.11
 */
class PhpiCalLib_DataTypes_Text extends PhpiCalLib_DataType {
	/**
	 * The text value
	 *
	 * @var string
	 */
	private $Value = '';
	
	/**
	 * Constructor
	 *
	 */
	public function __construct() {
		$this->DataType = PHPICALLIB_DATATYPE_TEXT;
	}
	
	/**
	 * Set the value
	 *
	 * @param string $Text
	 */
	public function SetValue($Text) {
		//http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.3.11
		//        text       = *(TSAFE-CHAR / ":" / DQUOTE / ESCAPED-CHAR)
		//           ; Folded according to description above
		//
		//        ESCAPED-CHAR = ("\\" / "\;" / "\," / "\N" / "\n")
		//           ; \\ encodes \, \N or \n encodes newline
		//           ; \; encodes ;, \, encodes ,
		//
		//        TSAFE-CHAR = %x20-21 / %x23-2B / %x2D-39 / %x3C-5B /
		//                     %x5D-7E / NON-US-ASCII
		//           ; Any character except CTLs not needed by the current
		//           ; character set, DQUOTE, ";", ":", "\", ","
		// Due to the quoting, this means that you can represent these characters:
		//        text       = *(%x20-21 / %x23-2B / %x2D-39 / %x3C-5B /
		//                     %x5D-7E / %x3A / %x22 / %x5C / %3B / %x2C / %x0A)
		//        text       = *(%x0A / %x20-21 / %x22 /%x23-2B / %x2C /%x2D-39 / %x3A / %3B / 
		//                     %x3C-5B / %x5C / %x5D-7E )
		//        text       = *(%x0A / %x20-7E )
		// And I'm going to add \t, as I think that was meant too
		$Pattern = '/^[\x09\x0A\x20-\x7E]*$/D';
		if (!preg_match($Pattern, $Text)) {
			$aTokens = preg_split('/[^\x09\x0A\x20-\x7E]/', $Text, -1, PREG_SPLIT_NO_EMPTY);
			throw new PhpiCalLib_ParameterException(sprintf('Invalid text (%s) token, invalid chars between \'%s\': %s', 
								$Pattern, implode(',',$aTokens), $Text));
		}
		$this->Value = $Text;
	}
	
	/**
	 * Access the unescaped text value
	 *
	 * @return string
	 */
	public function GetValue() {
		return $this->Value;
	}
	
	/**
	 * Extract the text value from the encoded string
	 *
	 * @param string $EncodedString
	 */
	public function Parse($EncodedString) {
		// Check it matches the text syntax
		// Note I've added support for \x09, \t and \", which aren't strictly in the spec
		$Pattern = '/^([\x09\x20-\x21\x23-\x2B\x2D-\x39\x3C-\x5B\x5D-\x7E:"]|\\\\\\\\|\\\\;|\\\\,|\\\\N|\\\\n|\\\\t|\\\\")*$/D';
		if (!preg_match($Pattern, $EncodedString)) {
			// It make have contained some escaped chars.
			$aTokens = preg_split('/[^\x09\x20-\x21\x23-\x2B\x2D-\x39\x3C-\x5B\x5D-\x7E:"]/', $EncodedString, -1, PREG_SPLIT_NO_EMPTY);
			throw new PhpiCalLib_ParseException(sprintf("Invalid text (%s) token between these tokens '%s' in: %s", $Pattern, implode("','", $aTokens), $EncodedString));
		}
		// Extract the text-value
		$TextValue = $EncodedString;
		$TextValue = str_replace('\\t', "\t",$TextValue);
		$TextValue = str_replace('\"', '"',$TextValue);
		$TextValue = str_replace("\\n", "\n",$TextValue);
		$TextValue = str_replace("\\N", "\n",$TextValue);
		$TextValue = str_replace("\\,", ",",$TextValue);
		$TextValue = str_replace("\\;", ";",$TextValue);
		$TextValue = str_replace("\\\\", "\\",$TextValue);
		$this->Value = $TextValue;
	}
	
	/**
	 * Convert to an encoded string
	 *
	 * @return string
	 */
	public function ToString() {
		// Encode to a "value" token
		$Text = $this->Value;
		$Text = str_replace("\\", "\\\\",$Text);
		$Text = str_replace("\n", "\\n",$Text);
		$Text = str_replace(",", "\\,",$Text);
		$Text = str_replace(";", "\\;",$Text);
		return $Text;
	}
	
}

/**
 * The time data type.
 * <pre>
 *        time         = time-hour time-minute time-second [time-utc]
 *
 *        time-hour    = 2DIGIT        ;00-23
 *        time-minute  = 2DIGIT        ;00-59
 *        time-second  = 2DIGIT        ;00-60
 *        ;The "60" value is used to account for positive "leap" seconds.
 *
 *        time-utc     = "Z"
 * @link http://tools.ietf.org/html/draft-ietf-calsify-rfc2445bis-08#section-3.3.12
 */
class PhpiCalLib_DataTypes_Time extends PhpiCalLib_DataType {
	// If this object is in Utc
	private $Utc = false;
	// The data
	private $Data = array(
				'Hour' => 0,
				'Minute' => 0,
				'Second' => 0);

	/**
	 * Constructor
	 *
	 */
	public function __construct() {
		$this->DataType = PHPICALLIB_DATATYPE_TIME;
	}
	
	/**
	 * Set the time-second component of the time
	 *
	 * @param number $Number
	 */
	public function SetSeconds($Number) {
		// Must be numeric
		PhpiCalLib_DataTypes_Date::DigitCheck($Number, 'time-second', 0, 60);
		$this->Data['Second'] = (int)$Number;
	}
	
	/**
	 * Access the number of seconds
	 *
	 * @return integer
	 */
	public function GetSeconds() {
		return (int)$this->Data['Second'];
	}
	
	/**
	 * Set the time-minute component of the time
	 *
	 * @param number $Number
	 */
	public function SetMinutes($Number) {
		// Must be numeric
		PhpiCalLib_DataTypes_Date::DigitCheck($Number, 'time-minute', 0, 59);
		$this->Data['Minute'] = $Number;
	}

	/**
	 * Access the number of minutes
	 *
	 * @return integer
	 */
	public function GetMinutes() {
		return $this->Data['Minute'];
	}
	
	/**
	 * Set the time-hour component of the time
	 *
	 * @param number $Number
	 */
	public function SetHours($Number) {
		// Must be numeric
		PhpiCalLib_DataTypes_Date::DigitCheck($Number, 'time-hour', 0, 23);
		$this->Data['Hour'] = $Number;
	}
	
	/**
	 * Access the number of hours
	 *
	 * @return integer
	 */
	public function GetHours() {
		return $this->Data['Hour'];
	}

	/**
	 * Set the time-utc component of the time
	 *
	 * @param bool $value True for Utc, else false
	 */
	public function SetUtc($value) {
		$this->Utc = ($value != false);
	}
	
	/**
	 * Determine if the time is UTC or floating/local
	 *
	 * @return bool
	 */
	public function GetUtc() {
		return $this->Utc;
	}

	/**
	 * Extract the date from the given timestamp
	 *
	 * @param integer $TimeStamp Unix epoch WRT to UTC
	 * @param bool $Utc true to extract the date WRT to utc, false to extract the "local time"
	 * 	 */
	public function FromTimeStamp($TimeStamp, $Utc) {
		$aTime = PhpiCalLib_DataTypes_Date::DecodeTimeStamp($TimeStamp, $Utc);
		// Set the attributes
		$this->SetSeconds($aTime[0]);
		$this->SetMinutes($aTime[1]);
		$this->SetHours($aTime[2]);
		$this->SetUtc($Utc);
	}

	/**
	 * Return the seconds from midnight on the same day
	 * 
	 * @return integer Seconds since January 1 1970 00:00:00 GMT
	 */
	public function ToTimeStamp() {
		// int gmmktime ( [int hour [, int minute [, int second [, int month [, int day [, int year [, int is_dst]]]]]]] )
		return gmmktime($this->Data['Hour'],$this->Data['Minute'],$this->Data['Second'],1,1,1970);
	}
	
	/**
	 * Extract the date-time value from the encoded string
	 *
	 * @param string $EncodedString
	 */
	public function Parse($EncodedString) {
		//        time         = time-hour time-minute time-second [time-utc]
		//        time-hour    = 2DIGIT        ;00-23
		//        time-minute  = 2DIGIT        ;00-59
		//        time-second  = 2DIGIT        ;00-60
		//        time-utc     = "Z"
		$Pattern = '/^(\d\d)(\d\d)(\d\d)(Z?)$/';
		$aMatches = array();
		if (!preg_match($Pattern, $EncodedString, $aMatches)) {
			throw new PhpiCalLib_ParameterException(sprintf("Invalid date-time (%s) in: %s", $Pattern, $EncodedString));
		}
		$this->SetHours($aMatches[1]);
		$this->SetMinutes($aMatches[2]);
		$this->SetSeconds($aMatches[3]);
		$this->SetUtc(!empty($aMatches[4]));
	}

	/**
	 * Convert to string
	 *
	 * @return string
	 */
	public function ToString() {
		return sprintf("%02d%02d%02d%s", $this->Data['Hour'], $this->Data['Minute'], $this->Data['Second'], $this->Utc ? 'Z' : '');
	}
}
?>
Return current item: PhpiCalLib