Fechas en PHP: olvida date() y strtotime() para cualquier cosa que no sea trivial. DateTimeImmutable hace los cálculos bien, incluidos los meses de distinta duración, las zonas horarias y los cambios de hora.
También te puede interesar
DateTimeImmutable, no date()
<?php
declare(strict_types=1);
$zona = new DateTimeZone('America/Mexico_City');
$fecha = new DateTimeImmutable('2026-10-27 18:30', $zona);
echo $fecha->format('d/m/Y H:i'), "\n";
echo $fecha->format('Y-m-d\TH:i:sP'), "\n";
echo $fecha->format(DATE_ATOM), "\n";
echo $fecha->format('l, j \d\e F'), "\n";
$desdeTexto = DateTimeImmutable::createFromFormat('d/m/Y', '31/12/2026', $zona);
echo $desdeTexto->format('Y-m-d'), "\n";salida27/10/2026 18:30 2026-10-27T18:30:00-06:00 2026-10-27T18:30:00-06:00 Tuesday, 27 de October 2026-12-31
Para trabajar con fechas, PHP tiene DateTimeImmutable: un objeto con la fecha, la hora y la zona horaria. format usa letras para cada parte: d día, m mes, Y año, H:i hora y minutos, P la diferencia con UTC. Las letras que quieras escribir tal cual van con una barra delante (\d\e). Los nombres de días y meses salen en inglés; para el español hace falta la extensión intl.
Sumar, restar y comparar
<?php
declare(strict_types=1);
$zona = new DateTimeZone('Europe/Madrid');
$pedido = new DateTimeImmutable('2026-10-27 10:00', $zona);
$entrega = $pedido->modify('+3 weekdays');
$vence = $pedido->add(new DateInterval('P1M'));
echo "Pedido: ", $pedido->format('D d/m'), "\n";
echo "Entrega: ", $entrega->format('D d/m'), "\n";
echo "Vence: ", $vence->format('D d/m'), "\n";
$intervalo = $pedido->diff(new DateTimeImmutable('2026-12-25', $zona));
echo "Faltan {$intervalo->days} días para Navidad\n";
var_dump($entrega > $pedido);
echo (new DateTimeImmutable('2026-01-31'))->modify('+1 month')->format('Y-m-d'), "\n";salidaPedido: Tue 27/10 Entrega: Fri 30/10 Vence: Fri 27/11 Faltan 58 días para Navidad bool(true) 2026-03-03
modify entiende expresiones en inglés como +3 weekdays (tres días laborables) o last day of next month, y add recibe un intervalo (P1M es un mes; P2DT3H, dos días y tres horas). diff da la diferencia, con el total de días en days. Las fechas se comparan con < y >. Y ojo con la última línea: el 31 de enero más un mes no existe en febrero, y PHP lo desborda al 3 de marzo; para «el último día del mes siguiente», modify('last day of next month').
Por qué Immutable
<?php
declare(strict_types=1);
$hoy = new DateTime('2026-10-27');
$manana = $hoy->modify('+1 day');
echo $hoy->format('d'), ' ', $manana->format('d'), "\n";
$hoy = new DateTimeImmutable('2026-10-27');
$manana = $hoy->modify('+1 day');
echo $hoy->format('d'), ' ', $manana->format('d'), "\n";salida28 28 27 28
Con DateTime, modify cambia el propio objeto: «mañana» y «hoy» acaban siendo la misma fecha. DateTimeImmutable devuelve un objeto nuevo y deja el original intacto. Usa siempre la versión inmutable.
Zonas horarias y cambios de hora
<?php
declare(strict_types=1);
$utc = new DateTimeImmutable('2026-10-27 15:00', new DateTimeZone('UTC'));
foreach (['America/Mexico_City', 'America/Bogota', 'Europe/Madrid', 'America/Argentina/Buenos_Aires'] as $z) {
printf("%-32s %s\n", $z, $utc->setTimezone(new DateTimeZone($z))->format('H:i T'));
}
$madrid = new DateTimeZone('Europe/Madrid');
$antes = new DateTimeImmutable('2026-10-24 12:00', $madrid);
$despues = $antes->modify('+1 day');
echo "\n", $antes->format('d/m H:i T'), ' → ', $despues->format('d/m H:i T'), "\n";
echo ($despues->getTimestamp() - $antes->getTimestamp()) / 3600, " horas reales\n";
echo date_default_timezone_get(), "\n";salidaAmerica/Mexico_City 09:00 CST America/Bogota 10:00 -05 Europe/Madrid 16:00 CET America/Argentina/Buenos_Aires 12:00 -03 24/10 12:00 CEST → 25/10 12:00 CET 25 horas reales UTC
setTimezone muestra el mismo instante en otra zona. El 25 de octubre de 2026 Europa atrasó la hora, y PHP lo sabe: «un día después» a las 12:00 son en realidad 25 horas. Por eso las fechas se guardan en UTC (en la base de datos, como texto ISO o como timestamp) y se convierten a la zona del usuario solo al mostrarlas. Y fija la zona por omisión en php.ini (date.timezone): si no, PHP usa UTC, como aquí.
Resumen
| Para | Usa |
|---|---|
| Una fecha | new DateTimeImmutable('2026-10-27', $zona) |
| Leer un formato concreto | DateTimeImmutable::createFromFormat('d/m/Y', $texto) |
| Mostrarla | $fecha->format('d/m/Y H:i') |
| Sumar o restar | modify('+3 weekdays') o add(new DateInterval('P1M')) |
| Diferencia | $a->diff($b)->days |
| Otra zona horaria | setTimezone(new DateTimeZone('Europe/Madrid')) |
Todo el código de esta página se ejecutó con PHP 8.5.12 en un contenedor limpio, sin acceso a la red, antes de publicarla; las salidas y los mensajes de error están copiados de esa ejecución.