LAMPDev

~/blog / dont-add-86400-seconds.md

Don't add 86,400 seconds to get the next day

It looks harmless: a day has 86,400 seconds, so to loop over a range of days, keep adding 86,400 to a timestamp.

date_default_timezone_set('Europe/Berlin');

$date = strtotime('2009-10-23 00:00:00');
$end  = strtotime('2009-10-26 00:00:00');

while ($date < $end) {
    echo date('Y-m-d', $date), PHP_EOL;
    $date += 86400;
}

Expected output:

2009-10-23
2009-10-24
2009-10-25

Actual output:

2009-10-23
2009-10-24
2009-10-25
2009-10-25

On 25 October 2009, clocks in Europe went back one hour when daylight saving time ended, so that day was 25 hours long. Midnight plus 86,400 seconds lands at 23:00 on the same calendar day. The day is printed twice, and from then on every timestamp is one hour off. In spring the opposite happens: the day is 23 hours long, and a day can be skipped.

The bug only appears in time zones with daylight saving time, and only two nights a year. That is why it so often survives testing and shows up in production reports.

Do this instead

Let PHP do calendar arithmetic. Add a calendar day, not a fixed number of seconds:

$period = new DatePeriod(
    new DateTimeImmutable('2009-10-23', new DateTimeZone('Europe/Berlin')),
    new DateInterval('P1D'),
    new DateTimeImmutable('2009-10-26', new DateTimeZone('Europe/Berlin'))
);

foreach ($period as $day) {
    echo $day->format('Y-m-d'), PHP_EOL;
}

Or, for a single step:

$next = $date->modify('+1 day');   // with DateTimeImmutable

Two more rules prevent most date bugs:

  • Store timestamps in UTC and convert to the user's time zone only when displaying them.
  • Never assume that a day has 24 hours, a month has 30 days or a year has 365 days. Use DateInterval, DatePeriod or a library such as Carbon.