Date Intervals in PHP 5.2

PHP provides a very useful DateTime class, and the related DateTimeZone, and DateInterval classes. (Most of) These have proven very useful when developing my plug-in Event Organiser, in particular making date generation from complex patterns fairly painless. However there is one problem = some of the more advanced methods of DateTime, and the entire DateInterval class are only supported in PHP 5.3. Quite a lot of websites are still hosted on the old PHP 5.2.

This means I’ve had to produce work-arounds in the cases where Event Organiser is run on PHP 5.2.

DateInterval

With events you might also want to describe how long the event is. In PHP 5.3 this is easy:

$date1 = new DateTime('2012-10-28 00:00:00',new DateTimeZone('Europe/London'));
$date2 = new DateTime('2012-10-28 03:00:00',new DateTimeZone('Europe/London'));
$interval = date_diff($date1, $date2);
$format = '%y years, %m months, %d days, %h hours, %i minutes, %s seconds, %a total days %R';
echo $interval->format($format);

You use the date_diff function which returns a DateInterval object. This can be formatted into string using any of the available place-holders.

Work around for PHP 5.2

It would be great to replicate this for PHP 5.2 – and it’s actually quite easy. We’ll produce a function sh_date_interval($date1,$date2,$format) which should return (almost!) the same as $interval->format($format) above.

To this, we’ll take the earlier date and increment by one year until it’s after our later date. We’ll then reverse the last increment (so the modified date is now less than a year before the later date) and record how many increments. This tells us how many years apart the dates are. We do this for months, days and hours.

For minutes and seconds do something different. There are a fixed number of seconds in a minute, and fixed number of minutes in an hour. Consequently rather than a while loop of an extra ~120 loops we just use simple calculations.

Note: Incrementing 2012-01-31 (1st January) by one month gives 2012-03-02 (2nd March). So these dates are a month apart. Incrementing 2012-02-29 (29th February) by a month gives 2012-03-29 (29th March) – so the 29th February and 31st March are 1 month and 2 days apart. This is expected behaviour and mimics that of DateInterval. The function handles leap years in the same way as DateInterval.

Note 2: The function does not handle daylight savings in in the same way as DateInterval. If at 01:00 you are to move the clocks forward to 02:00, DateInterval says that there are three hours between 00:00 and 03:00. The below function says there are two (which I believe to be correct – at least in human terms).

Note 3: I’m using this in a WordPress context – so I’m using the function zeroise which is not a native PHP function.

function sh_date_interval($_date1,$_date2, $format){

    //Make sure $date1 is ealier
    $date1 = ($_date1 <= $_date2 ? $_date1 : $_date2);
    $date2 = ($_date1 <= $_date2 ? $_date2 : $_date1);

    //Calculate R values
    $R = ($_date1 <= $_date2 ? '+' : '-');
    $r = ($_date1 <= $_date2 ? '' : '-');

    //Calculate total days
    $total_days = round(abs($date1->format('U') - $date2->format('U'))/86400);

    //A leap year work around - consistent with DateInterval
    $leap_year = ( $date1->format('m-d') == '02-29' ? true : false);
    if( $leap_year ){
        $date1->modify('-1 day');
    }

    $periods = array( 'years'=>-1,'months'=>-1,'days'=>-1,'hours'=>-1);

    foreach ($periods as $period => &$i ){

        if($period == 'days' && $leap_year )
            $date1->modify('+1 day');

        while( $date1 <= $date2 ){
            $date1->modify('+1 '.$period);
            $i++;
        }

        //Reset date and record increments
        $date1->modify('-1 '.$period);
    }
    extract($periods);

    //Minutes, seconds
    $seconds = round(abs($date1->format('U') - $date2->format('U')));
    $minutes = floor($seconds/60);
    $seconds = $seconds - $minutes*60;

    $replace = array(
        '/%y/' => $years,
        '/%Y/' => zeroise($years,2),
        '/%m/' => $months,
        '/%M/' => zeroise($months,2),
        '/%d/' => $days,
        '/%D/' => zeroise($days,2),
        '/%a/' => zeroise($total_days,2),
        '/%h/' => $hours,
        '/%H/' => zeroise($hours,2),
        '/%i/' => $minutes,
        '/%I/' => zeroise($minutes,2),
        '/%s/' => $seconds,
        '/%S/' => zeroise($seconds,2),
        '/%r/' => $r,
        '/%R/' => $R
    );

    return preg_replace(array_keys($replace), array_values($replace), $format);
}

Testing it out

The following dates are chosen to provide an example of leap year and daylight savings. Clocks are due to go forward an hour on Sunday, 31 March 2013, 01:00.

$date1 = new DateTime('2012-02-29 00:00:00',new DateTimeZone('Europe/London'));
$date2 = new DateTime('2013-03-31 03:00:00',new DateTimeZone('Europe/London'));

$interval = date_diff($date1, $date2);
$format = '%y year, %m month, %d day, %h hours, %i minutes, %s seconds, %a total days';
echo $interval->format($format); 
echo sh_date_interval($date1,$date2,$format); 

    /*
    * Prints
    * 1 year, 1 month, 2 day, 3 hours, 0 minutes, 0 seconds, 396 total days
    * 1 year, 1 month, 2 day, 2 hours, 0 minutes, 0 seconds, 396 total days
    */