PHP: How do you calculate the days between two dates?
The answer is one line. What is interesting is the other, older solution you find at least as often: the difference of the timestamps, divided by 86400. That one is wrong twice a year, and no amount of rounding fixes it.
The answer
<?php
$from = new DateTimeImmutable('2026-01-01');
$to = new DateTimeImmutable('2026-03-15');
$days = $from->diff($to)->days;
?>
->days : 73
That is all. The rest of this article is about why the other routes do not do
it, and what you can still get wrong with diff() itself.
->days is not ->d
The most common mistake with diff(). The returned DateInterval has both,
and they mean different things:
2026-01-01 -> 2026-02-01 : ->days=31 ->d=0 ->m=1 ->y=0
2026-01-01 -> 2027-01-01 : ->days=365 ->d=0 ->m=0 ->y=1
2026-01-01 -> 2026-01-10 : ->days=9 ->d=9 ->m=0 ->y=0
->days is the total number of days. ->d is the remainder in days after full
years and months have been subtracted. At a year’s distance ->d equals 0.
The third line is the treacherous one: within a single month the two are equal. So anyone testing their code with short spans does not notice the bug.
The direction is in ->invert
backwards: ->days=73 ->invert=1
->days is always positive, no matter which order you pass the dates in. If
you want to know whether the second date lies before the first, look at
->invert — or compare the objects directly, DateTime can be compared with
< and >.
Now the timestamp calculation
The old variant looks like this:
<?php
$days = ($end - $start) / 86400;
?>
86400 seconds is one day. Except not every day has 86400 seconds.
I worked this through for Vienna, once across the spring changeover and once across the autumn one.
Spring, the night of 29 March — one hour disappears:
2026-03-28 00:00:00 (CET)
2026-03-30 00:00:00 (CEST)
difference in seconds : 169200
divided by 86400 : 1.9583333333333
floor() of that : 1 <- one day too few!
diff()->days : 2 <- correct
Two calendar days, but only 47 hours. floor() turns that into one day.
At this point you might think of using ceil() instead. That rescues this case
— and destroys the other one.
Autumn, the night of 25 October — one hour twice over:
difference in seconds : 176400
divided by 86400 : 2.0416666666667 <- more than 2
ceil() of that : 3 <- one day too many!
diff()->days : 2
So that settles it: neither floor() nor ceil() works. Whatever fixes the
one case breaks the other.
/ 86400 floor() ceil() diff()->days
spring 1.9583 1 ✗ 2 ✓ 2
autumn 2.0416 2 ✓ 3 ✗ 2
diff() gets it right because it works with calendar dates and not with
seconds. It counts days, not blocks of 86400.
This, incidentally, is why rent calculations, notice periods and holiday-day counters are off by one twice a year. The bug gets reported, somebody looks at the code, finds nothing suspicious — and a week later it can no longer be reproduced.
The second common mistake: the time of day
You can go wrong with diff() too, namely here:
01.01. 23:00 -> 02.01. 01:00 : ->days=0
Two different calendar days, but only two hours between them. diff() counts
full 24-hour stretches, and not one of those has elapsed.
Usually, though, you want to count calendar days. For that you set both sides to midnight:
<?php
$a = $from->setTime(0, 0);
$b = $to->setTime(0, 0);
$days = $a->diff($b)->days;
?>
after setTime(0,0) : ->days=1
This is a separate bug, independent of daylight saving — and it happens every day, not just twice a year. If you read dates from a database that also stores a time, it is practically guaranteed.
Leap years
Briefly, since otherwise you wonder: they come out right without any help.
2024-02-01 -> 2024-03-01 : 29 days (leap year)
2026-02-01 -> 2026-03-01 : 28 days
Invalid input
Finally a difference that can save you a lot of searching:
new DateTimeImmutable('kein datum')
-> DateMalformedStringException: Failed to parse time string (kein datum)
strtotime('kein datum')
-> false
DateTimeImmutable throws an exception, so you notice the problem straight
away. strtotime() silently returns false — and false in a calculation is
0, so 1 January 1970. That is where the occasional “56 years ago” displays
come from.
Summary
$a->diff($b)->daysis the answer.->dis not the number of days, it is the remainder in days.->daysis always positive, the direction is in->invert.- The calculation
(b - a) / 86400is wrong across daylight saving changes, and neitherfloor()norceil()fixes both cases. - For calendar days, set both sides to midnight with
setTime(0, 0). strtotime()fails silently,DateTimeImmutablethrows an exception.
About Netcup (advertisement)
The German host Netcup offers, among other things, affordable and powerful web hosting packages, KVM-based root servers and dedicated servers. With our voucher codes you can save even more (6€ off your first order, 30% off all KVM-based root servers, ...).