sqlpostgresqltimezonetimestamptz

AT TIME ZONE no SQL: converter UTC armazenado para o horario local do usuario

O duplo comportamento do AT TIME ZONE: em timestamptz retorna o horario de parede local, em timestamp naive interpreta o valor naquela zona e retorna timestamptz.

2 min de leituraReferencesql · postgresql · timezone · timestamptz · timestamp · clickhouse

AT TIME ZONE e uma ferramenta com duas faces. O que ela faz depende inteiramente do tipo da entrada: para um timestamptz ela representa o momento como o horario de parede na zona escolhida, ja para um timestamp naive faz o oposto, declara que o valor ja estava naquela zona e devolve o instante absoluto.

Dois modos, um operador

A regra e curta: AT TIME ZONE sempre inverte o tipo para o oposto.

  • timestamptz AT TIME ZONE 'zona' -> timestamp (horario de parede naquela zona).
  • timestamp AT TIME ZONE 'zona' -> timestamptz (interpreta o valor naive como hora local daquela zona).
-- timestamptz -> timestamp: what the clock on the wall in Moscow shows
SELECT TIMESTAMPTZ '2026-06-17 12:00:00+00' AT TIME ZONE 'Europe/Moscow';
-- 2026-06-17 15:00:00

-- timestamp -> timestamptz: this naive value WAS Moscow local time
SELECT TIMESTAMP '2026-06-17 15:00:00' AT TIME ZONE 'Europe/Moscow';
-- 2026-06-17 12:00:00+00

As duas expressoes sao espelho uma da outra e fazem ida e volta sem perda. A primeira responde "que horas sao em Moscou neste momento"; a segunda responde "a qual instante absoluto correspondem as 15:00 de Moscou".

Convertendo UTC armazenado para a zona do usuario

O padrao canonico: voce armazena created_at como timestamptz (que internamente e UTC) e quer exibi-lo na zona de um usuario especifico.

SELECT
  o.id,
  o.amount,
  o.created_at,                                          -- absolute moment (UTC)
  o.created_at AT TIME ZONE u.tz AS local_created_at     -- wall time for the user
FROM orders o
JOIN users u ON u.id = o.user_id;

Aqui u.tz e uma coluna de texto com um nome de zona como 'America/Sao_Paulo'. Use sempre zonas IANA nomeadas, nunca deslocamentos fixos como '+03': um nome de zona conhece todo o historico de transicoes de horario de verao, um deslocamento cru nao.

Agrupar pedidos pelo "dia local" do usuario e igualmente direto, primeiro converter, depois truncar:

SELECT
  DATE_TRUNC('day', o.created_at AT TIME ZONE u.tz) AS local_day,
  SUM(o.amount) AS revenue
FROM orders o
JOIN users u ON u.id = o.user_id
WHERE o.status = 'paid'
GROUP BY 1
ORDER BY 1;

Horario de verao, onde realmente morde

A principal razao para nao somar deslocamentos na mao e o DST. Pegue Nova York em torno da transicao de primavera de 2026, quando os relogios pulam das 02:00 direto para as 03:00.

SELECT TIMESTAMPTZ '2026-03-08 06:30:00+00' AT TIME ZONE 'America/New_York' AS before_dst,
       TIMESTAMPTZ '2026-03-08 07:30:00+00' AT TIME ZONE 'America/New_York' AS after_dst;
-- before_dst = 2026-03-08 01:30:00   (offset -05)
-- after_dst  = 2026-03-08 03:30:00   (offset -04, the 02:00 hour never exists)

Passou exatamente uma hora de tempo absoluto entre os dois momentos, mas o relogio de parede pulou de 01:30 para 03:30, a hora 02:xx simplesmente nao existe naquela noite. Um deslocamento fixo de -05 daria a resposta errada na segunda linha.

Pegadinhas

  • Pegadinha: confusao de modos. Aplicar AT TIME ZONE a um valor que ja e uma coluna timestamp naive nao o "converte", declara que ele estava naquela zona. Verifique primeiro o tipo da coluna.
  • Aplicar duas vezes faz ida e volta. (ts AT TIME ZONE 'Europe/Moscow') da um timestamp naive; aplique AT TIME ZONE de novo e voce recupera um timestamptz, as vezes util para "remudar de zona", mas com mais frequencia um bug.
  • Use nomes IANA. Zonas como 'Europe/Moscow' sobrevivem a mudancas nas regras de DST; deslocamentos como '+03' nao.

MySQL e ClickHouse

Nenhum dos dois motores tem a sintaxe AT TIME ZONE. No MySQL use CONVERT_TZ(ts, 'UTC', 'Europe/Moscow') (as tabelas de zonas nomeadas precisam estar carregadas). No ClickHouse use toTimeZone(ts, 'Europe/Moscow') para um DateTime, lembrando que ali a zona e apenas um atributo de exibicao sobre um timestamp baseado em UTC.

Pratique com exercícios reais

Resolva exercícios no treinador de SQL com correção instantânea e dicas.

Abrir o treinador