W3docs

date_sub()

Aprenda a subtrair datas em PHP com DateTime::sub() e DateInterval, incluindo armadilhas comuns como estouro de mês e DST.

Subtraindo tempo de uma data em PHP

Subtrair uma duração de uma data — "30 dias atrás", "o início do mês passado", "duas horas antes de agora" — é uma das tarefas de data mais comuns em PHP. A função procedural antiga date_sub() (um alias de DateTime::sub()) foi removida no PHP 8.0, portanto o código moderno deve usar o método orientado a objetos DateTime::sub() (ou DateTimeImmutable::sub()) em conjunto com um DateInterval.

Esta página explica como DateTime::sub() funciona, como construir a string de intervalo, a distinção entre mutável e imutável, e as armadilhas de calendário (estouro de mês, intervalos invertidos, horário de verão) que costumam pegar as pessoas de surpresa.

Como DateTime::sub() funciona

sub() recebe um único argumento DateInterval que descreve quanto tempo remover. Sua assinatura é:

public DateTime::sub(DateInterval $interval): DateTime

Dois pontos importantes a lembrar:

  • Ele muta o objeto no lugar — o DateTime original é alterado.
  • Ele também retorna o mesmo objeto, portanto é possível encadear chamadas.
$date = new DateTime('2023-10-15', new DateTimeZone('UTC'));
$interval = new DateInterval('P5D'); // 5 days
$date->sub($interval);
echo $date->format('Y-m-d'); // Outputs: 2023-10-10

Passar um DateTimeZone explícito mantém o resultado previsível; sem ele, PHP usa o fuso horário de date_default_timezone_set() ou do php.ini.

Construindo a string DateInterval

DateInterval utiliza o formato de duração ISO 8601: P[n]Y[n]M[n]DT[n]H[n]M[n]S. O prefixo P (período) é obrigatório, e um T separa a parte de data da parte de tempo.

TokenSignificadoExemploDuração
YAnosP3Y3 anos
MMesesP6M6 meses
DDiasP10D10 dias
HHorasPT4H4 horas
MMinutosPT30M30 minutos
SSegundosPT45S45 segundos

Observe que M significa meses antes do T e minutos após ele — uma fonte frequente de bugs. Combine tokens para subtrair várias unidades de uma vez:

$date = new DateTime('2023-10-15 12:00:00');
$interval = new DateInterval('P2M1DT3H'); // 2 months, 1 day, 3 hours
$date->sub($interval);
echo $date->format('Y-m-d H:i:s'); // Outputs: 2023-08-14 09:00:00

Intervalos somente de tempo funcionam da mesma forma — aqui, 90 minutos:

$date = new DateTime('2023-10-15 08:30:00');
$date->sub(new DateInterval('PT90M')); // 90 minutes
echo $date->format('Y-m-d H:i:s'); // Outputs: 2023-10-15 07:00:00

Nota: DateInterval valida o formato de forma rigorosa. Uma string inválida (por exemplo, sem o P, ou PT sem tokens de tempo) lança uma Exception. Se a duração vier de entrada do usuário, envolva o construtor em um bloco try...catch.

Mutável vs. imutável: prefira DateTimeImmutable

Como DateTime::sub() altera o objeto no lugar, uma data passada adiante pode ser modificada inesperadamente por código que chama sub() nela. DateTimeImmutable resolve isso: seu sub() mantém o original inalterado e retorna uma nova instância.

$date = new DateTimeImmutable('2023-10-15');
$earlier = $date->sub(new DateInterval('P10D'));

echo $date->format('Y-m-d');    // Outputs: 2023-10-15 (unchanged)
echo "\n";
echo $earlier->format('Y-m-d'); // Outputs: 2023-10-05

Para a maior parte do código de aplicação, use DateTimeImmutable por padrão e recorra ao DateTime mutável apenas quando quiser atualizações no lugar de forma intencional.

Armadilhas que você deve conhecer

Estouro de mês

Subtrair meses inteiros não limita ao último dia do mês de destino — PHP normaliza o excesso para o mês seguinte. Subtrair um mês de 31 de março resulta em março, não fevereiro:

$date = new DateTime('2023-03-31');
$date->sub(new DateInterval('P1M'));
echo $date->format('Y-m-d'); // Outputs: 2023-03-03

Aqui P1M vai primeiro para "31 de fevereiro", que PHP converte para 3 de março (fevereiro tem 28 dias em 2023). Se você precisar do último dia do mês anterior, use uma string relativa: new DateTime('last day of previous month').

Intervalos invertidos somam em vez de subtrair

Um DateInterval possui uma propriedade invert. Quando ela é 1, o intervalo é negativo, então sub() efetivamente soma a duração:

$interval = new DateInterval('P1M');
$interval->invert = 1; // negative interval
$date = new DateTime('2023-10-15');
$date->sub($interval);
echo $date->format('Y-m-d'); // Outputs: 2023-11-15

Isso é relevante quando você reutiliza o intervalo retornado por DateTime::diff(), que define invert automaticamente com base na direção da diferença.

Horário de verão

Ao subtrair dias através de uma fronteira de horário de verão, PHP ajusta o horário do relógio para que o resultado no calendário seja correto. Se em vez disso você subtrair horas (PT24H), obtém exatamente 24 horas de tempo decorrido, o que pode resultar em um horário diferente no relógio. Escolha intervalos baseados em dias ou em horas de forma deliberada, dependendo se você quer "mesmo horário, dia anterior" ou "exatamente 24 horas antes".

Usando Carbon para sintaxe fluente

Para bases de código grandes, a biblioteca Carbon envolve DateTimeImmutable com helpers legíveis e encadeáveis. É opcional — as classes nativas cobrem os casos padrão — mas pode tornar a lógica complexa mais clara:

use Carbon\Carbon;

$date = Carbon::parse('2023-10-15');
$newDate = $date->subMonths(2)->subDays(5);
echo $newDate->toDateString(); // Outputs: 2023-08-10

Conclusão

Use DateTime::sub() (ou, de preferência, DateTimeImmutable::sub()) com um DateInterval para subtrair durações de uma data. Construa o intervalo com o formato ISO 8601 P…T…, tenha em mente a distinção entre M para meses e M para minutos, e fique atento ao estouro de mês e aos intervalos invertidos.

Para aprofundar, veja date_add() para a operação inversa, date_diff() para medir o intervalo entre duas datas, e date_format() para formatar o resultado.

Prática

Prática
O que DateTime::sub() faz com o DateInterval passado a ele?
O que DateTime::sub() faz com o DateInterval passado a ele?
Was this page helpful?