Fechas y horas en PHP con DateTimeImmutable

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.

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";

Probar en el editor →

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";

Probar en el editor →

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";

Probar en el editor →

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";

Probar en el editor →

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

ParaUsa
Una fechanew DateTimeImmutable('2026-10-27', $zona)
Leer un formato concretoDateTimeImmutable::createFromFormat('d/m/Y', $texto)
Mostrarla$fecha->format('d/m/Y H:i')
Sumar o restarmodify('+3 weekdays') o add(new DateInterval('P1M'))
Diferencia$a->diff($b)->days
Otra zona horariasetTimezone(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.