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';