
O Laravel converte automaticamente as colunas de data dos teus modelos em objetos Carbon. Retém três coisas: guarda tudo em UTC e converte apenas na apresentação com ->tz(), declara as tuas próprias colunas em casts(), e não esqueças que no Carbon 3 os métodos diffIn* devolvem um decimal com sinal e já não um inteiro absoluto.
Quase todas as aplicações Laravel manipulam datas: uma data de publicação, o fim de um período experimental, um prazo de entrega, o intervalo entre dois eventos. O Laravel entrega esse trabalho ao Carbon, uma extensão da classe DateTime do PHP, e converte automaticamente as colunas de data dos teus modelos em instâncias de Carbon.
Artigo do percurso Desenvolvimento web. Este guia cobre o que vais realmente fazer: criar uma data, convertê-la para o fuso certo, calcular um intervalo, mostrá-la em francês, manipulá-la no Eloquent e congelá-la nos testes. Todos os resultados apresentados a seguir foram produzidos em Laravel 13.30.1 e Carbon 3.13.2.
O que mudou com o Carbon 3
O Laravel 11, 12 e 13 trazem o Carbon 3. Se estás a reler código escrito para o Laravel 8 ou 9, há duas mudanças que te vão surpreender.
1. Os métodos diffIn* devolvem um número decimal, já não um inteiro. No Carbon 2, diffInDays() devolvia 113. No Carbon 3, a mesma expressão devolve o valor exato:
$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. O resultado tem sinal. No Carbon 2, a diferença era absoluta por predefinição. No Carbon 3 é negativa quando a data passada como argumento é a anterior:
$a->diffInDays($b); // 113.72916666666667
$b->diffInDays($a); // -113.72916666666667
$b->diffInDays($a, true); // 113.72916666666667 (absolute: true)Na prática, um if ($date->diffInDays(now()) > 30) escrito para o Carbon 2 pode deixar de disparar depois de uma atualização, porque o valor passou a ser negativo. É a armadilha mais cara da migração.
Já agora, floatDiffInDays() e as suas variantes desapareceram: duplicavam o novo comportamento.
Criar uma data
Os dois helpers globais do Laravel cobrem a maioria das necessidades:
now(); // 2026-09-02 14:30:00 — o instante atual
today(); // 2026-09-02 00:00:00 — hoje à meia-noitePara tudo o resto, passa pela facade. Repara no namespace: o Laravel expõe a sua própria subclasse, Illuminate\Support\Carbon, que acrescenta alguns métodos a Carbon\Carbon. É essa que deves importar numa aplicação 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); // hora = agora
Carbon::createMidnightDate(2026, 2, 1); // hora = 00:00:00
Carbon::createFromTimeString('10:15:00'); // data = hoje
Carbon::createFromFormat('d/m/Y', '01/02/2026');createFromDate(2026, 2, 1) não põe a hora a zero: conserva a hora atual. Testada a 2 de setembro de 2026 às 14:30, a chamada devolve 2026-02-01 14:30:00. Usa createMidnightDate() se quiseres a meia-noite.
Carbon::parse() aceita praticamente tudo, incluindo expressões em inglês:
Carbon::parse('2026-09-02T14:30:00+02:00');
Carbon::parse('last day of February 2024'); // 2024-02-29 00:00:00Estas expressões relativas são apelativas, mas frágeis: last day of next month não devolve o mesmo consoante o dia em que o código é executado. Reserva o parse() para as cadeias que vêm de uma fonte máquina (ISO 8601, timestamp) e constrói as restantes datas de forma explícita.
Fusos horários: o único modelo que aguenta
A regra cabe numa frase: guarda tudo em UTC e converte apenas na apresentação. O Laravel segue-a por predefinição, com o config/app.php a fixar 'timezone' => 'UTC'. Não mexas neste valor: é ele que determina o que é escrito na base de dados, e uma base em hora local torna-se ingerível à primeira mudança da hora.
A conversão faz-se sobre a instância, no momento de mostrar:
$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:00Estes três objetos descrevem o mesmo instante. É verificável: o timestamp Unix é idêntico. Mudar o fuso não desloca o momento, muda a forma de o ler.
Convém, no entanto, distinguir duas escritas que se parecem:
Carbon::tomorrow('Europe/Paris'); // meia-noite em Paris
Carbon::tomorrow()->tz('Europe/Paris'); // meia-noite UTC, lida na hora de Paris (02:00)A primeira cria uma data no fuso. A segunda cria uma data em UTC e depois converte-a. A lista de fusos aceites é a do PHP, 419 entradas no PHP 8.4.25:
timezone_identifiers_list(); // ['Africa/Abidjan', 'Africa/Accra', …]Para guardar o fuso de um utilizador, acrescenta uma coluna timezone à tabela users e alimenta-a a partir de uma lista pendente construída com estes identificadores. Um pacote como jamesmills/laravel-timezone automatiza a deteção na autenticação, mas na maioria dos casos basta uma coluna e um formulário.
Somar e subtrair
Cada unidade tem um método no singular e outro no plural, tanto para somar como para subtrair:
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); // ignora sábado e domingoHá dois comportamentos que vale a pena conhecer antes de os descobrires em produção.
O Carbon é mutável. addMonth() modifica o objeto sobre o qual o chamas, não devolve uma cópia:
$date = Carbon::create(2026, 1, 31);
$date->addMonth();
echo $date; // 2026-03-03 — $date mudou
$date = Carbon::create(2026, 1, 31);
echo $date->copy()->addMonth(); // 2026-03-03
echo $date; // 2026-01-31 — $date está intactaOs meses transbordam. 31 de janeiro mais um mês dá 3 de março, porque o 31 de fevereiro não existe e o Carbon arrasta o excedente. Se o que queres é o fim do mês, pede-o:
Carbon::create(2026, 1, 31)->copy()->addMonth(); // 2026-03-03
Carbon::create(2026, 1, 31)->copy()->addMonthNoOverflow(); // 2026-02-28Evitar as mutações com o CarbonImmutable
Em vez de semear copy() por todo o lado, podes tornar imutáveis todas as datas da aplicação. Cada operação devolve então uma nova instância e a original nunca se mexe:
use Carbon\CarbonImmutable;
use Illuminate\Support\Facades\Date;
public function boot(): void
{
Date::use(CarbonImmutable::class);
}Depois desta chamada, now() devolve uma instância de Carbon\CarbonImmutable e as datas hidratadas pelo Eloquent também. É uma mudança de comportamento global: decide-a no arranque do projeto, não numa aplicação já escrita com chamadas a addDay() que contam com a mutação.
Calcular o intervalo entre duas datas
Com os valores com sinal e em vírgula flutuante do Carbon 3, diz sempre exatamente o que queres:
$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); // valor absoluto
(int) $debut->diffInDays($fin); // 113, dias inteiros
round($debut->diffInHours($fin), 2); // 2729.5
$debut->diffInWeekdays($fin); // 82, sem fins de semana
$debut->diffInMonths($fin); // 3.733198924731183Para uma apresentação legível, o diffForHumans() trata da formulação, inclusive em francês:
$fin->diffForHumans($debut); // '3 months after'
$fin->locale('fr')->diffForHumans($debut); // '3 mois après'
$fin->locale('fr')->diffForHumans(); // 'dans 3 mois' (em relação a agora)Mostrar uma data em francês
O format() usa os códigos do PHP e não traduz nada: os nomes dos dias e dos meses continuam em inglês. Para francês, precisas de translatedFormat() ou de 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'O translatedFormat() mantém os códigos do PHP e traduz os rótulos. O isoFormat() usa códigos do tipo LLLL, LL ou dddd, que se adaptam às convenções de cada língua. Para não escreveres locale('fr') por todo o lado, define a locale da aplicação uma única vez:
APP_LOCALE=fr
APP_FAKER_LOCALE=fr_FRO Carbon nos modelos Eloquent
created_at e updated_at são convertidas automaticamente, desde que as colunas existam mesmo na base de dados. Chegam já como instâncias de Illuminate\Support\Carbon, não como cadeias.
$post = Post::first();
$post->created_at->locale('fr')->isoFormat('LL'); // '2 septembre 2026'
$post->created_at->diffForHumans();
$post->created_at->isToday();Numa vista Blade, estas chamadas escrevem-se da mesma forma entre chavetas.
Para as tuas próprias colunas, acrescentadas por uma migração, declara-as no método casts(). Atenção: a antiga propriedade protected $dates = [...] foi retirada no Laravel 10 e já não tem qualquer efeito. Código que ainda dependa dela deixa-te cadeias de caracteres onde esperavas objetos.
protected function casts(): array
{
return [
'published_at' => 'datetime',
'expires_at' => 'datetime:Y-m-d',
'starts_at' => 'immutable_datetime',
];
}O formato indicado depois de datetime: não altera o armazenamento, apenas a serialização em JSON. As comparações de datas escrevem-se então com naturalidade nas consultas Eloquent:
Post::where('published_at', '<=', now())->get();
Post::whereDate('published_at', today())->get();
Post::whereBetween('published_at', [now()->subWeek(), now()])->get();Congelar o tempo nos testes
Um teste que dependa do relógio real acaba sempre por falhar num 29 de fevereiro ou depois da meia-noite. O Carbon sabe mentir sobre a data atual, e o Laravel expõe helpers para isso:
$this->travelTo(Carbon::parse('2026-09-02 14:30'));
expect(now()->toDateTimeString())->toBe('2026-09-02 14:30:00');
$this->travel(5)->days(); // avança o relógio 5 dias
$this->travelBack(); // volta à hora realFora dos testes do Laravel, o equivalente em cru é Carbon::setTestNow(), que se anula com uma chamada sem argumentos. Foi este mecanismo que serviu para produzir os valores deste guia, todos calculados a partir de 2 de setembro de 2026 às 14:30 UTC.
Carbon::setTestNow('2026-09-02 14:30:00');
// …
Carbon::setTestNow(); // liberta o relógioErros frequentes
absolute: true ou converte para (int).$date->addMonth() modifica $date. Usa copy() antes da operação, ou passa a aplicação para CarbonImmutable através de Date::use().addMonthNoOverflow() para ficares dentro do mês.casts(), caso contrário estás a manipular cadeias.createMidnightDate() devolve mesmo 00:00:00.translatedFormat() ou isoFormat().

