UTC_TIMESTAMP
La función UTC_TIMESTAMP devuelve la fecha y hora actuales en formato UTC (Tiempo Universal Coordinado), combinando en un solo valor lo que UTC_DATE y UTC_TIME ofrecen por separado. Es la función más recomendada para registrar marcas de tiempo en aplicaciones globales, ya que proporciona un punto de referencia universal e inequívoco para cada evento.
A diferencia de NOW(), que devuelve la fecha y hora según la zona horaria de la sesión, UTC_TIMESTAMP siempre refleja el momento exacto en la escala de tiempo universal. Esto significa que si dos servidores en continentes diferentes ejecutan UTC_TIMESTAMP en el mismo instante, ambos obtendrán el mismo valor, algo que no se puede garantizar con NOW().
Sintaxis
UTC_TIMESTAMP
UTC_TIMESTAMP([fsp])El parámetro opcional fsp (fractional seconds precision) acepta valores de 0 a 6 y determina la cantidad de decimales en los segundos. Sin argumento, devuelve DATETIME sin fracciones. En contexto de cadena el formato es YYYY-MM-DD HH:MM:SS, y en contexto numérico es YYYYMMDDHHMMSS.
Comportamiento básico
La diferencia entre UTC_TIMESTAMP y NOW se hace evidente cuando la sesión utiliza una zona horaria distinta de UTC.
SET time_zone = 'America/Argentina/Buenos_Aires';
SELECT
UTC_TIMESTAMP() AS utc,
NOW() AS local_buenos_aires;| utc | local_buenos_aires |
|---|---|
| 2026-02-14 22:30:15 | 2026-02-14 19:30:15 |
Buenos Aires está en UTC-3, por lo que la hora local es tres horas menor. Si cambias la zona horaria de la sesión, NOW() cambia pero UTC_TIMESTAMP() permanece constante.
SET time_zone = 'Asia/Tokyo';
SELECT
UTC_TIMESTAMP() AS utc,
NOW() AS local_tokyo;| utc | local_tokyo |
|---|---|
| 2026-02-14 22:30:15 | 2026-02-15 07:30:15 |
Observa cómo en Tokio (UTC+9) ya es el día siguiente, mientras que UTC sigue en el 14 de febrero. Este ejemplo demuestra por qué almacenar siempre en UTC evita confusiones cuando los datos se consultan desde diferentes regiones.
Para obtener precisión de milisegundos, que es lo más habitual en aplicaciones modernas, se utiliza UTC_TIMESTAMP(3).
SELECT UTC_TIMESTAMP(3) AS utc_milisegundos;| utc_milisegundos |
|---|
| 2026-02-14 22:30:15.847 |
Caso práctico: columnas created_at y updated_at
El uso más extendido de UTC_TIMESTAMP es como valor por defecto en las columnas de auditoría de las tablas. Esta práctica garantiza que cada registro tenga una marca de tiempo consistente.
CREATE TABLE productos (
id INT AUTO_INCREMENT PRIMARY KEY,
nombre VARCHAR(200) NOT NULL,
precio DECIMAL(10,2) NOT NULL,
stock INT DEFAULT 0,
created_at DATETIME(3) DEFAULT (UTC_TIMESTAMP(3)),
updated_at DATETIME(3) DEFAULT (UTC_TIMESTAMP(3)) ON UPDATE CURRENT_TIMESTAMP(3)
);
INSERT INTO productos (nombre, precio, stock)
VALUES
('Laptop ThinkPad X1', 1299.99, 45),
('Monitor LG 4K 27"', 449.99, 120),
('Teclado mecánico Keychron', 89.99, 300);
SELECT nombre, precio, created_at
FROM productos;| nombre | precio | created_at |
|---|---|---|
| Laptop ThinkPad X1 | 1299.99 | 2026-02-14 22:30:15.847 |
| Monitor LG 4K 27" | 449.99 | 2026-02-14 22:30:15.912 |
| Teclado mecánico Keychron | 89.99 | 2026-02-14 22:30:15.934 |
Cada inserción queda marcada con el momento UTC exacto en que ocurrió, con precisión de milisegundos para distinguir inserciones en lote.
Caso práctico: auditoría de cambios de precio
Un sistema de comercio electrónico registra cada cambio de precio para análisis histórico y cumplimiento regulatorio. Todas las marcas de tiempo se almacenan en UTC.
CREATE TABLE historial_precios (
id INT AUTO_INCREMENT PRIMARY KEY,
producto_id INT NOT NULL,
precio_anterior DECIMAL(10,2),
precio_nuevo DECIMAL(10,2) NOT NULL,
cambiado_por VARCHAR(100),
cambiado_en DATETIME(3) DEFAULT (UTC_TIMESTAMP(3))
);
SELECT
p.nombre,
hp.precio_anterior,
hp.precio_nuevo,
ROUND(
(hp.precio_nuevo - hp.precio_anterior) / hp.precio_anterior * 100, 1
) AS variacion_porcentaje,
hp.cambiado_por,
hp.cambiado_en
FROM historial_precios hp
JOIN productos p ON hp.producto_id = p.id
WHERE hp.cambiado_en >= UTC_TIMESTAMP() - INTERVAL 7 DAY
ORDER BY hp.cambiado_en DESC;| nombre | precio_anterior | precio_nuevo | variacion_porcentaje | cambiado_por | cambiado_en |
|---|---|---|---|---|---|
| Laptop ThinkPad X1 | 1399.99 | 1299.99 | -7.1 | admin@tienda.com | 2026-02-14 15:20:33.412 |
| Monitor LG 4K 27" | 399.99 | 449.99 | 12.5 | admin@tienda.com | 2026-02-12 09:45:12.738 |
Al filtrar con UTC_TIMESTAMP() - INTERVAL 7 DAY, obtienes exactamente los cambios de la última semana en tiempo universal, sin importar desde qué zona horaria se ejecute la consulta.
Caso práctico: comparar tiempos entre zonas horarias
Cuando tienes usuarios distribuidos globalmente y quieres mostrar cuánto tiempo ha pasado desde un evento, trabajar en UTC simplifica enormemente los cálculos.
SELECT
u.nombre,
u.pais,
u.ultimo_acceso_utc,
TIMESTAMPDIFF(MINUTE, u.ultimo_acceso_utc, UTC_TIMESTAMP()) AS minutos_inactivo,
CASE
WHEN TIMESTAMPDIFF(MINUTE, u.ultimo_acceso_utc, UTC_TIMESTAMP()) < 5 THEN 'En línea'
WHEN TIMESTAMPDIFF(MINUTE, u.ultimo_acceso_utc, UTC_TIMESTAMP()) < 30 THEN 'Reciente'
WHEN TIMESTAMPDIFF(HOUR, u.ultimo_acceso_utc, UTC_TIMESTAMP()) < 24 THEN 'Hoy'
ELSE 'Inactivo'
END AS estado
FROM usuarios u
WHERE u.activo = 1
ORDER BY u.ultimo_acceso_utc DESC
LIMIT 5;| nombre | pais | ultimo_acceso_utc | minutos_inactivo | estado |
|---|---|---|---|---|
| Yuki Tanaka | Japón | 2026-02-14 22:28:10.000 | 2 | En línea |
| María Rodríguez | España | 2026-02-14 22:15:45.000 | 15 | Reciente |
| John Smith | EE.UU. | 2026-02-14 18:30:00.000 | 240 | Hoy |
| Ana López | México | 2026-02-13 14:20:00.000 | 1930 | Inactivo |
| Hans Müller | Alemania | 2026-02-13 08:10:00.000 | 2300 | Inactivo |
Dado que tanto ultimo_acceso_utc como UTC_TIMESTAMP() están en la misma referencia temporal, la diferencia calculada por TIMESTAMPDIFF es siempre precisa, sin importar la zona horaria del usuario o del servidor.
Manejo de NULL
UTC_TIMESTAMP nunca devuelve NULL por sí misma. Sin embargo, al compararla con columnas que pueden ser nulas, el resultado de la expresión puede verse afectado.
SELECT
nombre,
fecha_vencimiento_utc,
CASE
WHEN fecha_vencimiento_utc IS NULL THEN 'Sin vencimiento'
WHEN fecha_vencimiento_utc < UTC_TIMESTAMP() THEN 'Vencido'
ELSE 'Vigente'
END AS estado
FROM licencias;| nombre | fecha_vencimiento_utc | estado |
|---|---|---|
| Plan Básico | 2026-01-31 23:59:59.000 | Vencido |
| Plan Profesional | 2026-12-31 23:59:59.000 | Vigente |
| Plan Enterprise | NULL | Sin vencimiento |
El CASE evalúa IS NULL antes de la comparación, evitando que una comparación con NULL produzca un resultado indeterminado.
Combinación con otras funciones
UTC_TIMESTAMP se integra naturalmente con funciones de extracción y aritmética de fechas.
SELECT
UTC_TIMESTAMP() AS ahora_utc,
DATE(UTC_TIMESTAMP()) AS solo_fecha,
TIME(UTC_TIMESTAMP()) AS solo_hora,
DATE_ADD(UTC_TIMESTAMP(), INTERVAL 48 HOUR) AS en_48_horas,
UNIX_TIMESTAMP(UTC_TIMESTAMP()) AS epoch;| ahora_utc | solo_fecha | solo_hora | en_48_horas | epoch |
|---|---|---|---|---|
| 2026-02-14 22:30:15 | 2026-02-14 | 22:30:15 | 2026-02-16 22:30:15 | 1771111815 |
También puedes combinarla con DATE_FORMAT para generar representaciones legibles y con CONVERT_TZ para mostrar al usuario la hora en su zona local.
SELECT
UTC_TIMESTAMP() AS utc,
CONVERT_TZ(UTC_TIMESTAMP(), '+00:00', 'Europe/Madrid') AS hora_madrid,
CONVERT_TZ(UTC_TIMESTAMP(), '+00:00', 'America/Lima') AS hora_lima;| utc | hora_madrid | hora_lima |
|---|---|---|
| 2026-02-14 22:30:15 | 2026-02-14 23:30:15 | 2026-02-14 17:30:15 |
Esta combinación de almacenar en UTC y convertir al mostrar es el patrón estándar en la industria para el manejo correcto de zonas horarias.
En el siguiente artículo veremos CONVERT_TZ para convertir entre zonas horarias.
Escrito por Eduardo Lázaro
