Las zonas horarias son uno de los puntos donde más bugs aparecen en aplicaciones PHP. DateTimeZone es la clase que encapsula una zona horaria y permite asignarla a cualquier objeto DateTime o DateTimeImmutable para operar con horas correctas y convertir entre zonas.
Especificar la zona horaria al crear una fecha
<?php
// Sin zona horaria: PHP usa la configurada en php.ini o date_default_timezone_set()
$local = new DateTimeImmutable('2026-07-23 11:00:00');
// Con zona horaria explícita: preferible
$tz = new DateTimeZone('Europe/Madrid');
$madrid = new DateTimeImmutable('2026-07-23 11:00:00', $tz);
echo $madrid->format('Y-m-d H:i:s P');
// 2026-07-23 11:00:00 +02:00 (CEST en verano)
?>
Nombres de zona IANA frente a offsets fijos
PHP admite dos tipos de identificadores de zona horaria:
- Nombres IANA como
Europe/Madrid,America/New_YorkoAsia/Tokyo. Estos tienen en cuenta el horario de verano automáticamente. - Offsets fijos como
+02:00oUTC. No cambian con el horario de verano, por eso son menos adecuados para mostrar horas locales.
<?php
// Nombre IANA: ajusta automáticamente CEST/CET
$tzMadrid = new DateTimeZone('Europe/Madrid');
$verano = new DateTimeImmutable('2026-07-23', $tzMadrid);
$invierno = new DateTimeImmutable('2026-12-23', $tzMadrid);
echo $verano->format('P'); // +02:00 (CEST)
echo $invierno->format('P'); // +01:00 (CET)
// Offset fijo: siempre +02:00 aunque sea invierno
$tzFijo = new DateTimeZone('+02:00');
$con_fijo = new DateTimeImmutable('2026-12-23', $tzFijo);
echo $con_fijo->format('P'); // +02:00 (incorrecto para Madrid en diciembre)
?>
Convertir entre zonas con setTimezone()
<?php
// Una fecha en UTC
$tzUTC = new DateTimeZone('UTC');
$tzMadrid = new DateTimeZone('Europe/Madrid');
$tzTokyo = new DateTimeZone('Asia/Tokyo');
$utc = new DateTimeImmutable('2026-07-23 09:00:00', $tzUTC);
// Convertir a Madrid (CEST = UTC+2)
$madrid = $utc->setTimezone($tzMadrid);
echo $madrid->format('H:i T'); // 11:00 CEST
// Convertir a Tokio (JST = UTC+9)
$tokyo = $utc->setTimezone($tzTokyo);
echo $tokyo->format('H:i T'); // 18:00 JST
?>
Guardar fechas en UTC en base de datos
La práctica recomendada es almacenar siempre en UTC y convertir a la zona del usuario al mostrar:
<?php
// Recibimos la fecha del usuario en su zona horaria
$zonaUsuario = new DateTimeZone('America/New_York');
$fechaUsuario = new DateTimeImmutable('2026-07-23 08:00:00', $zonaUsuario);
// Convertimos a UTC antes de guardar en BD
$utcParaBD = $fechaUsuario->setTimezone(new DateTimeZone('UTC'));
$guardadaEn = $utcParaBD->format('Y-m-d H:i:s');
// '2026-07-23 12:00:00' ? esto va a la columna DATETIME de MySQL
// Al mostrar: leer UTC de BD y convertir a zona del usuario
$desdeBD = new DateTimeImmutable($guardadaEn, new DateTimeZone('UTC'));
$paraUsuario = $desdeBD->setTimezone($zonaUsuario);
echo $paraUsuario->format('d/m/Y H:i');
// 23/07/2026 08:00 ? la hora original del usuario
?>
Listar zonas horarias disponibles
<?php
// Todas las zonas IANA disponibles en PHP
$zonas = DateTimeZone::listIdentifiers();
echo count($zonas) . ' zonas horarias'; // ~420 zonas
// Filtrar por continente
$zonasEuropa = DateTimeZone::listIdentifiers(DateTimeZone::EUROPE);
foreach ($zonasEuropa as $zona) {
echo $zona . "n"; // Europe/Madrid, Europe/Paris, etc.
}
?>
Evitar el error de timezone en la aplicación
<?php
// Bootstrap de la aplicación (index.php o similar)
date_default_timezone_set('UTC'); // zona base siempre UTC
// Zona del usuario (de su perfil o cabecera HTTP)
$zonaUsuario = $_SESSION['timezone'] ?? 'Europe/Madrid';
$tz = new DateTimeZone($zonaUsuario);
// A partir de aquí, todas las fechas para mostrar pasan por $tz
function formatearFechaLocal(string $fechaUTC, DateTimeZone $tz): string
{
$dt = new DateTimeImmutable($fechaUTC, new DateTimeZone('UTC'));
return $dt->setTimezone($tz)->format('d/m/Y H:i');
}
?>
La documentación oficial de DateTimeZone incluye la lista completa de constantes de región y los métodos para obtener el offset en segundos respecto a UTC.
