La gestión de zonas horarias en MySQL es un tema que parece simple hasta que tu aplicación tiene usuarios en diferentes países, servidores en distintos centros de datos o necesitas correlacionar eventos que ocurrieron en diferentes husos horarios. MySQL maneja las zonas horarias a nivel del sistema operativo, del servidor y de cada sesión individual, y entender cómo interactúan estos niveles es esencial para evitar errores sutiles con fechas y horas.
La zona horaria del sistema
Cuando MySQL arranca, lee la zona horaria del sistema operativo y la almacena en la variable system_time_zone. Este valor no puede cambiarse sin reiniciar el servidor con una zona horaria diferente a nivel del sistema operativo.
SELECT @@system_time_zone;| @@system_time_zone |
|---|
| UTC |
En servidores de producción, es una práctica recomendada configurar el sistema operativo en UTC para evitar complicaciones con el horario de verano. Las conversiones a zonas horarias locales se realizan entonces a nivel de aplicación o de sesión de MySQL.
La variable time_zone
MySQL tiene dos niveles de configuración de zona horaria: global y de sesión. La variable time_zone controla ambos.
-- Ver la zona horaria global
SELECT @@global.time_zone;| @@global.time_zone |
|---|
| SYSTEM |
-- Ver la zona horaria de la sesión actual
SELECT @@session.time_zone;| @@session.time_zone |
|---|
| SYSTEM |
El valor SYSTEM significa que MySQL está usando la zona horaria del sistema operativo. Puedes especificar una zona horaria diferente de dos formas: usando un offset numérico o un nombre de zona horaria.
Formato offset
-- Establecer la zona horaria de la sesión a UTC+1 (Europa Central)
SET SESSION time_zone = '+01:00';
-- UTC-5 (Este de EEUU, hora estándar)
SET SESSION time_zone = '-05:00';
-- UTC (equivalente a +00:00)
SET SESSION time_zone = '+00:00';Formato con nombre
-- Usando nombres de zona horaria IANA
SET SESSION time_zone = 'Europe/Madrid';
SET SESSION time_zone = 'America/Mexico_City';
SET SESSION time_zone = 'Asia/Tokyo';Los nombres de zona horaria solo funcionan si las tablas de zonas horarias están cargadas en MySQL. Si intentas usar un nombre y recibes un error, necesitas cargar las tablas primero.
Cargar las tablas de zonas horarias
MySQL almacena la información de zonas horarias en la base de datos mysql, en las tablas time_zone, time_zone_name, time_zone_transition y time_zone_transition_type. Estas tablas vienen vacías por defecto y necesitan ser pobladas con los datos del sistema operativo.
En Linux, MySQL proporciona la herramienta mysql_tzinfo_to_sql para generar las sentencias SQL necesarias a partir de los archivos de zona horaria del sistema:
mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysqlWarning: Unable to load '/usr/share/zoneinfo/leap-seconds.list'
La advertencia sobre leap-seconds.list es normal y puede ignorarse. Después de cargar las tablas, puedes verificar que funcionan:
SELECT COUNT(*) AS zonas_disponibles
FROM mysql.time_zone_name;| zonas_disponibles |
|---|
| 593 |
-- Ahora los nombres de zona horaria funcionan
SET SESSION time_zone = 'Europe/Madrid';
SELECT NOW();Es importante actualizar estas tablas cuando el sistema operativo recibe actualizaciones de zonas horarias (por ejemplo, cuando un país cambia sus reglas de horario de verano). Para actualizar, simplemente ejecuta el comando mysql_tzinfo_to_sql de nuevo.
NOW() vs UTC_TIMESTAMP()
La diferencia entre estas dos funciones es fundamental para entender el manejo de zonas horarias en MySQL.
NOW() devuelve la fecha y hora según la zona horaria de la sesión actual. UTC_TIMESTAMP() siempre devuelve la fecha y hora en UTC, independientemente de la zona horaria de la sesión.
SET SESSION time_zone = 'Europe/Madrid';
SELECT
NOW() AS hora_local,
UTC_TIMESTAMP() AS hora_utc;| hora_local | hora_utc |
|---|---|
| 2025-03-18 17:30:00 | 2025-03-18 16:30:00 |
En marzo, España está en CET (UTC+1), por lo que NOW() muestra una hora más que UTC_TIMESTAMP(). En verano (CEST, UTC+2), la diferencia sería de dos horas.
Para aplicaciones internacionales, la recomendación es almacenar siempre las fechas en UTC y convertir a la hora local solo al mostrarlas al usuario. Esto evita problemas cuando cambia el horario de verano o cuando los datos se consultan desde diferentes zonas horarias.
Impacto en los tipos de datos
MySQL tiene dos tipos de datos principales para fechas con hora: DATETIME y TIMESTAMP. Su comportamiento con las zonas horarias es muy diferente.
DATETIME almacena el valor tal cual, sin ninguna conversión de zona horaria. Si insertas 2025-03-18 17:30:00, ese es el valor que se almacena y se devuelve, independientemente de la zona horaria de la sesión:
CREATE TABLE log_datetime (
id INT AUTO_INCREMENT PRIMARY KEY,
evento VARCHAR(100),
creado_en DATETIME DEFAULT CURRENT_TIMESTAMP
);
SET SESSION time_zone = 'Europe/Madrid';
INSERT INTO log_datetime (evento) VALUES ('Evento desde Madrid');
SET SESSION time_zone = 'America/Mexico_City';
SELECT * FROM log_datetime;| id | evento | creado_en |
|---|---|---|
| 1 | Evento desde Madrid | 2025-03-18 17:30:00 |
El valor no cambió a pesar de cambiar la zona horaria de la sesión.
TIMESTAMP almacena internamente el valor en UTC y lo convierte a la zona horaria de la sesión al leerlo y al escribirlo:
CREATE TABLE log_timestamp (
id INT AUTO_INCREMENT PRIMARY KEY,
evento VARCHAR(100),
creado_en TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
SET SESSION time_zone = 'Europe/Madrid';
INSERT INTO log_timestamp (evento) VALUES ('Evento desde Madrid');
SET SESSION time_zone = 'America/Mexico_City';
SELECT * FROM log_timestamp;| id | evento | creado_en |
|---|---|---|
| 1 | Evento desde Madrid | 2025-03-18 10:30:00 |
Ahora el valor se muestra como 10:30 (hora de Ciudad de México, UTC-6), que es el mismo instante en el tiempo que las 17:30 en Madrid (UTC+1). El tipo TIMESTAMP realizó la conversión automáticamente.
Caso práctico: aplicación multinacional
Imagina una plataforma de comercio electrónico con oficinas en Madrid, Ciudad de México y Tokio. Cada equipo necesita ver las transacciones en su hora local.
La estrategia recomendada es configurar el servidor en UTC y dejar que cada sesión ajuste su zona horaria:
[mysqld]
default-time-zone = '+00:00'Las aplicaciones se conectan y establecen su zona horaria:
-- Equipo de Madrid
SET SESSION time_zone = 'Europe/Madrid';
-- Equipo de México
SET SESSION time_zone = 'America/Mexico_City';
-- Equipo de Tokio
SET SESSION time_zone = 'Asia/Tokyo';Para una consulta de ventas del día, cada equipo ve las horas en su zona local:
SELECT
id_pedido,
cliente,
total,
fecha_pedido -- columna TIMESTAMP
FROM pedidos
WHERE DATE(fecha_pedido) = CURDATE()
ORDER BY fecha_pedido DESC;La función CONVERT_TZ() permite hacer conversiones explícitas cuando necesitas mostrar la hora en una zona diferente a la de tu sesión:
SELECT
id_pedido,
CONVERT_TZ(fecha_pedido, @@session.time_zone, 'America/New_York')
AS hora_nueva_york,
CONVERT_TZ(fecha_pedido, @@session.time_zone, 'Asia/Tokyo')
AS hora_tokio
FROM pedidos
WHERE id_pedido = 50842;Caso práctico: cambiar la zona horaria global
Si necesitas cambiar la zona horaria global del servidor sin reiniciar, puedes hacerlo con SET GLOBAL:
-- Cambiar a UTC explícitamente
SET GLOBAL time_zone = '+00:00';
-- O usando un nombre de zona
SET GLOBAL time_zone = 'UTC';Recuerda que este cambio solo afecta a las nuevas conexiones. Las sesiones existentes mantienen su zona horaria. Para que el cambio sea permanente, añádelo al archivo my.cnf:
[mysqld]
default-time-zone = '+00:00'Para verificar que todo es consistente:
SELECT
@@system_time_zone AS sistema,
@@global.time_zone AS global_mysql,
@@session.time_zone AS sesion_actual,
NOW() AS hora_actual,
UTC_TIMESTAMP() AS hora_utc;| sistema | global_mysql | sesion_actual | hora_actual | hora_utc |
|---|---|---|---|---|
| UTC | +00:00 | +00:00 | 2025-03-18 16:30:00 | 2025-03-18 16:30:00 |
Cuando el sistema, la zona global y la sesión están todas en UTC, NOW() y UTC_TIMESTAMP() devuelven el mismo valor, lo que simplifica enormemente el razonamiento sobre fechas y horas.
La gestión correcta de zonas horarias es uno de esos detalles que marca la diferencia entre una aplicación profesional y una que genera confusión cuando tiene usuarios en múltiples países. En la siguiente sección veremos los comandos SHOW, que permiten inspeccionar de forma rápida las bases de datos, tablas, procesos y privilegios del servidor.
Escrito por Eduardo Lázaro
