
Laravel wandelt die Datumsspalten deiner Modelle automatisch in Carbon-Objekte um. Merke dir drei Dinge: Speichere alles in UTC und rechne erst bei der Ausgabe mit ->tz() um, deklariere eigene Spalten in casts(), und rechne damit, dass die diffIn*-Methoden seit Carbon 3 einen vorzeichenbehafteten Float statt eines absoluten Integers liefern.
Fast jede Laravel-Anwendung rechnet mit Datumswerten: ein Veröffentlichungsdatum, das Ende einer Testphase, eine Lieferfrist, der Abstand zwischen zwei Ereignissen. Laravel überlässt diese Arbeit Carbon, einer Erweiterung der PHP-Klasse DateTime, und wandelt die Datumsspalten deiner Modelle automatisch in Carbon-Instanzen um.
Artikel aus dem Lernpfad Webentwicklung. Dieser Leitfaden behandelt, was in der Praxis wirklich anfällt: ein Datum erzeugen, es in die richtige Zeitzone umrechnen, einen Abstand berechnen, es auf Französisch ausgeben, es in Eloquent verarbeiten und es in Tests einfrieren. Alle unten gezeigten Ergebnisse stammen aus Laravel 13.30.1 und Carbon 3.13.2.
Was sich mit Carbon 3 geändert hat
Laravel 11, 12 und 13 bringen Carbon 3 mit. Wenn du Code wieder aufschlägst, der für Laravel 8 oder 9 geschrieben wurde, werden dich zwei Änderungen überraschen.
1. Die diffIn*-Methoden liefern eine Fließkommazahl, keinen Integer mehr. In Carbon 2 gab diffInDays() noch 113 zurück. In Carbon 3 liefert derselbe Ausdruck den exakten Wert:
$a = Carbon::parse('2026-09-02 14:30');
$b = Carbon::parse('2026-12-25 08:00');
$a->diffInDays($b); // 113.72916666666667 (Float)
(int) $a->diffInDays($b); // 1132. Das Ergebnis hat ein Vorzeichen. In Carbon 2 war die Differenz standardmäßig absolut. In Carbon 3 wird sie negativ, sobald das übergebene Datum weiter zurückliegt:
$a->diffInDays($b); // 113.72916666666667
$b->diffInDays($a); // -113.72916666666667
$b->diffInDays($a, true); // 113.72916666666667 (absolute: true)Konkret: Ein für Carbon 2 geschriebenes if ($date->diffInDays(now()) > 30) greift nach dem Update womöglich nicht mehr, weil der Wert negativ geworden ist. Das ist die teuerste Falle der Migration.
Nebenbei sind floatDiffInDays() und seine Varianten verschwunden: Sie waren durch das neue Verhalten überflüssig geworden.
Ein Datum erzeugen
Die beiden globalen Helper von Laravel decken die meisten Fälle ab:
now(); // 2026-09-02 14:30:00 — aktueller Zeitpunkt
today(); // 2026-09-02 00:00:00 — heute um MitternachtFür alles Weitere nimmst du die Fassade. Achte auf den Namespace: Laravel stellt eine eigene Unterklasse bereit, Illuminate\Support\Carbon, die Carbon\Carbon um einige Methoden ergänzt. Genau die gehört in einer Laravel-Anwendung importiert.
<?php
use Illuminate\Support\Carbon;
Carbon::now();
Carbon::today();
Carbon::tomorrow('Europe/Paris');
Carbon::yesterday();
Carbon::create(2026, 2, 1, 10, 0, 0, 'Europe/Paris'); // 2026-02-01 10:00:00
Carbon::createFromDate(2026, 2, 1); // Uhrzeit = jetzt
Carbon::createMidnightDate(2026, 2, 1); // Uhrzeit = 00:00:00
Carbon::createFromTimeString('10:15:00'); // Datum = heute
Carbon::createFromFormat('d/m/Y', '01/02/2026');createFromDate(2026, 2, 1) setzt die Uhrzeit nicht auf null, sondern übernimmt die aktuelle Uhrzeit. Am 2. September 2026 um 14:30 Uhr getestet, liefert der Aufruf 2026-02-01 14:30:00. Wenn du Mitternacht willst, nimm createMidnightDate().
Carbon::parse() schluckt so ziemlich alles, auch englische Ausdrücke:
Carbon::parse('2026-09-02T14:30:00+02:00');
Carbon::parse('last day of February 2024'); // 2024-02-29 00:00:00Diese relativen Ausdrücke sind verlockend, aber fragil: last day of next month liefert je nach Ausführungstag etwas anderes. Halte parse() für Strings aus maschinellen Quellen frei (ISO 8601, Timestamp) und baue alle anderen Datumswerte explizit auf.
Zeitzonen: das einzige Modell, das trägt
Die Regel passt in einen Satz: Speichere alles in UTC und rechne erst bei der Ausgabe um. Laravel hält sich standardmäßig daran, config/app.php setzt 'timezone' => 'UTC'. Lass diesen Wert stehen: Er bestimmt, was in die Datenbank geschrieben wird, und eine Datenbank in Ortszeit wird spätestens bei der ersten Zeitumstellung unbeherrschbar.
Umgerechnet wird auf der Instanz, im Moment der Ausgabe:
$moment = Carbon::create(2026, 7, 1, 12, 0, 0, 'UTC');
$moment->copy()->tz('Europe/Paris'); // 2026-07-01 14:00:00
$moment->copy()->setTimezone('Asia/Tokyo'); // 2026-07-01 21:00:00Diese drei Objekte beschreiben denselben Zeitpunkt. Das lässt sich prüfen: Ihr Unix-Timestamp ist identisch. Ein Zeitzonenwechsel verschiebt den Moment nicht, er ändert nur, wie man ihn liest.
Zwei sehr ähnliche Schreibweisen musst du dagegen auseinanderhalten:
Carbon::tomorrow('Europe/Paris'); // Mitternacht in Paris
Carbon::tomorrow()->tz('Europe/Paris'); // Mitternacht UTC, in Pariser Zeit gelesen (02:00)Die erste erzeugt ein Datum in der Zeitzone. Die zweite erzeugt ein Datum in UTC und rechnet es anschließend um. Akzeptiert wird die Zeitzonenliste von PHP, 419 Einträge unter PHP 8.4.25:
timezone_identifiers_list(); // ['Africa/Abidjan', 'Africa/Accra', …]Um die Zeitzone eines Nutzers zu speichern, ergänzt du die Tabelle users um eine Spalte timezone und befüllst sie über ein Auswahlfeld, das auf dieser Liste aufbaut. Ein Paket wie jamesmills/laravel-timezone automatisiert die Erkennung beim Login, aber in den meisten Fällen reichen eine Spalte und ein Formular.
Addieren und subtrahieren
Jede Einheit hat eine Methode im Singular und eine im Plural, jeweils zum Addieren und zum Subtrahieren:
now()->addDay();
now()->addDays(30);
now()->subDay();
now()->subDays(30);
now()->addWeeks(2);
now()->addMonths(3);
now()->addYears(5);
now()->addHours(6);
now()->addMinutes(90);
now()->subWeekdays(3); // überspringt Samstag und SonntagZwei Verhaltensweisen solltest du kennen, bevor du sie in der Produktion entdeckst.
Carbon ist mutable. addMonth() verändert das Objekt, auf dem du die Methode aufrufst, und gibt keine Kopie zurück:
$date = Carbon::create(2026, 1, 31);
$date->addMonth();
echo $date; // 2026-03-03 — $date hat sich geändert
$date = Carbon::create(2026, 1, 31);
echo $date->copy()->addMonth(); // 2026-03-03
echo $date; // 2026-01-31 — $date ist unverändertMonate laufen über. Der 31. Januar plus ein Monat ergibt den 3. März, denn den 31. Februar gibt es nicht und Carbon schiebt den Überschuss weiter. Wenn du das Monatsende willst, verlange es ausdrücklich:
Carbon::create(2026, 1, 31)->copy()->addMonth(); // 2026-03-03
Carbon::create(2026, 1, 31)->copy()->addMonthNoOverflow(); // 2026-02-28Mutationen mit CarbonImmutable vermeiden
Statt überall copy() zu verstreuen, kannst du sämtliche Datumswerte der Anwendung unveränderlich machen. Jede Operation liefert dann eine neue Instanz, das Original bleibt unangetastet:
use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\Date;
public function boot(): void
{
Date::use(CarbonImmutable::class);
}Nach diesem Aufruf liefert now() eine Instanz von Carbon\CarbonImmutable, und die von Eloquent hydrierten Datumswerte ebenso. Das ist eine globale Verhaltensänderung: eine Entscheidung für den Projektstart, nicht für eine Anwendung, die längst mit addDay()-Aufrufen arbeitet, die auf die Mutation setzen.
Den Abstand zwischen zwei Datumswerten berechnen
Bei den vorzeichenbehafteten Fließkommawerten von Carbon 3 sagst du besser immer genau, was du haben willst:
$debut = Carbon::parse('2026-09-02 14:30');
$fin = Carbon::parse('2026-12-25 08:00');
$debut->diffInDays($fin); // 113.72916666666667
$debut->diffInDays($fin, true); // Absolutwert
(int) $debut->diffInDays($fin); // 113, ganze Tage
round($debut->diffInHours($fin), 2); // 2729.5
$debut->diffInWeekdays($fin); // 82, ohne Wochenenden
$debut->diffInMonths($fin); // 3.733198924731183Für eine lesbare Ausgabe übernimmt diffForHumans() die Formulierung, auch auf Französisch:
$fin->diffForHumans($debut); // '3 months after'
$fin->locale('fr')->diffForHumans($debut); // '3 mois après'
$fin->locale('fr')->diffForHumans(); // 'dans 3 mois' (bezogen auf jetzt)Ein Datum auf Französisch ausgeben
format() arbeitet mit den Codes von PHP und übersetzt nichts: Tages- und Monatsnamen bleiben englisch. Für Französisch brauchst du translatedFormat() oder isoFormat().
$d = Carbon::parse('2026-09-02 14:30');
$d->toDateTimeString(); // '2026-09-02 14:30:00'
$d->toDateString(); // '2026-09-02'
$d->toIso8601String(); // '2026-09-02T14:30:00+00:00'
$d->format('d/m/Y H:i'); // '02/09/2026 14:30'
$d->locale('fr')->translatedFormat('l j F Y'); // 'mercredi 2 septembre 2026'
$d->locale('fr')->isoFormat('LLLL'); // 'mercredi 2 septembre 2026 14:30'translatedFormat() behält die PHP-Codes bei und übersetzt die Bezeichnungen. isoFormat() verwendet Codes wie LLLL, LL oder dddd, die sich an die Konventionen der jeweiligen Sprache anpassen. Damit du nicht überall locale('fr') schreiben musst, stellst du die Locale der Anwendung einmal zentral ein:
APP_LOCALE=fr
APP_FAKER_LOCALE=fr_FRCarbon in Eloquent-Modellen
created_at und updated_at werden automatisch umgewandelt, sofern die Spalten in der Datenbank auch wirklich existieren. Es sind dann bereits Instanzen von Illuminate\Support\Carbon, keine Strings.
$post = Post::first();
$post->created_at->locale('fr')->isoFormat('LL'); // '2 septembre 2026'
$post->created_at->diffForHumans();
$post->created_at->isToday();In einer Blade-View schreiben sich dieselben Aufrufe genauso, nur zwischen geschweiften Klammern.
Eigene Spalten, die du per Migration angelegt hast, deklarierst du in der Methode casts(). Achtung: Die alte Eigenschaft protected $dates = [...] wurde in Laravel 10 entfernt und hat keinerlei Wirkung mehr. Code, der noch darauf setzt, hinterlässt dir Strings, wo du Objekte erwartest.
protected function casts(): array
{
return [
'published_at' => 'datetime',
'expires_at' => 'datetime:Y-m-d',
'starts_at' => 'immutable_datetime',
];
}Das Format hinter datetime: ändert nichts an der Speicherung, nur an der Serialisierung nach JSON. Datumsvergleiche schreiben sich damit ganz natürlich in Eloquent-Abfragen:
Post::where('published_at', '<=', now())->get();
Post::whereDate('published_at', today())->get();
Post::whereBetween('published_at', [now()->subWeek(), now()])->get();Die Zeit in Tests einfrieren
Ein Test, der von der echten Uhrzeit abhängt, fällt irgendwann an einem 29. Februar oder kurz nach Mitternacht um. Carbon kann über das aktuelle Datum lügen, und Laravel stellt dafür passende Helper bereit:
$this->travelTo(Carbon::parse('2026-09-02 14:30'));
expect(now()->toDateTimeString())->toBe('2026-09-02 14:30:00');
$this->travel(5)->days(); // stellt die Uhr 5 Tage vor
$this->travelBack(); // zurück zur echten UhrzeitAußerhalb der Laravel-Tests ist Carbon::setTestNow() das nackte Äquivalent, ein Aufruf ohne Argument hebt es wieder auf. Genau dieser Mechanismus hat die Werte in diesem Leitfaden erzeugt, alle berechnet ab dem 2. September 2026, 14:30 Uhr UTC.
Carbon::setTestNow('2026-09-02 14:30:00');
// …
Carbon::setTestNow(); // gibt die Uhr wieder freiHäufige Fehler
absolute: true oder caste nach (int).$date->addMonth() verändert $date. Nutze copy() vor der Operation oder stelle die Anwendung über Date::use() auf CarbonImmutable um.addMonthNoOverflow() bleibst du im Monat.casts(), sonst hantierst du mit Strings.createMidnightDate() liefert wirklich 00:00:00.translatedFormat() oder isoFormat().

