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

Qual tipo de timestamp devo escolher em um banco de dados PostgreSQL?


Em primeiro lugar, o tratamento de tempo e aritmética do PostgreSQL são fantásticos e a Opção 3 é boa no caso geral. É, no entanto, uma visão incompleta de tempo e fusos horários e pode ser complementada:
  1. Armazenar o nome do fuso horário de um usuário como preferência do usuário (por exemplo, America/Los_Angeles , não -0700 ).
  2. Ter dados de eventos/hora do usuário enviados localmente para seu quadro de referência (provavelmente um deslocamento do UTC, como -0700 ).
  3. No aplicativo, converta a hora para UTC e armazenado usando um TIMESTAMP WITH TIME ZONE coluna.
  4. Retornar solicitações de horário locais para o fuso horário de um usuário (ou seja, converter de UTC para America/Los_Angeles ).
  5. Defina o timezone do seu banco de dados para UTC .

Essa opção nem sempre funciona porque pode ser difícil obter o fuso horário de um usuário e, portanto, o conselho de hedge para usar TIMESTAMP WITH TIME ZONE para aplicações leves. Dito isto, deixe-me explicar alguns aspectos de fundo desta Opção 4 com mais detalhes.

Como a Opção 3, o motivo do WITH TIME ZONE é porque o momento em que algo aconteceu é um absoluto momento no tempo. WITHOUT TIME ZONE produz um parente fuso horário. Nunca, nunca, nunca misture TIMESTAMPs absolutos e relativos.

De uma perspectiva programática e de consistência, certifique-se de que todos os cálculos sejam feitos usando UTC como fuso horário. Este não é um requisito do PostgreSQL, mas ajuda na integração com outras linguagens ou ambientes de programação. Configurando um CHECK na coluna para garantir que a gravação na coluna de carimbo de hora tenha um deslocamento de fuso horário de 0 é uma posição defensiva que evita algumas classes de bugs (por exemplo, um script despeja dados em um arquivo e outra coisa classifica os dados de tempo usando uma classificação léxica). Novamente, o PostgreSQL não precisa disso para fazer cálculos de data corretamente ou converter entre fusos horários (ou seja, o PostgreSQL é muito hábil em converter horários entre dois fusos horários arbitrários). Para garantir que os dados que entram no banco de dados sejam armazenados com um deslocamento de zero:
CREATE TABLE my_tbl (
  my_timestamp TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(),
  CHECK(EXTRACT(TIMEZONE FROM my_timestamp) = '0')
);
test=> SET timezone = 'America/Los_Angeles';
SET
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW());
ERROR:  new row for relation "my_tbl" violates check constraint "my_tbl_my_timestamp_check"
test=> SET timezone = 'UTC';
SET
test=> INSERT INTO my_tbl (my_timestamp) VALUES (NOW());
INSERT 0 1

Não é 100% perfeito, mas fornece uma medida anti-footshooting forte o suficiente para garantir que os dados já sejam convertidos para UTC. Existem muitas opiniões sobre como fazer isso, mas isso parece ser o melhor na prática da minha experiência.

Críticas ao tratamento de fuso horário do banco de dados são amplamente justificadas (há muitos bancos de dados que lidam com isso com grande incompetência), no entanto, o tratamento de timestamps e fusos horários do PostgreSQL é bastante impressionante (apesar de alguns "recursos" aqui e ali). Por exemplo, um desses recursos:
-- Make sure we're all working off of the same local time zone
test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT NOW();
              now              
-------------------------------
 2011-05-27 15:47:58.138995-07
(1 row)

test=> SELECT NOW() AT TIME ZONE 'UTC';
          timezone          
----------------------------
 2011-05-27 22:48:02.235541
(1 row)

Observe que AT TIME ZONE 'UTC' remove as informações do fuso horário e cria um TIMESTAMP WITHOUT TIME ZONE relativo usando o quadro de referência do seu destino (UTC ).

Ao converter de um TIMESTAMP WITHOUT TIME ZONE incompleto para um TIMESTAMP WITH TIME ZONE , o fuso horário ausente é herdado da sua conexão:
test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW());
 date_part 
-----------
        -7
(1 row)
test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541');
 date_part 
-----------
        -7
(1 row)

-- Now change to UTC    
test=> SET timezone = 'UTC';
SET
-- Create an absolute time with timezone offset:
test=> SELECT NOW();
              now              
-------------------------------
 2011-05-27 22:48:40.540119+00
(1 row)

-- Creates a relative time in a given frame of reference (i.e. no offset)
test=> SELECT NOW() AT TIME ZONE 'UTC';
          timezone          
----------------------------
 2011-05-27 22:48:49.444446
(1 row)

test=> SELECT EXTRACT(TIMEZONE_HOUR FROM NOW());
 date_part 
-----------
         0
(1 row)

test=> SELECT EXTRACT(TIMEZONE_HOUR FROM TIMESTAMP WITH TIME ZONE '2011-05-27 22:48:02.235541');
 date_part 
-----------
         0
(1 row)

A linha inferior:
  • armazenar o fuso horário de um usuário como um rótulo nomeado (por exemplo, America/Los_Angeles ) e não um deslocamento do UTC (por exemplo, -0700 )
  • use UTC para tudo, a menos que haja um motivo convincente para armazenar um deslocamento diferente de zero
  • trate todos os horários UTC diferentes de zero como um erro de entrada
  • nunca misture e combine carimbos de data/hora relativos e absolutos
  • também use UTC como o timezone no banco de dados, se possível

Nota de linguagem de programação aleatória:datetime do Python tipo de dados é muito bom em manter a distinção entre tempos absolutos e relativos (embora frustrante no início até que você o complemente com uma biblioteca como o PyTZ).

EDITAR

Deixe-me explicar um pouco mais a diferença entre relativo e absoluto.

O tempo absoluto é usado para registrar um evento. Exemplos:"Usuário 123 logado" ou "cerimônias de formatura começam em 28/05/2011 às 14h PST". Independentemente do seu fuso horário local, se você pudesse se teletransportar para onde o evento ocorreu, poderia testemunhar o evento acontecendo. A maioria dos dados de tempo em um banco de dados é absoluto (e, portanto, deve ser TIMESTAMP WITH TIME ZONE , de preferência com um deslocamento +0 e um rótulo textual representando as regras que regem o fuso horário específico - não um deslocamento).

Um evento relativo seria registrar ou agendar a hora de algo a partir da perspectiva de um fuso horário ainda a ser determinado. Exemplos:"as portas da nossa empresa abrem às 8h e fecham às 21h", "vamos nos encontrar todas as segundas-feiras às 7h para uma reunião semanal de café da manhã" ou "todo Halloween às 20h". Em geral, o tempo relativo é usado em um modelo ou fábrica para eventos, e o tempo absoluto é usado para quase todo o resto. Há uma rara exceção que vale a pena apontar que deve ilustrar o valor dos tempos relativos. Para eventos futuros que estão longe o suficiente no futuro, onde pode haver incerteza sobre o tempo absoluto em que algo pode ocorrer, use um registro de data e hora relativo. Aqui está um exemplo do mundo real:

Suponha que seja o ano de 2004 e você precise agendar uma entrega em 31 de outubro de 2008 às 13h na costa oeste dos EUA (ou seja, America/Los_Angeles /PST8PDT ). Se você armazenou isso usando o tempo absoluto usando ’2008-10-31 21:00:00.000000+00’::TIMESTAMP WITH TIME ZONE , a entrega teria aparecido às 14h porque o governo dos EUA aprovou a Lei de Política Energética de 2005 que mudou as regras que regem o horário de verão. Em 2004, quando a entrega foi agendada, a data 10-31-2008 seria o horário padrão do Pacífico (+8000 ), mas a partir de 2005+ os bancos de dados de fuso horário reconheceram que 10-31-2008 seria o horário de verão do Pacífico (+0700 ). Armazenar um carimbo de data/hora relativo com o fuso horário resultaria em uma programação de entrega correta porque um carimbo de data/hora relativo é imune a adulterações mal informadas do Congresso. Onde o ponto de corte entre o uso de tempos relativos vs absolutos para agendar coisas é uma linha difusa, mas minha regra geral é que o agendamento para qualquer coisa no futuro além de 3-6 meses deve fazer uso de timestamps relativos (agendado =absoluto vs planejado =relativo ???).

O outro/último tipo de tempo relativo é o INTERVAL . Exemplo:"a sessão expirará 20 minutos após o login do usuário". Um INTERVAL pode ser usado correctamente com carimbos de hora absolutos (TIMESTAMP WITH TIME ZONE ) ou carimbos de data/hora relativos (TIMESTAMP WITHOUT TIME ZONE ). É igualmente correto dizer que "uma sessão de usuário expira 20 minutos após um login bem-sucedido (login_utc + session_duration)" ou "nossa reunião matinal de café da manhã pode durar apenas 60 minutos (recurring_start_time + meeting_length)".

Últimos pedaços de confusão:DATE , TIME , TIME WITHOUT TIME ZONE e TIME WITH TIME ZONE são todos tipos de dados relativos. Por exemplo:'2011-05-28'::DATE representa uma data relativa, pois você não tem informações de fuso horário que possam ser usadas para identificar a meia-noite. Da mesma forma, '23:23:59'::TIME é relativo porque você não sabe o fuso horário nem o DATE representado pelo tempo. Mesmo com '23:59:59-07'::TIME WITH TIME ZONE , você não sabe qual é a DATE seria. E por último, DATE com um fuso horário não é de fato um DATE , é um TIMESTAMP WITH TIME ZONE :
test=> SET timezone = 'America/Los_Angeles';
SET
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC';
      timezone       
---------------------
 2011-05-11 07:00:00
(1 row)

test=> SET timezone = 'UTC';
SET
test=> SELECT '2011-05-11'::DATE AT TIME ZONE 'UTC';
      timezone       
---------------------
 2011-05-11 00:00:00
(1 row)

Colocar datas e fusos horários em bancos de dados é uma coisa boa, mas é fácil obter resultados sutilmente incorretos. É necessário um esforço adicional mínimo para armazenar informações de tempo correta e completamente, mas isso não significa que o esforço extra seja sempre necessário.