How not to use dates in PHP

How many times did you find yourself using basic arithmetic operations to make a range of UNIX timestamps, given that you know the ends of the interval? I'm talking about something like this:

<?php

date_default_timezone_set('Europe/Bucharest');

$start = strtotime('01 October 2008');
$end   = strtotime('01 November 2008');

$next = $start;
while ($next <= $end) {
    echo date('Y M d', $next) ,'<br>';
    $next += 60*60*24;
}

If you run the above little script you might be surprised to see that October 26th appears twice, one after the other.

If you're not aware of Daylight Saving Time you may have hard times understanding what's happening behind the scenes and solving the problem. In the above snippet I started the script by specifying that PHP's all date/time functions should use Bucharest's timezone. That means that my date() call in the for loop would know that October 26th, 2008 is officially marked as the end of Daylight Saving Time for Bucharest (as well as whole Europe). In that day 04:00 AM became 03:00 AM, "rewinding" time one hour and passing from EEST to EET.

So how can only one hour duplicate a day? Well, by observing DST, one day in year gets one hour longer, while another day gets one hour shorter. October 26th, 2008 is the day that got one hour longer, counting a total of 25 hours, while our script is adding 24 hours for each iteration. That's why, when the counter is supposed to return a UNIX timestamp for October 27th, 2008 it actually returns the timestamp for October 26, 2008 23:00:00, because now it's one hour behind DST. Here's a modified script to show exactly this transition:

<?php

date_default_timezone_set('Europe/Bucharest');

$start = strtotime('01 October 2008');
$end   = strtotime('01 November 2008');

$next = $start;
while ($next <= $end) {
    echo $next ,' ';
    echo date('Y M d H:i:s', $next) ,'<br>';
    $next += 60*60*24;
}

echo '<hr>';
$hours_in_26 = (strtotime('27 October 2008') - strtotime('26 October 2008'));
$hours_in_26 = $hours_in_26 / 60 / 60;
echo "October 26th, has $hours_in_26 hours when observing DST.";

I've also made a little diagram to highlight the problem. On the left hand side is the way date() function works when we've set a timezone value. On the right hand side is the way our script works, by treating all days as having only 24 hours. The green rectangle is that one hour difference between our script and date(). The red hours is when DST ends.

DST aware versus non DST aware

Now that we know the problem we should fix it and the most simple solution is to change the timezone. Instead of Europe/Bucharest I could have been used GMT and the above script had been worke well:

<?php

date_default_timezone_set('GMT');

Unfortunately, using GMT is not always an option, as you want to display users valid dates for their territories, that is, you want localization (L10N). The other solution, which is better, is to not rely on arithmetic for such calculations. You're better off using mktime() which, as any other date/time function, is DST aware.

<?php

date_default_timezone_set('Europe/Bucharest');

$start = strtotime('01 October 2008');
$end   = strtotime('01 November 2008');

$next = $start;
while ($next <= $end) {
    echo date('Y M d H:i:s', $next) ,'<br>';

    list($month, $day, $year) = explode('-', date('n-j-Y', $next));
    $next = mktime(0, 0, 0, $month, $day + 1, $year);
}

By the way, changing the timezone identifier to "Asia/Jerusalem" would double October 5th, 2008 in the first version of the script. You may find more about these on timeanddate.com.