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): DateTimeDos cosas a recordar:
- Muta el objeto en su lugar — el
DateTimeoriginal 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-10Pasar 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.
| Token | Significado | Ejemplo | Duración |
|---|---|---|---|
Y | Años | P3Y | 3 años |
M | Meses | P6M | 6 meses |
D | Días | P10D | 10 días |
H | Horas | PT4H | 4 horas |
M | Minutos | PT30M | 30 minutos |
S | Segundos | PT45S | 45 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:00Los 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:00Nota:
DateIntervalvalida el formato de forma estricta. Un string inválido (por ejemplo, sinP, oPTsin tokens de tiempo) lanza unaException. Si la duración proviene de entrada del usuario, envuelve el constructor en un bloquetry...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-05En 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-03Aquí 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-15Esto 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-10Conclusió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.