PHP: Wie berechnet man die Tage zwischen zwei Daten?
Die Antwort ist eine Zeile. Interessant ist die andere, ältere Lösung, die man mindestens genauso oft findet: Differenz der Timestamps, geteilt durch 86400. Die ist zweimal im Jahr falsch, und kein Runden repariert das.
Die Antwort
<?php
$von = new DateTimeImmutable('2026-01-01');
$bis = new DateTimeImmutable('2026-03-15');
$tage = $von->diff($bis)->days;
?>
->days : 73
Das war es schon. Der Rest des Artikels handelt davon, warum es die anderen
Wege nicht tun und was man bei diff() selbst noch falsch machen kann.
->days ist nicht ->d
Der häufigste Fehler bei diff(). Das zurückgegebene DateInterval hat
beides, und sie bedeuten etwas anderes:
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 ist die Gesamtzahl der Tage. ->d ist der Tagesrest, nachdem volle
Jahre und Monate abgezogen wurden. Bei einem Jahr Abstand ist ->d gleich
0.
Tückisch ist die dritte Zeile: Innerhalb eines Monats sind beide gleich. Wer seinen Code mit kurzen Zeiträumen testet, merkt den Fehler also nicht.
Die Richtung steckt in ->invert
rueckwaerts: ->days=73 ->invert=1
->days ist immer positiv, egal in welcher Reihenfolge du die Daten
übergibst. Wenn du wissen willst, ob das zweite Datum vor dem ersten liegt,
schaust du in ->invert — oder vergleichst die Objekte direkt, DateTime
lässt sich mit < und > vergleichen.
Jetzt die Rechnung mit den Timestamps
Die alte Variante sieht so aus:
<?php
$tage = ($ende - $start) / 86400;
?>
86400 Sekunden sind ein Tag. Nur haben nicht alle Tage 86400 Sekunden.
Ich habe das für Wien durchgerechnet, einmal über die Umstellung im Frühjahr und einmal über die im Herbst.
Frühjahr, die Nacht auf den 29. März — eine Stunde fällt weg:
2026-03-28 00:00:00 (CET)
2026-03-30 00:00:00 (CEST)
Differenz in Sekunden : 169200
geteilt durch 86400 : 1.9583333333333
floor() davon : 1 <- ein Tag zu wenig!
diff()->days : 2 <- richtig
Zwei Kalendertage, aber nur 47 Stunden. floor() macht daraus einen Tag.
Jetzt könnte man auf die Idee kommen, stattdessen ceil() zu nehmen. Das
rettet diesen Fall — und zerstört den anderen.
Herbst, die Nacht auf den 25. Oktober — eine Stunde doppelt:
Differenz in Sekunden : 176400
geteilt durch 86400 : 2.0416666666667 <- mehr als 2
ceil() davon : 3 <- ein Tag zu viel!
diff()->days : 2
Damit steht es fest: Weder floor() noch ceil() funktioniert. Was den einen
Fall repariert, macht den anderen kaputt.
/ 86400 floor() ceil() diff()->days
Frühjahr 1.9583 1 ✗ 2 ✓ 2
Herbst 2.0416 2 ✓ 3 ✗ 2
diff() rechnet richtig, weil es mit Kalenderdaten arbeitet und nicht mit
Sekunden. Es zählt Tage, keine 86400er-Blöcke.
Das ist übrigens der Grund, warum Mietberechnungen, Kündigungsfristen und Urlaubstage-Zähler zweimal im Jahr um einen Tag danebenliegen. Der Fehler wird gemeldet, jemand schaut sich den Code an, findet nichts Auffälliges — und in einer Woche lässt er sich nicht mehr reproduzieren.
Der zweite häufige Fehler: die Uhrzeit
Auch mit diff() kann man danebenliegen, und zwar hier:
01.01. 23:00 -> 02.01. 01:00 : ->days=0
Zwei verschiedene Kalendertage, aber nur zwei Stunden dazwischen. diff()
zählt volle 24-Stunden-Abschnitte, und davon ist keiner vergangen.
Meistens will man aber Kalendertage zählen. Dafür setzt du beide Seiten auf Mitternacht:
<?php
$a = $von->setTime(0, 0);
$b = $bis->setTime(0, 0);
$tage = $a->diff($b)->days;
?>
nach setTime(0,0) : ->days=1
Das ist ein eigener Fehler, unabhängig von der Sommerzeit — und er tritt jeden Tag auf, nicht nur zweimal im Jahr. Wenn du Daten aus einer Datenbank liest, in der auch eine Uhrzeit steht, ist er praktisch garantiert.
Schaltjahre
Kurz, weil man sich sonst wundert: Die kommen ohne Zutun richtig heraus.
2024-02-01 -> 2024-03-01 : 29 Tage (Schaltjahr)
2026-02-01 -> 2026-03-01 : 28 Tage
Ungültige Eingaben
Zum Schluss noch ein Unterschied, der einem viel Sucherei ersparen kann:
new DateTimeImmutable('kein datum')
-> DateMalformedStringException: Failed to parse time string (kein datum)
strtotime('kein datum')
-> false
DateTimeImmutable wirft eine Exception, du merkst den Fehler also sofort.
strtotime() liefert stumm false — und false ist in einer Rechnung 0,
also der 1. Januar 1970. Daher kommen die gelegentlichen Anzeigen von
“vor 56 Jahren”.
Zusammenfassung
$a->diff($b)->daysist die Antwort.->dist nicht die Anzahl der Tage, sondern der Tagesrest.->daysist immer positiv, die Richtung steht in->invert.- Die Rechnung
(b - a) / 86400ist bei Zeitumstellungen falsch, und wederfloor()nochceil()repariert beide Fälle. - Für Kalendertage beide Seiten mit
setTime(0, 0)auf Mitternacht setzen. strtotime()scheitert stumm,DateTimeImmutablewirft eine Exception.
Hinweis zu Netcup (Werbung)
Der deutsche Hoster Netcup bietet unter anderem günstige und zugleich leistungsstarke Webhosting Pakete, KVM-basierte Root Server und dezidierte Server an. Mit unseren Gutscheincodes kannst du noch mehr Geld sparen (6€ bei deiner ersten Bestellung, 30% Rabatt auf alle KVM-basierten Root Server, ...).