Diferença entre registros de data e hora com / sem fuso horário no PostgreSQL

186

Os valores de carimbo de data e hora são armazenados de maneira diferente no PostgreSQL quando o tipo de dados é WITH TIME ZONEversus WITHOUT TIME ZONE? As diferenças podem ser ilustradas com casos de teste simples?

Larsenal
fonte
3
Esta resposta relacionada pode ser útil.
Erwin Brandstetter

Respostas:

157

As diferenças são abordadas na documentação do PostgreSQL para tipos de data / hora . Sim, o tratamento TIMEou TIMESTAMPdifere entre um WITH TIME ZONEou WITHOUT TIME ZONE. Não afeta como os valores são armazenados; afeta como eles são interpretados.

Os efeitos dos fusos horários nesses tipos de dados são abordados especificamente nos documentos. A diferença surge do que o sistema pode razoavelmente saber sobre o valor:

  • Com um fuso horário como parte do valor, o valor pode ser renderizado como um horário local no cliente.

  • Sem um fuso horário como parte do valor, o fuso horário padrão óbvio é UTC e, portanto, é renderizado para esse fuso horário.

O comportamento difere dependendo de pelo menos três fatores:

  • A configuração de fuso horário no cliente.
  • O tipo de dados ( WITH TIME ZONEou seja, WITHOUT TIME ZONE) do valor.
  • Se o valor está especificado com um fuso horário específico.

Aqui estão exemplos que cobrem as combinações desses fatores:

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
fonte
88
Corrija apenas se estiver se referindo ao processo de inserção / recuperação de valores. Mas os leitores devem entender que ambos os tipos de dados timestamp with time zonee timestamp without time zone, no Postgres *, na verdade não armazenam informações de fuso horário. Você pode confirmar isso com uma olhada na página de documentos do tipo de dados: os dois tipos ocupam o mesmo número de octetos e têm o intervalo de valores salvo, portanto, não há espaço para armazenar informações de fuso horário. O texto da página confirma isso. Algo equívoco: "sem tz" significa "ignorar deslocamento ao inserir dados" e "com tz" significa "usar deslocamento para ajustar ao UTC".
Basil Bourque 15/02
42
Os tipos de dados são inadequados de uma segunda maneira: eles dizem "fuso horário", mas na verdade estamos falando de deslocamento do UTC / GMT. Um fuso horário é, na verdade, um deslocamento mais regras / histórico sobre horário de verão (DST) e outras anomalias.
Basil Bourque 15/02
4
Prefiro dizer que um deslocamento é um fuso horário mais regras para o horário de verão. Você não pode descobrir o fuso horário com um deslocamento, mas pode descobrir o deslocamento com o fuso horário e as regras de horário de verão.
Igorantos07
3
Citando o documento oficial : todas as datas e horas com reconhecimento de fuso horário são armazenadas internamente no UTC. Eles são convertidos para a hora local na zona especificada pelo parâmetro de configuração TimeZone antes de serem exibidos para o cliente.
Guillaume Husta
2
@ igorsantos07 Um fuso horário é o conjunto de regras / histórico sobre alterações no horário de verão e outras alterações. Sua redação parece supérflua. E sua afirmação de que "um deslocamento é um fuso horário mais regras para o horário de verão" está simplesmente errada: um deslocamento é apenas um número de horas, minutos e segundos - nada mais, nada menos.
Basil Bourque
34

Tento explicá-lo de maneira mais compreensível do que a documentação do PostgreSQL.

Nenhuma das TIMESTAMPvariantes armazena um fuso horário (ou um deslocamento), apesar do que os nomes sugerem. A diferença está na interpretação dos dados armazenados (e no aplicativo pretendido), não no próprio formato de armazenamento:

  • TIMESTAMP WITHOUT TIME ZONEarmazena a data e hora local (também conhecida como data do calendário da parede e hora do relógio da parede). Seu fuso horário não é especificado até onde o PostgreSQL pode dizer (embora seu aplicativo possa saber o que é). Portanto, o PostgreSQL não realiza nenhuma conversão relacionada ao fuso horário na entrada ou na saída. Se o valor foi inserido no banco de dados como '2011-07-01 06:30:30', então não importa em que fuso horário você o exibirá mais tarde, ainda será o ano de 2011, mês 07, dia 01, 06 horas, 30 minutos e 30 segundos (em algum formato). Além disso, qualquer deslocamento ou fuso horário que você especificar na entrada é ignorado pelo PostgreSQL, então '2011-07-01 06:30:30+00'e '2011-07-01 06:30:30+05'são os mesmos apenas '2011-07-01 06:30:30'. Para desenvolvedores Java: é análogo a java.time.LocalDateTime.

  • TIMESTAMP WITH TIME ZONEarmazena um ponto na linha do tempo UTC. Sua aparência (quantas horas, minutos etc.) depende do seu fuso horário, mas sempre se refere ao mesmo instante "físico" (como o momento de um evento físico real). A entrada é convertida internamente em UTC, e é assim que é armazenada. Para isso, o deslocamento da entrada deve ser conhecido; portanto, quando a entrada não contém deslocamento explícito ou fuso horário (como '2011-07-01 06:30:30'), presume-se que esteja no fuso horário atual da sessão do PostgreSQL, caso contrário, o deslocamento ou fuso horário especificado explicitamente será usado (como em '2011-07-01 06:30:30+05'). A saída é exibida convertida no fuso horário atual da sessão do PostgreSQL. Para desenvolvedores Java: É análogo a java.time.Instant(com menor resolução), mas com JDBC e JPA 2.2 você deve mapeá-lo para java.time.OffsetDateTime(ou para java.util.Dateoujava.sql.Timestamp claro).

Alguns dizem que ambas as TIMESTAMPvariações armazenam a data e hora UTC. Mais ou menos, mas é confuso colocar dessa maneira na minha opinião. TIMESTAMP WITHOUT TIME ZONEé armazenado como a TIMESTAMP WITH TIME ZONE, que é renderizado com o fuso horário UTC e fornece o mesmo ano, mês, dia, horas, minutos, segundos e microssegundos que estão na data e hora local. Mas não é para representar o ponto na linha do tempo que a interpretação UTC diz, é apenas o modo como os campos locais de data e hora são codificados. (Há alguns aglomerados de pontos na linha do tempo, pois o fuso horário real não é UTC; não sabemos o que é.)

ddekany
fonte
Não há nada de errado em recuperar a TIMESTAMP WITH TIME ZONEcomo a Instant. Ambos representam um ponto na linha do tempo no UTC. Instanté preferível, na minha opinião, ao OffsetDateTimeinvés de ser mais auto-documentado: A TIMESTAMP WITH TIME ZONEsempre é recuperado do banco de dados como UTC, e Instantsempre está no UTC para uma correspondência natural, enquanto um OffsetDateTimepode levar outras compensações.
Basil Bourque
@BasilBourque Infelizmente, a especificação JDBC atual, a especificação JPA 2.2 e também a documentação JDBC do PostgreSQL mencionam apenas OffsetDateTimeo tipo Java mapeado. Não sei se Instanceainda há suporte não oficial em algum lugar.
ddekany
pergunta, você diz que qualquer deslocamento que eu especificar na entrada como '2011-07-01 06:30:30+00'e '2011-07-01 06:30:30+05' é ignorado, mas eu sou capaz de fazer insert into test_table (date) values ('2018-03-24T00:00:00-05:00'::timestamptz);e ele o converterá em utc corretamente. em que data é data e hora sem fuso horário. Estou tentando entender qual é o principal valor do carimbo de data / hora com o fuso horário e está tendo problemas.
Pk1m
@ pk1m Você complica os problemas com o ::timestamptz. Com isso, você converte a string para TIMESTAMP WITH TIME ZONE, e quando isso será convertido para WITHOUT TIME ZONE, que armazenará o dia do "calendário de parede" e a hora do relógio de parede daquele instante, conforme visto no fuso horário da sessão (que talvez seja UTC). Ainda será apenas um registro de data e hora local com deslocamento não especificado (sem zona).
ddekany
Eu estou trabalhando com python, e é isso que é inserido quando inserir um objeto de data e hora com reconhecimento de data e hora. Parece-me que há valor em usar o carimbo de data / hora com fuso horário, mas não é necessário lidar com fusos horários.
pk1m
12

Aqui está um exemplo que deve ajudar. Se você tiver um carimbo de data / hora com um fuso horário, poderá convertê-lo em qualquer outro fuso horário. Se você não tiver um fuso horário base, ele não será convertido corretamente.

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

Resultado:

-[ 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
fonte
5
A afirmação "não será convertida corretamente" simplesmente não é verdadeira. Você tem que entender o que timestampe timestamptzdizer. timestamptzsignifica um ponto absoluto no tempo (UTC), enquanto timestampdenota o que o relógio mostrou em um determinado fuso horário. Assim, ao converter timestamptzpara um fuso horário, você está perguntando o que o relógio mostrou em Nova York nesse ponto absoluto no tempo? enquanto que ao "converter" a timestamp, você está perguntando qual era o ponto absoluto no tempo em que o relógio em Nova York mostrava x?
Fphilipe 27/03
A AT TIME ZONEconstrução é um quebra-cabeças próprio, mesmo que você já entenda os tipos WITHvs. WITHOUT TIME ZONEPortanto, é uma escolha curiosa para explicá-los. (: ( AT TIME ZONEConverte um WITH TIME ZONEtimestamp de um WITHOUT TIME ZONEtimestamp, e vice-versa ... não exatamente óbvio).
ddekany
now()::timestamp AT TIME ZONE 'CST'Não faz sentido, a menos que o que se em qualquer tempo um relógio para a zona 'CST' iria mostrar o tempo que seu relógio local está mostrando
Jasen