Postgres AT TIME ZONE 'UTC' no hace lo que crees que hace

Footguns with Postgres "at time zone 'UTC'"

Postgres AT TIME ZONE 'UTC' no hace lo que crees que hace

AT TIME ZONE 'UTC' convierte timestamptz a timestamp sin zona horaria, un tipo desaconsejado que provoca errores sutiles. Sumar meses depende de la zona horaria, y comparar timestamp con timestamptz siempre da false. La solución es aplicar AT TIME ZONE 'UTC' dos veces: una antes de sumar el intervalo y otra después para volver a timestamptz.

Comparar un timestamp y un timestamptz siempre dará false. Tiene sentido porque timestamp no es un punto real en el tiempo. La comparación es técnicamente absurda.
  1. heurekamala

    El texto dice: "Comparar un timestamp y un timestamptz siempre dará false."

    Eso es incorrecto. Postgres convierte el timestamp a timestamptz muy silenciosamente en segundo plano.

    Si usa la zona horaria de la sesión para esto.

    Si es true o false depende de la configuración de TimeZone. Esto es peor que "siempre false".

    En producción con UTC funciona. En el portátil de un desarrollador de California no funciona.

    Acabo de probarlo:

    SET TIME ZONE 'America/Los_Angeles';

    SELECT '2026-03-01 00:00:00'::timestamp = '2026-03-01 00:00:00+00'::timestamptz; /* f */

    SET TIME ZONE 'UTC';

    SELECT '2026-03-01 00:00:00'::timestamp = '2026-03-01 00:00:00+00'::timestamptz; /* t */

    Si puedes, usa Postgres 16.

    El asunto del doble AT TIME ZONE 'UTC' del texto no es necesario ahí. Ahí tienes date_add con una zona horaria como tercer argumento:

    date_add(b.month_start, interval '1 month', 'UTC')

    Esto suma el mes en UTC y sigue siendo un timestamptz.

  2. ulrikrasmussen

    El estándar SQL es, por desgracia, realmente horrible en lo que respecta al manejo del tiempo. El tipo `timestamp` no es un timestamp en absoluto porque no codifica un punto único en el tiempo, solo almacena una fecha y una hora que deben interpretarse en relación con una zona horaria. Debería llamarse "datetime".

    Mover un Java Instant de ida y vuelta entre una base de datos también es una tarea sorprendentemente difícil de hacer bien, y no ayuda que JDBC simplemente lo maneje completamente mal si usas sus métodos setTimestamp/getTimestamp. No porque sea un mal diseño con trampas, sino porque la implementación está simplemente mal y corromperá tus datos si tratas con instants cuya fecha calendario está lo suficientemente en el pasado debido a que usa la API heredada de fecha/hora que cambia al calendario gregoriano para fechas pasadas.

    El nombre `timestamp with time zone` también es engañoso porque en realidad no almacena una zona horaria, almacena el número de segundos desde la época como un java.time.Instant (aunque con una resolución diferente). La parte "with time zone" solo se refiere al formato textual en el que denotas los valores, que incluye la zona horaria después de la parte de fecha/hora para identificar de forma única un timestamp, pero la zona horaria se descarta y no se almacena después de que se ha parseado el valor. Esto es diferente de, por ejemplo, `ZonedDateTime` en Java, que sí almacenará el offset y por lo tanto corresponde a un par de (Instant, TimeZone).

  3. 1a527dd5

    Mi biblia: https://wiki.postgresql.org/wiki/Don't_Do_This

  4. Macha

    Mi experiencia es que (a pesar del consejo de los desarrolladores de Postgres de usarlo), timestamptz es en gran medida un tipo de dato inútil, ya que no es más que un envoltorio alrededor de la conversión a UTC.

    - ¿Estás almacenando eventos pasados? Solo guárdalos como UTC. Quizás uses timestamptz para que lo haga por ti, pero en realidad es una interfaz más obtusa para eso que timestamp

    - ¿Estás almacenando tiempos UTC futuros? Genial, solo usa UTC, mira arriba

    - ¿Estás almacenando tiempos humanos futuros? Entonces timestamptz es activamente dañino porque convierte ansiosamente a UTC, así que incluso si recibes una tzdb actualizada a tiempo para cuando llegue el evento, no sabes qué pasó en el momento de la escritura, así que ahora tu datetime es ambiguo. Es menos roto usar un timestamp simple + una columna de cadena de zona horaria (si necesitas ordenar por ello, quizás también una columna _utc desnormalizada, con el entendimiento de que tendrás que regenerarla o aceptar ligeros errores de desfase cuando actualices la tzdb, pero al menos puedes hacer esto cuando sabes cuál fue el valor de entrada, a diferencia de con timestamptz)

  5. layer8

    > Sumar un mes con + INTERVAL '1 months' depende de la zona horaria. […]

    Sumar meses no está bien definido de todos modos, incluso usando date, para días del mes > 28. Creo que es un error que los sistemas permitan genéricamente tal cálculo (a diferencia del código de aplicación que implementa reglas de negocio específicas del dominio).

Más de este día

2026-09-28