Diferencia entre marcas de tiempo con / sin zona horaria en PostgreSQL

186

¿Los valores de marca de tiempo se almacenan de manera diferente en PostgreSQL cuando el tipo de datos es WITH TIME ZONEversus WITHOUT TIME ZONE? ¿Se pueden ilustrar las diferencias con casos de prueba simples?

Larsenal
fuente
3
Esta respuesta relacionada puede ser de ayuda.
Erwin Brandstetter

Respuestas:

157

Las diferencias están cubiertas en la documentación de PostgreSQL para los tipos de fecha / hora . Sí, el tratamiento de TIMEo TIMESTAMPdifiere entre uno WITH TIME ZONEo WITHOUT TIME ZONE. No afecta cómo se almacenan los valores; afecta cómo se interpretan.

Los efectos de las zonas horarias en estos tipos de datos se tratan específicamente en los documentos. La diferencia surge de lo que el sistema puede saber razonablemente sobre el valor:

  • Con una zona horaria como parte del valor, el valor se puede representar como una hora local en el cliente.

  • Sin una zona horaria como parte del valor, la zona horaria predeterminada obvia es UTC, por lo que se representa para esa zona horaria.

El comportamiento difiere dependiendo de al menos tres factores:

  • La configuración de la zona horaria en el cliente.
  • El tipo de datos (es decir, WITH TIME ZONEo WITHOUT TIME ZONE) del valor.
  • Si el valor se especifica con una zona horaria particular.

Aquí hay ejemplos que cubren las combinaciones de esos factores:

foo=> SET TIMEZONE TO 'Japan';
SET
foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 00:00:00+09
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 06:00:00+09
(1 row)

foo=> SET TIMEZONE TO 'Australia/Melbourne';
SET
foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 00:00:00+11
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP;
      timestamp      
---------------------
 2011-01-01 00:00:00
(1 row)

foo=> SELECT '2011-01-01 00:00:00+03'::TIMESTAMP WITH TIME ZONE;
      timestamptz       
------------------------
 2011-01-01 08:00:00+11
(1 row)
nariz grande
fuente
88
Corrija solo si se refiere al proceso de inserción / recuperación de valores. Pero los lectores deben entender que ambos tipos de datos timestamp with time zoney timestamp without time zone, en Postgres *, en realidad no almacenan información de zona horaria. Puede confirmar esto con un vistazo a la página de documentos de tipo de datos: ambos tipos ocupan el mismo número de octetos y tienen el rango de valores guardados, por lo que no hay espacio para almacenar información de zona horaria. El texto de la página confirma esto. Algo inapropiado: "sin tz" significa "ignorar el desplazamiento al insertar datos" y "con tz" significa "utilizar el desplazamiento para ajustar a UTC".
Basil Bourque
42
Los tipos de datos son incorrectos en una segunda forma: dicen "zona horaria" pero en realidad estamos hablando de compensación de UTC / GMT. Una zona horaria es en realidad un desplazamiento más reglas / historial sobre el horario de verano (DST) y otras anomalías.
Basil Bourque
44
Prefiero decir que un desplazamiento es una zona horaria más reglas para el horario de verano. No puede descubrir la zona horaria dada una compensación, pero puede descubrir la compensación dada la zona horaria y las reglas de horario de verano.
igorsantos07
3
Citando el documento oficial : todas las fechas y horas con reconocimiento de zona horaria se almacenan internamente en UTC. Se convierten a la hora local en la zona especificada por el parámetro de configuración TimeZone antes de mostrarse al cliente.
Guillaume Husta
2
@ igorsantos07 Una zona horaria es el conjunto de reglas / historial sobre los cambios de horario de verano y otros cambios. Tu redacción parece superflua. Y su afirmación de que "una compensación es una zona horaria más reglas para el horario de verano" es simplemente incorrecta: una compensación es simplemente una cantidad de horas, minutos y segundos, nada más y nada menos.
Basil Bourque
34

Intento explicarlo de manera más comprensible que la documentación de PostgreSQL referida.

Ninguna de las TIMESTAMPvariantes almacena una zona horaria (o un desplazamiento), a pesar de lo que sugieren los nombres. La diferencia está en la interpretación de los datos almacenados (y en la aplicación prevista), no en el formato de almacenamiento en sí:

  • TIMESTAMP WITHOUT TIME ZONEalmacena la fecha y hora local (también conocida como fecha del calendario de pared y hora del reloj de pared). Su zona horaria no está especificada por lo que PostgreSQL puede decir (aunque su aplicación puede saber qué es). Por lo tanto, PostgreSQL no realiza una conversión relacionada con la zona horaria en la entrada o salida. Si el valor se ingresó en la base de datos como '2011-07-01 06:30:30', entonces no importa en qué zona horaria lo muestre más tarde, seguirá diciendo año 2011, mes 07, día 01, 06 horas, 30 minutos y 30 segundos (en algún formato). Además, cualquier desplazamiento o la zona horaria que especifique en la entrada es ignorada por PostgreSQL, por lo que '2011-07-01 06:30:30+00'y '2011-07-01 06:30:30+05'son los mismos que acaba '2011-07-01 06:30:30'. Para desarrolladores de Java: es análogo a java.time.LocalDateTime.

  • TIMESTAMP WITH TIME ZONEalmacena un punto en la línea de tiempo UTC. El aspecto (cuántas horas, minutos, etc.) depende de su zona horaria, pero siempre se refiere al mismo instante "físico" (como el momento de un evento físico real). La entrada se convierte internamente a UTC, y así es como se almacena. Para eso, se debe conocer el desplazamiento de la entrada, por lo que cuando la entrada no contiene un desplazamiento explícito o zona horaria (como '2011-07-01 06:30:30') se supone que está en la zona horaria actual de la sesión de PostgreSQL; de lo contrario, se utiliza la compensación o zona horaria especificada explícitamente (como en '2011-07-01 06:30:30+05'). La salida se muestra convertida a la zona horaria actual de la sesión de PostgreSQL. Para desarrolladores de Java: es análogo java.time.Instant(aunque con una resolución más baja), pero con JDBC y JPA 2.2 se supone que debes mapearlo java.time.OffsetDateTime( java.util.Dateojava.sql.Timestamp por supuesto).

Algunos dicen que ambas TIMESTAMPvariaciones almacenan la fecha y hora UTC. Más o menos, pero es confuso decirlo así en mi opinión. TIMESTAMP WITHOUT TIME ZONEse almacena como un TIMESTAMP WITH TIME ZONE, que se representa con la zona horaria UTC y da el mismo año, mes, día, horas, minutos, segundos y microsegundos que en la fecha y hora local. Pero no está destinado a representar el punto en la línea de tiempo que dice la interpretación UTC, es solo la forma en que se codifican los campos de fecha y hora locales. (Es un grupo de puntos en la línea de tiempo, ya que la zona de tiempo real no es UTC; no sabemos qué es).

ddekany
fuente
No hay nada de malo en recuperar a TIMESTAMP WITH TIME ZONEcomo a Instant. Ambos representan un punto en la línea de tiempo en UTC. Instantes preferible, en mi opinión, OffsetDateTimeya que es más autodocumentado: A TIMESTAMP WITH TIME ZONEsiempre se recupera de la base de datos como UTC, y un Instantsiempre está en UTC, por lo que es una coincidencia natural, mientras que uno OffsetDateTimepuede llevar otras compensaciones.
Basil Bourque
@BasilBourque Desafortunadamente, la especificación JDBC actual, la especificación JPA 2.2 y también la documentación JDBC de PostgreSQL solo se mencionan OffsetDateTimecomo el tipo de Java mapeado. No estoy seguro de si Instancetodavía se admite de forma no oficial en alguna parte.
ddekany
pregunta, usted dice que cualquier desplazamiento que especifique en la entrada como '2011-07-01 06:30:30+00'y '2011-07-01 06:30:30+05' se ignora, pero soy capaz de hacerlo insert into test_table (date) values ('2018-03-24T00:00:00-05:00'::timestamptz);y lo convertirá a utc correctamente. donde fecha es marca de tiempo sin zona horaria. Estoy tratando de entender cuál es el valor principal de la marca de tiempo con la zona horaria y tengo problemas.
pk1m
@ pk1m Usted complica las cosas con el ::timestamptz. Con eso, convierte la cadena a TIMESTAMP WITH TIME ZONE, y cuando eso se convierta aún más WITHOUT TIME ZONE, almacenará el día del "calendario de pared" y la hora del reloj de pared de ese instante como se ve desde su zona horaria de sesión (que tal vez sea UTC). Todavía solo será una marca de tiempo local con desplazamiento no especificado (sin zona).
ddekany
Estoy trabajando con Python, y eso es lo que se inserta cuando se inserta un objeto de fecha y hora de fecha y hora. Me parece que hay un valor en el uso de la marca de tiempo con la zona horaria, pero no es necesario manejar las zonas horarias.
pk1m
12

Aquí hay un ejemplo que debería ayudar. Si tiene una marca de tiempo con una zona horaria, puede convertir esa marca de tiempo en cualquier otra zona horaria. Si no tiene una zona horaria base, no se convertirá correctamente.

SELECT now(),
   now()::timestamp,
   now() AT TIME ZONE 'CST',
   now()::timestamp AT TIME ZONE 'CST'

Salida:

-[ RECORD 1 ]---------------------------
now      | 2018-09-15 17:01:36.399357+03
now      | 2018-09-15 17:01:36.399357
timezone | 2018-09-15 08:01:36.399357
timezone | 2018-09-16 02:01:36.399357+03
serby
fuente
55
La afirmación "no se convertirá correctamente" simplemente no es cierta. Tienes que entender qué timestampy qué timestamptzsignifica. timestamptzsignifica un punto absoluto en el tiempo (UTC) mientras que timestampdenota lo que mostró el reloj en una zona horaria determinada. Por lo tanto, cuando se convierte timestamptza una zona horaria, se pregunta qué mostró el reloj en Nueva York en este momento absoluto. mientras que al "convertir" a timestamp, se pregunta cuál fue el punto absoluto en el tiempo cuando el reloj de Nueva York mostró x.
fphilipe
El AT TIME ZONEconstructo es un desafío para la mente propio, incluso si ya comprende los tipos WITHvs. WITHOUT TIME ZONEEntonces es una elección curiosa para explicarlos. (: ( AT TIME ZONEconvierte una WITH TIME ZONEmarca de tiempo en una WITHOUT TIME ZONEmarca de tiempo, y viceversa ... no es exactamente obvio.)
ddekany
now()::timestamp AT TIME ZONE 'CST'no tiene sentido, a menos que en qué instante un reloj para la zona 'CST' muestre la hora en que se muestra actualmente su reloj local
Jasen