PostgreSQL
 sql >> Base de Dados >  >> RDS >> PostgreSQL

Convertendo entre fusos horários no Postgres


Deixe-me explicar os dois exemplos:

Em ambos, assumimos um fuso horário UTC (ou seja, SET timezone TO UTC ).
db=# SELECT timezone('US/Pacific', '2016-01-01 00:00');
      timezone
---------------------
 2015-12-31 16:00:00
(1 row)

Isso é equivalente a SELECT timezone('US/Pacific', '2016-01-01 00:00'::timestamptz) , ou seja, o Postgres converteu implicitamente a string em um timestamptz .

Sabemos que o timezone função converte para frente e para trás entre timestamp e timestamptz :



Já que estamos dando um timestamptz como entrada, ele produzirá um timestamp . Em outras palavras, está convertendo o ponto absoluto no tempo 2016-01-01 00:00Z para um tempo de parede em US/Pacific , ou seja, o que o relógio em Los Angeles mostrou naquele momento absoluto.

No exemplo 2 estamos fazendo o oposto, ou seja, pegando um timestamp e convertendo para um timestamptz . Em outras palavras, estamos perguntando:qual foi o momento absoluto em que o relógio em Los Angeles mostrou 2016-01-01 00:00 ?

Você menciona:

'2016-01-01 00:00'::timestamp é um timestamp , ou seja, um tempo de parede. Não tem noção de fuso horário.

Acho que você pode não ter entendido completamente a diferença entre timestamp e timestamptz , que é fundamental aqui. Pense neles como tempo de parede , ou seja, a hora exibida em algum lugar do mundo em um relógio pendurado na parede e a hora absoluta , ou seja, o tempo absoluto em nosso universo.

Os exemplos que você faz em sua própria resposta não são muito precisos.
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);

O problema com seu exemplo é que você está construindo um conjunto de dados com uma única coluna. Como uma coluna pode ter apenas um tipo, cada linha (ou valor único neste caso) está sendo convertida para o mesmo tipo, ou seja, timestamptz , embora alguns valores tenham sido calculados como timestamp (por exemplo, valor 3). Assim, você tem uma conversão implícita adicional aqui.

Vamos dividir o exemplo em consultas separadas e ver o que está acontecendo:

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

Como você já deve saber, timestamptz '2012-03-05 17:00:00+0' e '2012-03-05 17:00:00+0'::timestamptz são equivalentes (prefiro o último). Assim, apenas para usar a mesma sintaxe do artigo, vou reescrever:
db=# SELECT '2012-03-05 17:00:00+0'::timestamptz;
      timestamptz
------------------------
 2012-03-05 17:00:00+00

Agora, o que está acontecendo aqui? Bem, menos do que em sua explicação original. A string é simplesmente analisada como um timestamptz . Quando o resultado é impresso, ele usa o timezone atualmente definido config para convertê-lo de volta em uma representação legível por humanos da estrutura de dados subjacente, ou seja, 2012-03-05 17:00:00+00 .

Vamos alterar o timezone config e veja o que acontece:
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

A única coisa que mudou foi como o timestamptz é impresso na tela, ou seja, usando o Europa/Berlim fuso horário.

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

Novamente, apenas analisando a data.

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

É o mesmo que '2012-03-05 18:00:00+1'::timestamp . O que acontece aqui é que o deslocamento do fuso horário é simplesmente ignorado porque você está solicitando um timestamp .

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

Vamos reescrever para ser mais simples:
db=# SELECT timezone('+6', '2012-03-05 11:00:00'::timestamp);
        timezone
------------------------
 2012-03-05 17:00:00+00
(1 row)

Isso está perguntando:qual foi a hora absoluta em que o relógio na parede no fuso horário com um deslocamento de +6 horas estava mostrando 2012-03-05 11:00:00 ?

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

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

Isso está perguntando:qual foi a hora absoluta em que o relógio na parede no fuso horário UTC estava mostrando 2012-03-05 17:00:00 ?

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

Aqui você está transmitindo duas vezes para timestamp , o que não faz diferença. Vamos simplificar:
db=# SELECT '2012-03-05 17:00:00'::timestamp;
      timestamp
---------------------
 2012-03-05 17:00:00
(1 row)

Isso está claro, eu acho.

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

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

Você está analisando primeiro a string como um timestamp e, em seguida, convertê-lo para um timestamptz usando o timezone atualmente definido . Se alterarmos o timezone , obtemos outra coisa porque o Postgres assume esse fuso horário ao converter um timestamp (ou uma string sem informações de fuso horário) para 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)

Esse tempo absoluto, expresso em UTC, é 2012-03-05 16:00:00+00 , portanto diferente do exemplo original.

Eu espero que isto esclareça as coisas. Novamente, entendendo a diferença entre timestamp e timestamptz é chave. Pense em tempo de parede versus tempo absoluto.