W3docs

date_sub()

Aprende a restar duraciones a fechas en PHP con DateTime::sub() y DateInterval, incluyendo el formato ISO 8601 y casos especiales.

Restar tiempo a una fecha en PHP

Restar una duración a una fecha — "hace 30 días", "el inicio del mes pasado", "dos horas antes de ahora" — es una de las tareas con fechas más comunes en PHP. La antigua función procedural date_sub() (un alias de DateTime::sub()) fue eliminada en PHP 8.0, por lo que el código moderno debe usar el método orientado a objetos DateTime::sub() (o DateTimeImmutable::sub()) junto con un DateInterval.

Esta página explica cómo funciona DateTime::sub(), cómo construir el string de intervalo, la distinción entre mutable e inmutable, y los problemas de calendario (desbordamiento de mes, intervalos invertidos, horario de verano) que suelen causar errores.

Cómo funciona DateTime::sub()

sub() recibe un único argumento DateInterval que describe cuánto tiempo eliminar. Su firma es:

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

Dos cosas a recordar:

  • Muta el objeto en su lugar — el DateTime original se modifica.
  • También devuelve el mismo objeto, por lo que se pueden encadenar llamadas.
$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

Pasar un DateTimeZone explícito mantiene el resultado predecible; sin uno, PHP usa la zona horaria de date_default_timezone_set() o php.ini.

Construir el string DateInterval

DateInterval usa el formato de duración ISO 8601: P[n]Y[n]M[n]DT[n]H[n]M[n]S. El prefijo P (período) es obligatorio, y una T separa la parte de fecha de la parte de tiempo.

TokenSignificadoEjemploDuración
YAñosP3Y3 años
MMesesP6M6 meses
DDíasP10D10 días
HHorasPT4H4 horas
MMinutosPT30M30 minutos
SSegundosPT45S45 segundos

Nótese que M significa meses antes de la T y minutos después — una fuente frecuente de errores. Combina tokens para restar varias unidades a la 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

Los intervalos de solo tiempo funcionan del mismo modo — aquí, 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 el formato de forma estricta. Un string inválido (por ejemplo, sin P, o PT sin tokens de tiempo) lanza una Exception. Si la duración proviene de entrada del usuario, envuelve el constructor en un bloque try...catch.

Mutable vs. inmutable: prefiere DateTimeImmutable

Dado que DateTime::sub() modifica el objeto en su lugar, una fecha que pasas puede ser alterada inesperadamente por código que llame a sub() sobre ella. DateTimeImmutable resuelve esto: su sub() deja el original intacto y devuelve una nueva instancia.

$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

En la mayoría del código de aplicación, usa DateTimeImmutable por defecto y recurre al DateTime mutable solo cuando quieras actualizaciones en lugar.

Problemas que debes conocer

Desbordamiento de mes

Restar meses enteros no recorta al último día del mes de destino — PHP normaliza el desbordamiento al mes siguiente. Restar un mes desde el 31 de marzo acaba en marzo, no en febrero:

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

Aquí P1M primero va a "31 de febrero", que PHP convierte al 3 de marzo (febrero tiene 28 días en 2023). Si necesitas el último día del mes anterior, usa un string relativo: new DateTime('last day of previous month').

Los intervalos invertidos suman en vez de restar

Un DateInterval tiene una propiedad invert. Cuando vale 1, el intervalo es negativo, por lo que sub() en realidad suma la duración:

$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

Esto importa cuando reutilizas el intervalo devuelto por DateTime::diff(), que establece invert automáticamente según la dirección de la diferencia.

Horario de verano

Al restar días a través de un cambio de horario de verano, PHP ajusta la hora del reloj para que el resultado calendario sea correcto. Si en cambio restas horas (PT24H), obtienes exactamente 24 horas de tiempo transcurrido, lo que puede terminar en una hora diferente del reloj. Elige intervalos basados en días o en horas según si quieres decir "la misma hora, el día anterior" o "exactamente 24 horas antes".

Usar Carbon para una sintaxis fluida

En bases de código grandes, la biblioteca Carbon envuelve DateTimeImmutable con helpers legibles y encadenables. Es opcional — las clases nativas cubren los casos estándar —, pero puede hacer que la lógica compleja sea más clara:

use Carbon\Carbon;

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

Conclusión

Usa DateTime::sub() (o, preferiblemente, DateTimeImmutable::sub()) con un DateInterval para restar duraciones a una fecha. Construye el intervalo con el formato ISO 8601 P…T…, ten en cuenta la distinción M entre meses y minutos, y presta atención al desbordamiento de mes y a los intervalos invertidos.

Para ir más lejos, consulta date_add() para la operación inversa, date_diff() para medir la diferencia entre dos fechas, y date_format() para formatear el resultado.

Práctica

Práctica
¿Qué hace DateTime::sub() con el DateInterval que le pasas?
¿Qué hace DateTime::sub() con el DateInterval que le pasas?
Was this page helpful?