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

PostgreSQL errado ao converter de timestamp sem fuso horário para timestamp com fuso horário

Principais pontos a serem entendidos


timestamp without time zone AT TIME ZONE reinterpreta um timestamp como estando nesse fuso horário com a finalidade de convertê-lo para UTC .

timestamp with time zone AT TIME ZONE converte um timestamptz em um timestamp no fuso horário especificado.

O PostgreSQL usa fusos horários ISO-8601, que especificam que leste de Greenwich é positivo... a menos que você use um especificador de fuso horário POSIX, caso em que segue POSIX. Segue-se a insanidade.

Por que o primeiro produz um resultado inesperado


Timestamps e fusos horários no SQL são horríveis. Esse:
select '2011-12-30 00:30:00'::timestamp without time zone AT TIME ZONE 'EST5EDT';

interpreta o literal de tipo desconhecido '2011-12-30 00:30:00' como timestamp without time zone , que Pg assume que está no fuso horário local, salvo indicação em contrário. Quando você usa AT TIME ZONE , é (de acordo com a especificação) reinterpretado como um timestamp with time zone no fuso horário EST5EDT então armazenado como um tempo absoluto em UTC - então é convertido de EST5EDT para UTC, ou seja, o deslocamento do fuso horário é subtraído . x - (-5) é x + 5 .

Este carimbo de data/hora, ajustado para armazenamento UTC, é então ajustado para o seu servidor TimeZone configuração para exibição para que seja exibida na hora local.

Se, em vez disso, você deseja dizer "Eu tenho este carimbo de data/hora na hora UTC e deseja ver qual é a hora local equivalente em EST5EDT", se você quiser ser independente da configuração do fuso horário do servidor, precisará escrever algo como:
select TIMESTAMP '2011-12-30 00:30:00' AT TIME ZONE 'UTC'
       AT TIME ZONE 'EST5EDT';

Isso diz "Dado timestamp 2011-12-30 00:30:00, trate-o como um timestamp em UTC ao converter para timestamptz, então converta esse timestamptz para um horário local em EST5EDT".

Horrível, não é? Quero dar uma conversa firme quem decidiu sobre a semântica maluca de AT TIME ZONE - deve ser algo como timestamp CONVERT FROM TIME ZONE '-5' e timestamptz CONVERT TO TIME ZONE '+5' . Além disso, timestamp with time zone deve realmente levar seu fuso horário com ele, não ser armazenado em UTC e convertido automaticamente para hora local.

Por que o segundo funciona (desde que TimeZone =UTC)


Sua versão original de "trabalhos":
select '2011-12-30 00:30:00' AT TIME ZONE 'EST5EDT';

só estará correto se TimeZone estiver definido como UTC, porque a conversão text-to-timestamptz assume TimeZone quando um não é especificado.

Por que o terceiro funciona


Dois problemas se cancelam.

A outra versão que parece funcionar é independente do TimeZone, mas só funciona porque dois problemas se anulam. Primeiro, como explicado acima, timestamp without time zone AT TIME ZONE reinterpreta o carimbo de data/hora como estando nesse fuso horário para conversão em um carimbo de data/hora UTC; isso efetivamente subtrai o deslocamento do fuso horário.

No entanto, por motivos que eu não sei, o PostgreSQL usa carimbos de data/hora com o sinal inverso ao que estou acostumado a ver na maioria dos lugares. Veja a documentação:

Outra questão a ter em mente é que em nomes de fuso horário POSIX, deslocamentos positivos são usados ​​para locais a oeste de Greenwich. Em todos os outros lugares, o PostgreSQL segue a convenção ISO-8601 de que os deslocamentos de fuso horário positivos estão a leste de Greenwich.

Isso significa que EST5EDT é igual a +5 , não -5 . É por isso que funciona:porque você está subtraindo o deslocamento tz, não o adiciona, mas está subtraindo um deslocamento negado!

O que você precisa para acertar é em vez disso:
select TIMESTAMP '2011-12-30 00:30:00' AT TIME ZONE 'UTC'
       AT TIME ZONE '+5';