Ir al contenido principal
Tecnología

Configuración de zona horaria en MySQL - Servidor, conexión y comportamiento de columnas

Tres capas - Servidor, sesión y columna

El comportamiento de zona horaria en MySQL está gobernado por tres capas: el valor predeterminado del servidor configurado en my.cnf, el valor por sesión ajustable mediante SET time_zone, y el tipo de columna en sí (TIMESTAMP frente a DATETIME). La mayoría de los errores de zona horaria provienen de confundir estas capas. Saber cuál cambiar para un síntoma dado es la base de una operación fiable.

El valor predeterminado del servidor se configura con default-time-zone en el archivo my.cnf. El valor de sesión puede cambiarse con SET time_zone = '+09:00' después de conectarse, y la mayoría de los controladores ORM lo hacen automáticamente al establecer una conexión. El comportamiento de columna, en cambio, está determinado enteramente por el tipo elegido en la definición del esquema y no puede cambiarse en tiempo de ejecución.

TIMESTAMP vs DATETIME - La diferencia crítica

TIMESTAMP almacena valores internamente como UTC y los convierte a la time_zone de la sesión al leerlos. Esto significa que clientes en diferentes zonas horarias ven horas locales consistentes para el mismo instante subyacente. El rango válido va desde 1970-01-01 00:00:01 UTC hasta 2038-01-19 03:14:07 UTC porque el valor son segundos de época Unix en 32 bits, y ese techo no ha cambiado en toda la serie 8.0. Lo que MySQL 8.0.28 amplió en plataformas de 64 bits es el rango de argumentos que aceptan UNIX_TIMESTAMP() y FROM_UNIXTIME(), que ahora llega al año 3001, mientras que el tipo TIMESTAMP sigue deteniéndose en 2038.

DATETIME no tiene noción de zona horaria: conserva el valor tal como se escribió, sin ninguna conversión. El mismo valor se lee igual independientemente de la configuración de sesión, y el rango válido va desde 1000-01-01 hasta 9999-12-31. DATETIME parece más simple pero se vuelve más difícil de usar correctamente en sistemas multizona porque el significado del valor almacenado depende de un contexto que existe fuera de la base de datos.

TIMESTAMP frente a DATETIME - almacenamiento, lectura y rango
AspectoTIMESTAMPDATETIME
Qué se almacenaEl instante convertido internamente a UTCEl valor exactamente tal como se escribió, por ejemplo 2026-05-20 10:00:00
Depende de la time_zone al leerSí. Se convierte a la time_zone de la sesión al leerloNo. El mismo valor se lee igual con cualquier configuración de sesión
Rango válidoDe 1970-01-01 00:00:01 UTC a 2038-01-19 03:14:07 UTC (por la representación en segundos de época Unix de 32 bits)De 1000-01-01 a 9999-12-31
DEFAULT CURRENT_TIMESTAMPDisponible. La hora actual se convierte a UTC según la time_zone de la sesiónDisponible desde MySQL 5.6, pero cambiar la time_zone del servidor altera en silencio el significado de los valores ya escritos
Qué fija el significado del valorEl propio tipo, porque siempre denota un instante absolutoUn contexto externo a la base de datos, registrando aparte a qué zona pertenece el valor

La configuración recomendada en un proyecto nuevo es fijar la time_zone del servidor en UTC, usar TIMESTAMP y concentrar la conversión de visualización en la capa de aplicación.

La variable de sesión time_zone

Después de conectarse, SET time_zone = 'Asia/Tokyo' hace que las lecturas de TIMESTAMP posteriores, NOW() y CURRENT_TIMESTAMP() usen la zona especificada. Para usar nombres de zona IANA, el servidor debe tener los datos de zona horaria cargados mediante mysql_tzinfo_to_sql. Muchas imágenes Docker oficiales de MySQL no incluyen estos datos, por lo que SET time_zone = 'Asia/Tokyo' falla con ERROR 1298 a menos que se carguen previamente.

Si cargar los datos IANA no es práctico, se puede usar la notación de desplazamiento como SET time_zone = '+09:00' en su lugar. Esto funciona para zonas con desplazamientos estables durante todo el año pero no puede representar el horario de verano. Para sistemas que sirven localidades con horario de verano como Estados Unidos o Australia, los nombres IANA son esenciales y el esfuerzo inicial de cargar los datos de zona vale la pena.

Problemas del controlador JDBC - serverTimezone y connectionTimeZone

MySQL Connector/J acepta un parámetro serverTimezone o, en la versión 8.0+, connectionTimeZone en la URL de JDBC. El valor recomendado en versiones modernas del controlador es connectionTimeZone=SERVER (que respeta la configuración del lado del servidor) o un nombre de zona IANA explícito, y el valor predeterminado es LOCAL, es decir la zona horaria por defecto de la JVM. Si se omite el parámetro, es ese valor predeterminado el que desplaza en silencio los valores de Instant u OffsetDateTime de Java al escribir. Además, connectionTimeZone por sí solo no modifica la variable de sesión time_zone del servidor, por lo que hay que añadir forceConnectionTimeZoneToSession=true cuando la sesión también deba seguirlo.

Un accidente de producción común es código que funcionaba localmente porque el JDK del desarrollador y MySQL coincidían en JST, pero produce desfases de 9 horas en un servidor UTC en producción. La solución es especificar connectionTimeZone explícitamente en la URL de JDBC y migrar todo el uso de java.util.Date a tipos de java.time como Instant u OffsetDateTime. Date lleva semántica oculta de zona horaria local que las capas JDBC convierten de formas sorprendentes.

Semántica de DEFAULT CURRENT_TIMESTAMP

El patrón TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP se usa ampliamente para columnas created_at y updated_at. El tiempo actual se toma de la time_zone de la sesión, luego se almacena internamente como UTC. Si la time_zone del servidor es UTC, los valores almacenados corresponden exactamente al segundo de época Unix. Si es JST, lo mismo es cierto tras la conversión, pero cambiar la time_zone del servidor posteriormente no reinterpreta retroactivamente las filas existentes.

Las columnas DATETIME también pueden tener como predeterminado CURRENT_TIMESTAMP desde MySQL 5.6, pero DATETIME no almacena zona horaria, así que cambiar la time_zone del servidor cambia el significado de cualquier valor escrito antes del cambio. Esto hace que la time_zone del servidor sea una decisión casi irreversible una vez que se han acumulado datos, y cualquier cambio debe planificarse con plena conciencia de la interpretación histórica.

Práctica recomendada - UTC de extremo a extremo

Para proyectos nuevos, configure la time_zone del servidor en UTC, use columnas TIMESTAMP y represente los valores en el código de aplicación como Instant u OffsetDateTime. La capa de presentación es el único lugar que convierte a la zona horaria local del usuario. Esto elimina toda una clase de errores y hace que los despliegues multirregión sean directos, porque cada réplica interpreta los valores almacenados de forma idéntica.

Los sistemas existentes que funcionan con servidores en JST con columnas DATETIME no pueden cambiar de la noche a la mañana. El plan pragmático es usar TIMESTAMP para columnas nuevas, configurar connectionTimeZone explícitamente en las URL de JDBC, registrar marcas temporales con desplazamientos UTC explícitos y documentar el significado previsto de cada columna existente. Una migración de varios trimestres que ajusta el esquema columna por columna es mucho más segura que un corte total en un solo día.

XB!LINE

¿Te resultó útil este artículo?

Artículos Relacionados