PostgreSQL timestamp vs timestamptz: ¿qué se almacena realmente bajo el capó?
PostgreSQL mantiene tanto timestamp como timestamptz como un único entero de 64 bits: el número de microsegundos desde 1970-01-01 00:00:00 UTC. La distinción emerge únicamente durante el formateo de datos para consumo humano.
¿Por Qué Confunde a la Gente?
- Dos columnas, una fecha… dos resultados de consulta diferentes
- Tu app inserta
2025-07-29 10:00, pero otro equipo ve02:00 - El frontend renderiza una cadena ISO que no coincide con el log del backend
Dos Latas de Melocotones: Una Simple, Una Etiquetada
| Tipo de dato | Nombre oficial | Valor almacenado | Qué ocurre en SELECT |
|---|---|---|---|
timestamp | timestamp sin zona horaria | conteo de microsegundos en bruto | Se devuelve sin cambios — Postgres nunca adivina una zona horaria |
timestamptz | timestamp con zona horaria | mismo conteo de microsegundos | Postgres aplica la configuración TimeZone de la sesión justo antes de enviar el texto |
Analogía
timestamp= una lata de melocotones sin etiqueta de origen. Sabes que es fruta, pero no dónde fue enlatada.timestamptz= una lata orgullosamente estampada “Hecho en UTC+8.” Quien la abra puede decidir si convierte el panel nutricional.
Bajo el Capó: Es Solo un Número Enorme
2000-01-01 00:00:00 UTC → 0
2000-01-01 00:00:01 UTC → 1 000 000
- Unidad: microsegundos (una millonésima de segundo)
- Rango: 4713 a.C. – 294276 d.C. — aprobado por Indiana Jones
- El almacenamiento para
timestampytimestamptzes idéntico; la interpretación difiere
Una Demostración de 15 Segundos
-- El cliente piensa en hora de Shanghái
SET TimeZone = 'Asia/Shanghai';
CREATE TABLE demo (
created_ts timestamp,
created_tz timestamptz
);
INSERT INTO demo VALUES ('2025-07-29 10:00', '2025-07-29 10:00');
| Consulta | Resultado | Por qué |
|---|---|---|
SELECT created_ts FROM demo; | 2025-07-29 10:00:00 | Valor en bruto, sin cálculo de zona horaria |
SELECT created_tz FROM demo; | 2025-07-29 10:00:00+08 | Etiqueta aplicada en la salida |
SET TimeZone = 'UTC'; y luego seleccionar | 2025-07-29 02:00:00+00 | El mismo instante, nueva perspectiva |
Aritmética de Timestamps e Intervalos
Uno de los aspectos más prácticos de los timestamps de PostgreSQL es la aritmética de intervalos. Como ambos tipos almacenan conteos de microsegundos, puedes añadir y restar intervalos directamente:
-- Añadir 3 horas y 30 minutos
SELECT '2025-07-29 10:00'::timestamptz + INTERVAL '3 hours 30 minutes';
-- → 2025-07-29 13:30:00+08
-- Encontrar la diferencia entre dos timestamps
SELECT '2025-07-30 09:00'::timestamptz - '2025-07-29 10:00'::timestamptz;
-- → 23:00:00 (un intervalo)
-- Extraer campos específicos
SELECT EXTRACT(EPOCH FROM '2025-07-29 10:00:00+08'::timestamptz);
-- → 1753768800 (timestamp Unix en segundos)
-- Truncar al límite del día (útil para agregaciones diarias)
SELECT date_trunc('day', '2025-07-29 15:42:19+08'::timestamptz);
-- → 2025-07-29 00:00:00+08
La función EXTRACT(EPOCH FROM ...) es particularmente útil cuando necesitas pasar timestamps a sistemas externos que esperan segundos de época Unix. Por el contrario, puedes convertir una época de vuelta a timestamp:
SELECT to_timestamp(1753768800);
-- → 2025-07-29 10:00:00+08 (en sesión Asia/Shanghai)
Un punto sutil pero importante: la aritmética de intervalos con timestamp (sin zona horaria) ignora completamente las transiciones de horario de verano, mientras que timestamptz las respeta. Esto significa que añadir INTERVAL '1 day' a un valor timestamptz que cruza un límite de cambio horario devolverá correctamente la misma hora en el reloj de pared — no exactamente 24 horas después.
Consideraciones de Indexación y Rendimiento
Tanto timestamp como timestamptz se almacenan como enteros de 8 bytes, por lo que no hay diferencia de rendimiento entre ellos para almacenamiento o indexación. Los índices B-tree funcionan de forma idéntica en ambos tipos porque la comparación subyacente es simplemente una comparación de enteros.
Sin embargo, hay algunas consideraciones prácticas:
- Consultas de rango:
WHERE created_at > '2025-07-01'funciona eficientemente con un índice en cualquiera de los tipos. Contimestamptz, PostgreSQL convierte el literal a UTC antes de la comparación, por lo que el índice sigue usándose. - Claves de partición: Cuando se usa particionamiento por rango en columnas timestamp,
timestamptzes generalmente más seguro porque los límites de partición son inequívocos (siempre UTC). Contimestamp, un límite como'2025-07-01 00:00'podría significar cosas diferentes para diferentes sesiones. - Índices funcionales: Si frecuentemente consultas solo por fecha (ignorando la hora), considera un índice en
date_trunc('day', created_at)para acelerar las consultas de agregación diaria.
Problemas Comunes y Soluciones Rápidas
1. Diferentes usuarios, diferentes relojes
- Causa: los clientes usan diferentes configuraciones
TimeZonecontimestamptz - Solución: mantén todo en
timestamp+ acordar una zona, o fuerzaSET TimeZone = 'UTC'en la inicialización de la conexión
Un patrón común en el código de aplicación es establecer la zona horaria una vez en la inicialización del pool de conexiones:
-- En tu configuración de conexión (p.ej., configuración del pool de pg)
SET timezone = 'UTC';
Esto garantiza que todas las sesiones vean la misma representación UTC, y la capa de aplicación gestiona la conversión a hora local para la visualización.
2. Almacenar “hora de pared” pero elegir el tipo equivocado
- Los calendarios de negocio (horarios de tienda, fechas límite) deberían usar
timestamp - Los flujos de trabajo transfronterizos (pedidos, logs) deberían almacenar UTC en
timestamptz
La prueba es simple: si la pregunta es “¿en qué momento ocurrió esto?” usa timestamptz. Si la pregunta es “¿qué muestra el reloj de pared?” usa timestamp.
3. APIs que se desvían
- Siempre envía
timestamptzcomo cadenas ISO-8601 con el desplazamiento (Zo+08:00) - Deja que la interfaz de usuario formatee localmente
4. Comparar timestamps entre tipos
Mezclar timestamp y timestamptz en comparaciones o joins es una fuente común de errores sutiles:
-- Peligroso: el cast implícito aplica la zona horaria de la sesión
SELECT * FROM orders o
JOIN schedules s ON o.created_tz = s.start_ts;
-- PostgreSQL convierte s.start_ts a timestamptz usando la zona horaria de la sesión
-- ¡Diferentes sesiones pueden obtener resultados de join diferentes!
Solución: siempre convierte explícitamente cuando compares entre tipos, o estandariza en un tipo por dominio.
5. Problemas predeterminados de los ORMs
Muchos ORMs (Django, SQLAlchemy, ActiveRecord) usan por defecto timestamp sin zona horaria. Revisa tus archivos de migración — si tu app sirve a usuarios en múltiples zonas horarias, cambia el valor predeterminado a timestamptz. En Django, establece USE_TZ = True en la configuración. En SQLAlchemy, usa DateTime(timezone=True).
Tabla de Referencia Rápida: ¿Cuál Debo Usar?
Solo calendario local → timestamp
Cualquier cosa global → timestamptz (almacenar en UTC)
- Informes financieros, horarios de clases →
timestamp - Registros de auditoría, pedidos de e-commerce →
timestamptz
Verifica en Segundos con Go Tools
| Necesidad | Herramienta | Cómo hacerlo |
|---|---|---|
| Inspeccionar el valor de época desde SQL | Conversor de Época | Pega 1690622400, haz clic en Convertir |
| Comparar dos zonas horarias de un vistazo | Conversor de Zona Horaria | Introduce 10:00 Asia/Shanghai |
| Ordenar JSON en bulk con campos de tiempo | Formateador JSON | Suelta el payload, formatea y examina |
Todas las utilidades se ejecutan completamente en tu navegador — los datos nunca salen de tu máquina.
Preguntas Frecuentes
¿Cuál es la diferencia entre timestamp y timestamptz en PostgreSQL?
timestamp (sin zona horaria) almacena un valor de fecha y hora tal cual, sin contexto de zona horaria. timestamptz (con zona horaria) convierte la entrada a UTC para su almacenamiento y vuelve a convertir a la zona horaria de la sesión en la recuperación. Usa timestamptz en casi todos los casos — previene los errores relacionados con zonas horarias en sistemas distribuidos.
¿PostgreSQL realmente almacena la zona horaria en timestamptz?
No — a pesar del nombre, PostgreSQL no almacena la zona horaria en sí. Convierte la entrada a UTC y almacena únicamente el valor UTC (un conteo de microsegundos desde 2000-01-01). En la recuperación, convierte de UTC a la zona horaria que especifique la configuración timezone de tu sesión. La información de la zona horaria original se descarta.
¿Cómo cambio la zona horaria de una sesión de PostgreSQL?
Ejecuta SET timezone = 'America/New_York'; para cambiar la zona horaria de la sesión. Esto afecta a cómo se muestran e interpretan los valores timestamptz. Para valores predeterminados a nivel de servidor, establece timezone en postgresql.conf. Usa siempre nombres de zona horaria IANA (como Asia/Shanghai) en lugar de abreviaciones (como CST) para evitar ambigüedades.
¿Debería usar timestamp o timestamptz para almacenar tiempos de eventos?
Usa timestamptz para prácticamente todo — acciones de usuario, llamadas a API, registros de auditoría y eventos programados. Solo usa timestamp (sin zona horaria) para tiempos abstractos que no están ligados a un momento específico, como “la tienda abre a las 09:00” que significa las 9 de la mañana en la zona horaria local, no un instante UTC específico.
¿Cómo maneja PostgreSQL el horario de verano con timestamptz?
PostgreSQL maneja correctamente el horario de verano cuando usa timestamptz porque almacena todo en UTC internamente. Cuando recuperas un valor, PostgreSQL convierte desde UTC usando las reglas DST actuales para la zona horaria de tu sesión. Esto significa que el mismo instante UTC almacenado muestra correctamente diferentes horas locales antes y después de una transición de horario de verano.
Para una guía completa sobre los timestamps Unix — incluyendo el manejo de precisión, las mejores prácticas de zona horaria y ejemplos de código en JavaScript, Python y Go — consulta nuestra Guía de Timestamp Unix.
Resumen
- Ambos tipos de tiempo de Postgres son contadores de microsegundos; la etiqueta es toda la diferencia
- Elegir el equivocado significa timestamps desconcertantes y cálculos rotos
- Prueba, convierte y verifica con las herramientas adecuadas para ahorrarte horas de depuración