
Laravel convertit automatiquement les colonnes de date de vos modèles en objets Carbon. Retenez trois choses : stockez tout en UTC et ne convertissez qu’à l’affichage avec ->tz(), déclarez vos propres colonnes dans casts(), et sachez qu’en Carbon 3 les méthodes diffIn* renvoient un flottant signé et non plus un entier absolu.
Presque toutes les applications Laravel manipulent des dates : une date de publication, une fin d’essai, un délai de livraison, un écart entre deux évènements. Laravel confie ce travail à Carbon, une extension de la classe DateTime de PHP, et convertit automatiquement les colonnes de date de vos modèles en instances Carbon.
Article du parcours Développement web. Ce guide couvre ce que vous ferez réellement : créer une date, la convertir dans le bon fuseau, calculer un écart, l’afficher en français, la manipuler dans Eloquent et la figer dans vos tests. Tous les résultats affichés ci-dessous ont été produits sur Laravel 13.30.1 et Carbon 3.13.2.
Ce qui a changé avec Carbon 3
Laravel 11, 12 et 13 embarquent Carbon 3. Si vous relisez du code écrit pour Laravel 8 ou 9, deux changements vont vous surprendre.
1. Les méthodes diffIn* renvoient un nombre à virgule, plus un entier. En Carbon 2, diffInDays() renvoyait 113. En Carbon 3, la même expression renvoie la valeur exacte :
$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. Le résultat est signé. En Carbon 2, la différence était absolue par défaut. En Carbon 3, elle est négative quand la date passée en argument est antérieure :
$a->diffInDays($b); // 113.72916666666667
$b->diffInDays($a); // -113.72916666666667
$b->diffInDays($a, true); // 113.72916666666667 (absolute: true)Concrètement, un if ($date->diffInDays(now()) > 30) écrit pour Carbon 2 peut cesser de se déclencher après une mise à jour, parce que la valeur est devenue négative. C’est le piège le plus coûteux de la migration.
Au passage, floatDiffInDays() et ses variantes ont disparu : elles faisaient double emploi avec le nouveau comportement.
Créer une date
Les deux helpers globaux de Laravel couvrent la majorité des besoins :
now(); // 2026-09-02 14:30:00 — instant présent
today(); // 2026-09-02 00:00:00 — aujourd’hui à minuitPour tout le reste, passez par la façade. Notez l’espace de noms : Laravel expose sa propre sous-classe, Illuminate\Support\Carbon, qui ajoute quelques méthodes à Carbon\Carbon. C’est celle qu’il faut importer dans une application Laravel.
<?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); // heure = maintenant
Carbon::createMidnightDate(2026, 2, 1); // heure = 00:00:00
Carbon::createFromTimeString('10:15:00'); // date = aujourd’hui
Carbon::createFromFormat('d/m/Y', '01/02/2026');createFromDate(2026, 2, 1) ne remet pas l’heure à zéro : elle conserve l’heure courante. Testé le 2 septembre 2026 à 14h30, l’appel renvoie 2026-02-01 14:30:00. Utilisez createMidnightDate() si vous voulez minuit.
Carbon::parse() accepte à peu près tout, y compris des expressions en anglais :
Carbon::parse('2026-09-02T14:30:00+02:00');
Carbon::parse('last day of February 2024'); // 2024-02-29 00:00:00Ces expressions relatives sont séduisantes mais fragiles : last day of next month ne renvoie pas la même chose selon le jour où le code s’exécute. Réservez parse() aux chaînes venant d’une source machine (ISO 8601, timestamp) et construisez les autres dates explicitement.
Fuseaux horaires : le seul modèle qui tient
La règle tient en une phrase : stockez tout en UTC, convertissez seulement à l’affichage. Laravel s’y conforme par défaut, config/app.php fixant 'timezone' => 'UTC'. Ne changez pas cette valeur : elle détermine ce qui est écrit en base, et une base en heure locale devient ingérable au premier changement d’heure.
La conversion se fait sur l’instance, au moment de l’affichage :
$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:00Ces trois objets décrivent le même instant. C’est vérifiable : leur timestamp Unix est identique. Changer le fuseau ne déplace pas le moment, il change la façon de le lire.
Il faut en revanche distinguer deux écritures qui se ressemblent :
Carbon::tomorrow('Europe/Paris'); // minuit à Paris
Carbon::tomorrow()->tz('Europe/Paris'); // minuit UTC, lu à l’heure de Paris (02:00)La première crée une date dans le fuseau. La seconde crée une date en UTC puis la convertit. La liste des fuseaux acceptés est celle de PHP, 419 entrées sur PHP 8.4.25 :
timezone_identifiers_list(); // ['Africa/Abidjan', 'Africa/Accra', …]Pour stocker le fuseau d’un utilisateur, ajoutez une colonne timezone à la table users et alimentez-la depuis un menu déroulant construit sur cette liste. Un paquet comme jamesmills/laravel-timezone automatise la détection à la connexion, mais une colonne et un formulaire suffisent dans la plupart des cas.
Ajouter et soustraire
Chaque unité dispose d’une méthode au singulier et d’une au pluriel, en ajout et en soustraction :
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); // ignore samedi et dimancheDeux comportements méritent d’être connus avant de les découvrir en production.
Carbon est mutable. addMonth() modifie l’objet sur lequel vous l’appelez, il ne renvoie pas une copie :
$date = Carbon::create(2026, 1, 31);
$date->addMonth();
echo $date; // 2026-03-03 — $date a changé
$date = Carbon::create(2026, 1, 31);
echo $date->copy()->addMonth(); // 2026-03-03
echo $date; // 2026-01-31 — $date est intactLes mois débordent. Le 31 janvier plus un mois donne le 3 mars, parce que le 31 février n’existe pas et que Carbon reporte le surplus. Si vous voulez la fin du mois, demandez-la :
Carbon::create(2026, 1, 31)->copy()->addMonth(); // 2026-03-03
Carbon::create(2026, 1, 31)->copy()->addMonthNoOverflow(); // 2026-02-28Éviter les mutations avec CarbonImmutable
Plutôt que de semer des copy() partout, vous pouvez rendre toutes les dates de l’application immuables. Chaque opération renvoie alors une nouvelle instance et l’originale ne bouge jamais :
use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\Date;
public function boot(): void
{
Date::use(CarbonImmutable::class);
}Après cet appel, now() renvoie une instance de Carbon\CarbonImmutable et les dates hydratées par Eloquent le sont aussi. C’est un changement de comportement global : à décider en début de projet, pas sur une application déjà écrite avec des addDay() qui comptent sur la mutation.
Calculer un écart entre deux dates
Avec les valeurs signées et flottantes de Carbon 3, précisez toujours ce que vous voulez :
$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); // valeur absolue
(int) $debut->diffInDays($fin); // 113, jours entiers
round($debut->diffInHours($fin), 2); // 2729.5
$debut->diffInWeekdays($fin); // 82, hors week-ends
$debut->diffInMonths($fin); // 3.733198924731183Pour un affichage lisible, diffForHumans() se charge de la formulation, y compris en français :
$fin->diffForHumans($debut); // '3 months after'
$fin->locale('fr')->diffForHumans($debut); // '3 mois après'
$fin->locale('fr')->diffForHumans(); // 'dans 3 mois' (par rapport à maintenant)Afficher une date en français
format() utilise les codes de PHP et ne traduit rien : les noms de jours et de mois restent en anglais. Pour du français, il faut translatedFormat() ou 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() garde les codes PHP et traduit les libellés. isoFormat() utilise les codes de type LLLL, LL, dddd, qui s’adaptent aux conventions de chaque langue. Pour éviter d’écrire locale('fr') partout, réglez la locale de l’application une fois :
APP_LOCALE=fr
APP_FAKER_LOCALE=fr_FRCarbon dans les modèles Eloquent
created_at et updated_at sont converties automatiquement, à condition que les colonnes existent bien en base. Elles arrivent déjà sous forme d’instances de Illuminate\Support\Carbon, pas de chaînes.
$post = Post::first();
$post->created_at->locale('fr')->isoFormat('LL'); // '2 septembre 2026'
$post->created_at->diffForHumans();
$post->created_at->isToday();Dans une vue Blade, ces appels s’écrivent de la même façon entre accolades.
Pour vos propres colonnes, ajoutées par une migration, déclarez-les dans la méthode casts(). Attention : l’ancienne propriété protected $dates = [...] a été retirée de Laravel 10 et n’a plus aucun effet. Du code qui s’en sert vous laissera des chaînes de caractères là où vous attendiez des objets.
protected function casts(): array
{
return [
'published_at' => 'datetime',
'expires_at' => 'datetime:Y-m-d',
'starts_at' => 'immutable_datetime',
];
}Le format passé après datetime: ne change pas le stockage, seulement la sérialisation en JSON. Les comparaisons de dates s’écrivent alors naturellement dans les requêtes Eloquent :
Post::where('published_at', '<=', now())->get();
Post::whereDate('published_at', today())->get();
Post::whereBetween('published_at', [now()->subWeek(), now()])->get();Figer le temps dans les tests
Un test qui dépend de l’heure réelle finit toujours par échouer un 29 février ou à minuit passé. Carbon sait mentir sur la date courante, et Laravel expose des helpers pour cela :
$this->travelTo(Carbon::parse('2026-09-02 14:30'));
expect(now()->toDateTimeString())->toBe('2026-09-02 14:30:00');
$this->travel(5)->days(); // avance l’horloge de 5 jours
$this->travelBack(); // revient à l’heure réelleEn dehors des tests Laravel, l’équivalent brut est Carbon::setTestNow(), à annuler avec un appel sans argument. C’est ce mécanisme qui a servi à produire les valeurs de ce guide, toutes calculées à partir du 2 septembre 2026 à 14h30 UTC.
Carbon::setTestNow('2026-09-02 14:30:00');
// …
Carbon::setTestNow(); // libère l’horlogeErreurs fréquentes
absolute: true ou castez en (int).$date->addMonth() modifie $date. Utilisez copy() avant l’opération, ou basculez l’application sur CarbonImmutable via Date::use().addMonthNoOverflow() pour rester dans le mois.casts(), sinon vous manipulez des chaînes.createMidnightDate() donne bien 00:00:00.translatedFormat() ou isoFormat().

