Simple wrapper class for PEAR Date

Handling date and time in PHP makes most coders nervous. There are a bunch of functions playing arround with system time and timezone features, using UNIX timestamps and therefore are very limitted in application. At least I know a lot of guys born before 1970. Native Unix timestamps may be of great use for system level stuff like logging information or handling object lifetime, but not for real world scenarios: handling user age or historical events is impossible with 32 bit UNIX timestamps.

I've been searching for an alternative way of handling time with PHP and doing the usual stuff in a convenient way. Adding a timespan to a time or date, calculating the delta of two timestamps, or just things like finding out how old somebody is... all this can cause headache and may make you read all those manual pages of PHP, MySQL (and whatever your standard works are) again and again.

Today I've been playing arround with PEAR Date and PEAR Date_Span (both parts of the PEAR Date package) to get rid of the above nightmare lastingly. I ended up with a quite small but - as I think - really smart wrapper class for those PEAR classes.

My goal was to have a class that is able to represent any timestamp I'd ever like to use, but to extend it, so it

  • is capable of handling any date in the past or future,
  • gently converts to the common 0000-00-00 00:00:00 format,
  • doesn't depend on timezones and
  • can easyly be calculated with.

The theory is, time and date gets stored in $this->_Date in UTC format and all calculation are done on this value. When fetching the object like (string)$TimeStamp, $TimeStamp->__toString() returns the UTC value. To get the localized, just call for example $TimeStamp->getLocalized('UTC+2');

So finally the basic methods of my TimeStamp class are:

  • TimeStamp( [$time,$isUTC] ) //constructor
  • __toString() // magic __toString method
  • getLocalized( $timezone ) // get the localized timestamp
  • add(), sub() // add or sub time to the timestamp
  • getAge(), getAgeInXxx() // calculate the age in different flavors
  • and some output functions...

When instantiating an object of my TimeStamp class you have to decide, if the time you provide is already in UTC format. The reason is, when storing or reading TimeStamps from/to your database, a TimeStamp should have UTC format. If it doesn't the stored data would depend on your server's timezone and would become corrupt in case the server's timezone changes.

Therefore, when instantiating a TimeStamp object from data retrieved from the database, you'll most probably set $isUTC to TRUE, whilst when instantiating it with PHP data you'll set $isUTC to FALSE (or leave it empty) so the TimeStamp class will convert it to UTC for you.

Using the TimeStamp class you are able to say goodbye to MySQL (or whatever your server ist) automatic timestamps and set each timestamp explicitely when inserting datasets using PHP. You'll also be able to easily calculate with any TimeStamp or DateTime data you handle.

Here's the class code:

/**
* TimeStamp wrapping the PEAR Date class.
*/
class TimeStamp {                     
	/**
	* @var Date
	*/
	var $_Date = null;                  
 
	/**
	* @desc create a new TimeStamp for obj-instance fields
	* @param $time (optional) - leave blank to get NOW() timestamp, or pass in a unix timestamp or datetime string if you like
	* @param $isUTC (optional) - set TRUE if the passed in $time is UTC (e.g. comes from a DB field that's maintained in UTC)
	*/
	public function __construct( $time = null, $isUTC = false ){        
 
		if( !class_exists('Date') ){
			throw new Exception( 'PEAR Date not available' );
		}
 
		$this->_Date = new Date();
 
		if( !is_null($time) ){  
			$this->_Date->setDate( $time );
			if( $isUTC ){
				$this->_Date->setTZbyID( 'UTC' );     
			}  
		}        
 
		$this->_Date->toUTC();             
	}     
 
	public function __toString(){
		return $this->_Date->format( '%Y-%m-%d %H:%M:%S' ); // this is UTC, as we store it in server logs and the DB for example
	}
 
	public function getLocalized( $tz=null ){
 
		$rd = new Date( $this->_Date );
 
		if( $tz ){
			$rd->convertTZbyID( $tz ); // convert to the user's timezone... if none set, the system timezone will be used
		}else{
			$rd->convertTZ( Date_TimeZone::getDefault() ); // convert to the default system timezone
		}
 
		return $rd->format( '%Y-%m-%d %H:%M:%S' );
	}
 
	public function getDateString(){  
		global $global;     			
		return $this->_Date->format('%Y-%m-%d');
	}      
 
	public function getTimeString(){         
		global $global;     			
		return $this->_Date->format('%H:%i:%s');
	}   
 
	public function add( $Y=0, $M=0, $D=0, $h=0, $m=0, $s=0 ){			         			           
		$span = new Date_Span( "$Y-$M-$D $h:$m:$s" );											 
		$this->_Date->addSpan( $span );
	}      
 
	public function sub( $Y=0, $M=0, $D=0, $h=0, $m=0, $s=0 ){   			         			           
		$span = new Date_Span( "$Y-$M-$D $h:$m:$s" );											 
		$this->_Date->subtractSpan( $span );
	}
 
	public function getAge(){
		return $this->getAgeInYears();
	}     
 
	/**
	* Get the age of this date in years. This routine simply gets the
	* age in months, devides it by 12 and rounds down to whole years.
	*/
	public function getAgeInYears(){
		$months = $this->getAgeInMonths();
		$years = intval($months/12);
		return $years;
	}
 
	/**
	* Get the age of this date in months. The special thing is that a
	* monthely age is discrete by days. For example, on your birthday you
	* become one month or year older immediately, no matter if you were
	* born 1am or 11pm.
	* 
	* The special case of trespassing the turn of the year is implicitely
	* handled by adding the delta-months (dMonth) to the months of the
	* delta-years*12.
	*/
	public function getAgeInMonths(){
		$now = new Date();
		$now->toUTC();
		$dYear = $now->year - $this->_Date->year;
		$dMonth = $now->month - $this->_Date->month;
		$dDay = $now->day - $this->_Date->day;
 
		$months = $dYear*12 + $dMonth;
		if( $dDay<0 ){      
			$months--;
		}       
 
		return $months;
	}
 
	/**
	* All of the getAgeInXxx routines below just rely on the PEAR classes
	* Date and Date_Span. A Date_Span is created from $this->Date to
	* now (new Date() == now). Then the integer-part of Date_Span::toDays(),
	* toHours(), toMinutes() or toSeconds() is returned. Be aware we herein
	* are limited to 32 bit again and shouldn't getAgeInSeconds of
	* TimeStamps older than round about 63 years.
	*/
	public function getAgeInWeeks(){  
		$days = $this->getAgeInDays();                   
		return intval($days/7);
	}
 
	public function getAgeInDays(){   
		$span = new Date_Span();
		$span->setFromDateDiff( $this->_Date, new Date() );
		return intval($span->toDays()); 
	}
 
	public function getAgeInHours(){
		$span = new Date_Span();
		$span->setFromDateDiff( $this->_Date, new Date() );
		return intval($span->toHours());
	}
 
	public function getAgeInMinutes(){
		$span = new Date_Span();
		$span->setFromDateDiff( $this->_Date, new Date() );
		return intval($span->toMinutes());
	}  
 
	public function getAgeInSeconds(){
		$span = new Date_Span();
		$span->setFromDateDiff( $this->_Date, new Date() );
		return intval($span->toSeconds());
	}                             
}

The above code is less complicated than it looks on this page. Just copy it to your PHP IDE/editor and you'll see, it's actually very little code built arround the PEAR Date and Date_Span class.

An example of how to use it in combination with PHP and MySQL requests:

class ExampleClass {
	var TimeStamp;
	var PayLoad;
}
 
$object = new ExampleClass();
$object->PayLoad = "This is an example without sense.";
$object->TimeStamp = new TimeStamp( "1980-02-29", TRUE ); // UTC
echo $object->TimeStamp->getLocalized(); // system timezone!
echo $object->TimeStamp->getLocalized( 'UTC+2' ); // Central European Summer Time
// output:
// 1980-02-29 00:00:00
// 1980-02-29 02:00:00
 
// when not taking care of timezones, the timestamp will
// accept PHPs system timezone format as input and provide
// UTC to database and users, like that's the most neutral
// way to handle time!
$object->TimeStamp = new TimeStamp( "1980-02-29" ); // system timezone
echo $object->TimeStamp; // UTC!
// output (when system timezone is UTC+2):
// 1980-02-28 22:00:00
 
// To build an SQL statement using UTC format
// just use the TimeStamp as a string:
$sql = "INSERT INTO table SET TimeStamp=".addslashes($object->TimeStamp); 

As you can see in the above example, the TimeStamp class allows using time in a very natural and convenient way. Also you will no more have to care about timezones or portability of database content. I use the class for a private PHP framework that is currently under development.

All code presented in this article is free to use for everybody. If you like it, please drop a comment and consider linking this article.