sql >> Base de Datos >  >> RDS >> PostgreSQL

Conversión entre zonas horarias en Postgres

Permítanme explicar los dos ejemplos:

En ambos asumimos una zona horaria UTC (es decir, SET timezone TO UTC ).

db=# SELECT timezone('US/Pacific', '2016-01-01 00:00');
      timezone
---------------------
 2015-12-31 16:00:00
(1 row)

Esto es equivalente a SELECT timezone('US/Pacific', '2016-01-01 00:00'::timestamptz) , es decir, Postgres convirtió implícitamente la cadena en un timestamptz .

Sabemos que la timezone la función convierte de un lado a otro entre timestamp y timestamptz :

Ya que le estamos dando un timestamptz como entrada, generará una timestamp . En otras palabras, está convirtiendo el punto absoluto en el tiempo 2016-01-01 00:00Z a un muro de tiempo en US/Pacific , es decir, lo que mostraba el reloj de Los Ángeles en ese momento absoluto.

En el ejemplo 2, estamos haciendo lo contrario, es decir, tomando una timestamp y convertirlo a un timestamptz . En otras palabras, estamos preguntando:¿cuál fue el punto absoluto en el tiempo cuando el reloj de Los Ángeles mostró 2016-01-01 00:00 ?

Usted menciona:

'2016-01-01 00:00'::timestamp es una timestamp , es decir, un tiempo de pared. No tiene una noción de zona horaria.

Creo que es posible que no haya entendido completamente la diferencia entre timestamp y timestamptz , que es clave aquí. Solo piense en ellos como tiempo de pared , es decir, la hora que aparecía en algún lugar del mundo en un reloj colgado en la pared, y hora absoluta , es decir, el tiempo absoluto en nuestro universo.

Los ejemplos que pones en tu propia respuesta no son del todo exactos.

SELECT ts FROM  (VALUES
(timestamptz '2012-03-05 17:00:00+0') -- outputs 2012-03-05 17:00:00+00 --1
,(timestamptz '2012-03-05 18:00:00+1') -- outputs 2012-03-05 17:00:00+00 --2
,(timestamp   '2012-03-05 18:00:00+1') -- outputs 2012-03-05 18:00:00+00 --3
,(timestamp   '2012-03-05 11:00:00'  AT TIME ZONE '+6') -- outputs 2012-03-05 17:00:00+00 --4
,(timestamp   '2012-03-05 17:00:00'  AT TIME ZONE 'UTC') -- outputs 2012-03-05 17:00:00+00 --5
,(timestamp   '2012-03-05 17:00:00'::timestamp) -- outputs 2012-03-05 17:00:00+00 --6
,(timestamp   '2012-03-05 17:00:00'::timestamptz) -- outputs 2012-03-05 17:00:00+00 --7
    ) t(ts);

El problema con su ejemplo es que está construyendo un conjunto de datos con una sola columna. Dado que una columna solo puede tener un tipo, cada fila (o valor único en este caso) se convierte al mismo tipo, a saber, timestamptz , aunque algunos valores se calcularon como timestamp (por ejemplo, valor 3). Por lo tanto, aquí tiene una conversión implícita adicional.

Dividamos el ejemplo en consultas separadas y veamos qué está pasando:

Ejemplo 1

db=# SELECT timestamptz '2012-03-05 17:00:00+0';
      timestamptz
------------------------
 2012-03-05 17:00:00+00

Como ya sabrá, timestamptz '2012-03-05 17:00:00+0' y '2012-03-05 17:00:00+0'::timestamptz son equivalentes (prefiero lo segundo). Por lo tanto, solo para usar la misma sintaxis que en el artículo, reescribiré:

db=# SELECT '2012-03-05 17:00:00+0'::timestamptz;
      timestamptz
------------------------
 2012-03-05 17:00:00+00

Ahora, ¿qué está pasando aquí? Bueno, menos que en tu explicación original. La cadena simplemente se analiza como un timestamptz . Cuando se imprime el resultado, utiliza la timezone configurada actualmente config para volver a convertirlo en una representación legible por humanos de la estructura de datos subyacente, es decir, 2012-03-05 17:00:00+00 .

Cambiemos la timezone config y ver qué pasa:

db=# SET timezone TO 'Europe/Berlin';
SET
db=# SELECT '2012-03-05 17:00:00+0'::timestamptz;
      timestamptz
------------------------
 2012-03-05 18:00:00+01

Lo único que cambió es cómo el timestamptz se imprime en la pantalla, es decir, utilizando el Europa/Berlín zona horaria.

Ejemplo 2

db=# SELECT timestamptz '2012-03-05 18:00:00+1';
      timestamptz
------------------------
 2012-03-05 17:00:00+00
(1 row)

De nuevo, solo analizando la fecha.

Ejemplo 3

db=# SELECT timestamp '2012-03-05 18:00:00+1';
      timestamp
---------------------
 2012-03-05 18:00:00
(1 row)

Esto es lo mismo que '2012-03-05 18:00:00+1'::timestamp . Lo que sucede aquí es que el desplazamiento de la zona horaria simplemente se ignora porque está solicitando una timestamp .

Ejemplo 4

db=# SELECT timestamp '2012-03-05 11:00:00' AT TIME ZONE '+6';
        timezone
------------------------
 2012-03-05 17:00:00+00
(1 row)

Reescribamos para que sea más simple:

db=# SELECT timezone('+6', '2012-03-05 11:00:00'::timestamp);
        timezone
------------------------
 2012-03-05 17:00:00+00
(1 row)

Esto es preguntar:¿cuál era la hora absoluta cuando el reloj en la pared en la zona horaria con un desplazamiento de +6 horas mostraba 2012-03-05 11:00:00 ?

Ejemplo 5

db=# SELECT timestamp '2012-03-05 17:00:00' AT TIME ZONE 'UTC';
        timezone
------------------------
 2012-03-05 17:00:00+00
(1 row)

Reescribamos:

db=# SELECT timezone('UTC', '2012-03-05 17:00:00'::timestamp);
        timezone
------------------------
 2012-03-05 17:00:00+00
(1 row)

Esto es preguntar:¿cuál era la hora absoluta cuando el reloj en la pared en la zona horaria UTC mostraba 2012-03-05 17:00:00 ?

Ejemplo 6

db=# SELECT timestamp '2012-03-05 17:00:00'::timestamp;
      timestamp
---------------------
 2012-03-05 17:00:00
(1 row)

Aquí estás transmitiendo dos veces a timestamp , lo que no hace ninguna diferencia. Simplifiquemos:

db=# SELECT '2012-03-05 17:00:00'::timestamp;
      timestamp
---------------------
 2012-03-05 17:00:00
(1 row)

Eso está claro, creo.

Ejemplo 7

db=# SELECT timestamp '2012-03-05 17:00:00'::timestamptz;
      timestamptz
------------------------
 2012-03-05 17:00:00+00
(1 row)

Reescribamos:

db=# SELECT ('2012-03-05 17:00:00'::timestamp)::timestamptz;
      timestamptz
------------------------
 2012-03-05 17:00:00+00
(1 row)

Primero está analizando la cadena como una timestamp y luego convertirlo a un timestamptz utilizando la timezone configurada actualmente . Si cambiamos la timezone , obtenemos algo más porque Postgres asume esa zona horaria al convertir una timestamp (o una cadena sin información de zona horaria) a timestamptz :

db=# SET timezone TO 'Europe/Berlin';
SET
db=# SELECT ('2012-03-05 17:00:00'::timestamp)::timestamptz;
      timestamptz
------------------------
 2012-03-05 17:00:00+01
(1 row)

Esta hora absoluta, expresada en UTC, es 2012-03-05 16:00:00+00 , por lo tanto diferente del ejemplo original.

Espero que esto aclare las cosas. Nuevamente, entendiendo la diferencia entre timestamp y timestamptz es clave. Piensa en el tiempo de la pared frente al tiempo absoluto.