CONVERT_TZ
La función CONVERT_TZ convierte un valor de fecha y hora de una zona horaria a otra. Es la pieza clave para cualquier aplicación que almacene timestamps en UTC y necesite mostrarlos en la hora local de cada usuario. Sin esta función, tendrías que calcular manualmente los desfases horarios y gestionar las complejidades del horario de verano, algo que CONVERT_TZ resuelve de forma transparente cuando se usan zonas horarias con nombre.
En el modelo recomendado de manejo de fechas, todos los valores se almacenan en UTC y se convierten a la zona horaria del usuario solamente al momento de la presentación. CONVERT_TZ es la función que hace posible esta conversión directamente en SQL, sin necesidad de lógica adicional en la aplicación.
Sintaxis
CONVERT_TZ(dt, from_tz, to_tz)La función recibe tres argumentos. El primero, dt, es el valor DATETIME que deseas convertir. Los argumentos from_tz y to_tz especifican las zonas horarias de origen y destino respectivamente. Estos pueden expresarse de dos formas: como desplazamientos numéricos (por ejemplo '+00:00', '-05:00') o como nombres de zona horaria IANA (por ejemplo 'America/Mexico_City', 'Europe/Madrid').
Instalación de las tablas de zonas horarias
Para usar nombres de zona horaria en lugar de desplazamientos numéricos, MySQL necesita tener cargadas las tablas de zona horaria en la base de datos mysql. En sistemas Linux, esto se hace ejecutando desde la terminal del sistema operativo el siguiente comando.
-- Desde la terminal del sistema (no desde MySQL):
-- mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysqlPuedes verificar si las tablas están cargadas consultando directamente.
SELECT COUNT(*) AS zonas_disponibles
FROM mysql.time_zone_name;| zonas_disponibles |
|---|
| 594 |
Si el resultado es 0, los nombres de zona horaria no funcionarán y CONVERT_TZ devolverá NULL cuando los uses. En ese caso, puedes utilizar desplazamientos numéricos como alternativa temporal.
Comportamiento básico
El uso más simple es convertir un valor UTC a una zona horaria local específica.
SELECT
CONVERT_TZ('2026-02-14 18:00:00', '+00:00', 'America/Mexico_City') AS hora_mexico,
CONVERT_TZ('2026-02-14 18:00:00', '+00:00', 'Europe/Madrid') AS hora_madrid,
CONVERT_TZ('2026-02-14 18:00:00', '+00:00', 'Asia/Tokyo') AS hora_tokyo;| hora_mexico | hora_madrid | hora_tokyo |
|---|---|---|
| 2026-02-14 12:00:00 | 2026-02-14 19:00:00 | 2026-02-15 03:00:00 |
Observa cómo las 18:00 UTC se traducen a mediodía en Ciudad de México (UTC-6), a las 7 PM en Madrid (UTC+1) y a las 3 AM del día siguiente en Tokio (UTC+9).
También puedes convertir entre dos zonas horarias que no sean UTC.
SELECT CONVERT_TZ('2026-06-15 14:00:00', 'America/New_York', 'Europe/London') AS hora_londres;| hora_londres |
|---|
| 2026-06-15 19:00:00 |
En este ejemplo, las 2 PM de Nueva York en junio (cuando está en horario de verano, EDT = UTC-4) se convierten a las 7 PM de Londres (que también está en horario de verano, BST = UTC+1). La función maneja automáticamente el horario de verano cuando se usan nombres de zona IANA.
Desplazamientos numéricos vs nombres IANA
Los desplazamientos numéricos como '+05:30' o '-03:00' son estáticos: no consideran el horario de verano. Los nombres IANA como 'America/New_York' sí lo consideran, lo que los hace mucho más fiables para conversiones precisas.
-- En enero, Nueva York está en EST (UTC-5)
SELECT CONVERT_TZ('2026-01-15 12:00:00', 'UTC', 'America/New_York') AS invierno;
-- En julio, Nueva York está en EDT (UTC-4)
SELECT CONVERT_TZ('2026-07-15 12:00:00', 'UTC', 'America/New_York') AS verano;| invierno |
|---|
| 2026-01-15 07:00:00 |
| verano |
|---|
| 2026-07-15 08:00:00 |
Con el desplazamiento numérico '-05:00', ambas conversiones restarían 5 horas, lo cual sería incorrecto para julio. Por esta razón, siempre que sea posible, utiliza nombres de zona horaria IANA.
Zonas horarias comunes
A continuación se muestran algunas de las zonas horarias más utilizadas en aplicaciones hispanohablantes.
SELECT
'UTC' AS zona, '+00:00' AS desfase_estandar
UNION ALL SELECT 'America/Mexico_City', 'UTC-6'
UNION ALL SELECT 'America/Bogota', 'UTC-5'
UNION ALL SELECT 'America/Lima', 'UTC-5'
UNION ALL SELECT 'America/Santiago', 'UTC-4 / UTC-3'
UNION ALL SELECT 'America/Argentina/Buenos_Aires', 'UTC-3'
UNION ALL SELECT 'Europe/Madrid', 'UTC+1 / UTC+2'
UNION ALL SELECT 'America/New_York', 'UTC-5 / UTC-4'
UNION ALL SELECT 'Asia/Tokyo', 'UTC+9';| zona | desfase_estandar |
|---|---|
| UTC | +00:00 |
| America/Mexico_City | UTC-6 |
| America/Bogota | UTC-5 |
| America/Lima | UTC-5 |
| America/Santiago | UTC-4 / UTC-3 |
| America/Argentina/Buenos_Aires | UTC-3 |
| Europe/Madrid | UTC+1 / UTC+2 |
| America/New_York | UTC-5 / UTC-4 |
| Asia/Tokyo | UTC+9 |
Las zonas que muestran dos desfases aplican horario de verano.
Caso práctico: mostrar pedidos en hora local del cliente
Una tienda online almacena todos los pedidos en UTC pero necesita mostrar la hora local a cada cliente según su zona horaria registrada.
SELECT
p.id AS pedido,
c.nombre AS cliente,
c.zona_horaria,
p.created_at_utc,
CONVERT_TZ(p.created_at_utc, '+00:00', c.zona_horaria) AS hora_local_cliente,
DATE_FORMAT(
CONVERT_TZ(p.created_at_utc, '+00:00', c.zona_horaria),
'%d/%m/%Y %H:%i'
) AS hora_formateada
FROM pedidos p
JOIN clientes c ON p.cliente_id = c.id
ORDER BY p.created_at_utc DESC
LIMIT 4;| pedido | cliente | zona_horaria | created_at_utc | hora_local_cliente | hora_formateada |
|---|---|---|---|---|---|
| 3421 | María García | Europe/Madrid | 2026-02-14 20:15:00 | 2026-02-14 21:15:00 | 14/02/2026 21:15 |
| 3420 | Carlos Mendoza | America/Mexico_City | 2026-02-14 19:45:00 | 2026-02-14 13:45:00 | 14/02/2026 13:45 |
| 3419 | Laura Fernández | America/Argentina/Buenos_Aires | 2026-02-14 18:30:00 | 2026-02-14 15:30:00 | 14/02/2026 15:30 |
| 3418 | Pedro Ruiz | America/Lima | 2026-02-14 17:00:00 | 2026-02-14 12:00:00 | 14/02/2026 12:00 |
Cada cliente ve la hora de su pedido en su zona horaria local. La combinación de CONVERT_TZ con DATE_FORMAT permite además personalizar la presentación.
Caso práctico: reportes por día local
Cuando generas reportes de ventas agrupados por día, es importante agrupar según el día local del mercado, no según el día UTC. Un pedido hecho a las 23:30 hora de Madrid (22:30 UTC) debe contarse como venta del día en Madrid, no del día siguiente.
SELECT
DATE(CONVERT_TZ(p.created_at_utc, '+00:00', 'Europe/Madrid')) AS dia_local_madrid,
COUNT(*) AS total_pedidos,
SUM(p.total) AS ingresos
FROM pedidos p
WHERE p.mercado = 'España'
AND p.created_at_utc >= CONVERT_TZ('2026-02-01', 'Europe/Madrid', '+00:00')
AND p.created_at_utc < CONVERT_TZ('2026-02-15', 'Europe/Madrid', '+00:00')
GROUP BY dia_local_madrid
ORDER BY dia_local_madrid DESC
LIMIT 5;| dia_local_madrid | total_pedidos | ingresos |
|---|---|---|
| 2026-02-14 | 87 | 12450.50 |
| 2026-02-13 | 124 | 18230.75 |
| 2026-02-12 | 95 | 14120.00 |
| 2026-02-11 | 110 | 16580.25 |
| 2026-02-10 | 73 | 10890.30 |
Nota cómo el filtro WHERE también usa CONVERT_TZ de forma inversa: convierte las fechas locales de Madrid a UTC para comparar con la columna almacenada en UTC. Esto asegura que el rango de fechas sea exacto.
Caso práctico: dashboard con múltiples zonas horarias
Un dashboard ejecutivo muestra la hora actual en las principales oficinas de la empresa.
SELECT
'Ciudad de México' AS oficina,
DATE_FORMAT(CONVERT_TZ(UTC_TIMESTAMP(), '+00:00', 'America/Mexico_City'), '%H:%i') AS hora_local,
CASE
WHEN HOUR(CONVERT_TZ(UTC_TIMESTAMP(), '+00:00', 'America/Mexico_City')) BETWEEN 9 AND 17
THEN 'Horario laboral'
ELSE 'Fuera de horario'
END AS estado
UNION ALL
SELECT 'Madrid',
DATE_FORMAT(CONVERT_TZ(UTC_TIMESTAMP(), '+00:00', 'Europe/Madrid'), '%H:%i'),
CASE WHEN HOUR(CONVERT_TZ(UTC_TIMESTAMP(), '+00:00', 'Europe/Madrid')) BETWEEN 9 AND 17
THEN 'Horario laboral' ELSE 'Fuera de horario' END
UNION ALL
SELECT 'Buenos Aires',
DATE_FORMAT(CONVERT_TZ(UTC_TIMESTAMP(), '+00:00', 'America/Argentina/Buenos_Aires'), '%H:%i'),
CASE WHEN HOUR(CONVERT_TZ(UTC_TIMESTAMP(), '+00:00', 'America/Argentina/Buenos_Aires')) BETWEEN 9 AND 17
THEN 'Horario laboral' ELSE 'Fuera de horario' END;| oficina | hora_local | estado |
|---|---|---|
| Ciudad de México | 16:30 | Horario laboral |
| Madrid | 23:30 | Fuera de horario |
| Buenos Aires | 19:30 | Fuera de horario |
Manejo de NULL
CONVERT_TZ devuelve NULL en tres situaciones: cuando el valor dt es NULL, cuando alguna de las zonas horarias no es válida, o cuando las tablas de zona horaria no están instaladas y se usan nombres IANA.
SELECT
CONVERT_TZ(NULL, '+00:00', '+05:00') AS dt_nulo,
CONVERT_TZ('2026-02-14 12:00:00', 'Zona/Invalida', '+00:00') AS zona_invalida,
CONVERT_TZ('2026-02-14 12:00:00', '+00:00', '+05:00') AS conversion_valida;| dt_nulo | zona_invalida | conversion_valida |
|---|---|---|
| NULL | NULL | 2026-02-14 17:00:00 |
Si obtienes NULL inesperados con nombres IANA, lo primero que debes verificar es que las tablas de zona horaria estén cargadas.
SELECT
p.id,
COALESCE(
CONVERT_TZ(p.created_at_utc, '+00:00', c.zona_horaria),
p.created_at_utc
) AS fecha_mostrar
FROM pedidos p
JOIN clientes c ON p.cliente_id = c.id;En este ejemplo, COALESCE devuelve la fecha UTC original si la conversión falla por una zona horaria no válida en la tabla de clientes.
Combinación con otras funciones
CONVERT_TZ se combina frecuentemente con DATE_FORMAT para presentación, con UTC_TIMESTAMP para conversiones en tiempo real y con funciones de extracción como HOUR o DATE para agrupar datos según la hora local.
SELECT
DATE_FORMAT(
CONVERT_TZ(v.fecha_utc, '+00:00', 'America/Bogota'),
'%W %d de %M de %Y, %H:%i'
) AS fecha_legible,
v.monto
FROM ventas v
WHERE v.sucursal = 'Bogotá'
ORDER BY v.fecha_utc DESC
LIMIT 3;| fecha_legible | monto |
|---|---|
| Saturday 14 de February de 2026, 17:30 | 2450.00 |
| Saturday 14 de February de 2026, 14:15 | 890.50 |
| Friday 13 de February de 2026, 19:45 | 3200.75 |
Para obtener los nombres de días y meses en español, configura lc_time_names antes de ejecutar la consulta con SET lc_time_names = 'es_ES'.
En el siguiente artículo veremos GET_FORMAT para obtener cadenas de formato predefinidas.
Escrito por Eduardo Lázaro
