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): DateTimeDois pontos importantes a lembrar:
- Ele muta o objeto no lugar — o
DateTimeoriginal é 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-10Passar 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.
| Token | Significado | Exemplo | Duração |
|---|---|---|---|
Y | Anos | P3Y | 3 anos |
M | Meses | P6M | 6 meses |
D | Dias | P10D | 10 dias |
H | Horas | PT4H | 4 horas |
M | Minutos | PT30M | 30 minutos |
S | Segundos | PT45S | 45 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:00Intervalos 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:00Nota:
DateIntervalvalida o formato de forma rigorosa. Uma string inválida (por exemplo, sem oP, ouPTsem tokens de tempo) lança umaException. Se a duração vier de entrada do usuário, envolva o construtor em um blocotry...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-05Para 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-03Aqui 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-15Isso é 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-10Conclusã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.