PostgreSQL timestamp vs timestamptz: o que é armazenado realmente por baixo dos panos?
O PostgreSQL mantém tanto timestamp quanto timestamptz como um único inteiro de 64 bits: o número de microssegundos desde 1970-01-01 00:00:00 UTC. A distinção emerge unicamente durante a formatação dos dados para consumo humano.
Por que confunde as pessoas?
- Duas colunas, uma data… dois resultados de consulta diferentes
- Sua app insere
2025-07-29 10:00, mas outra equipe vê02:00 - O frontend renderiza uma string ISO que não coincide com o log do backend
Duas latas de pêssego: uma simples, uma etiquetada
| Tipo de dado | Nome oficial | Valor armazenado | O que acontece no SELECT |
|---|---|---|---|
timestamp | timestamp sem fuso horário | contagem de microssegundos bruta | É retornado sem alteração — o Postgres nunca adivinha um fuso horário |
timestamptz | timestamp com fuso horário | mesma contagem de microssegundos | O Postgres aplica a configuração TimeZone da sessão logo antes de enviar o texto |
Analogia
timestamp= uma lata de pêssegos sem etiqueta de origem. Você sabe que é fruta, mas não sabe onde foi enlatada.timestamptz= uma lata orgulhosamente carimbada “Feito em UTC+8.” Quem abri-la pode decidir se converte o painel nutricional.
Por baixo dos panos: é apenas um número enorme
2000-01-01 00:00:00 UTC → 0
2000-01-01 00:00:01 UTC → 1 000 000
- Unidade: microssegundos (um milionésimo de segundo)
- Faixa: 4713 a.C. – 294276 d.C. — aprovado pelo Indiana Jones
- O armazenamento para
timestampetimestamptzé idêntico; a interpretação difere
Uma demonstração de 15 segundos
-- O cliente pensa em horário de Xangai
SET TimeZone = 'Asia/Shanghai';
CREATE TABLE demo (
created_ts timestamp,
created_tz timestamptz
);
INSERT INTO demo VALUES ('2025-07-29 10:00', '2025-07-29 10:00');
| Consulta | Resultado | Por quê |
|---|---|---|
SELECT created_ts FROM demo; | 2025-07-29 10:00:00 | Valor bruto, sem cálculo de fuso horário |
SELECT created_tz FROM demo; | 2025-07-29 10:00:00+08 | Etiqueta aplicada na saída |
SET TimeZone = 'UTC'; e depois selecionar | 2025-07-29 02:00:00+00 | O mesmo instante, nova perspectiva |
Aritmética de timestamps e intervalos
Um dos aspectos mais práticos dos timestamps do PostgreSQL é a aritmética de intervalos. Como ambos os tipos armazenam contagens de microssegundos, você pode adicionar e subtrair intervalos diretamente:
-- Adicionar 3 horas e 30 minutos
SELECT '2025-07-29 10:00'::timestamptz + INTERVAL '3 hours 30 minutes';
-- → 2025-07-29 13:30:00+08
-- Encontrar a diferença entre dois timestamps
SELECT '2025-07-30 09:00'::timestamptz - '2025-07-29 10:00'::timestamptz;
-- → 23:00:00 (um intervalo)
-- Extrair campos específicos
SELECT EXTRACT(EPOCH FROM '2025-07-29 10:00:00+08'::timestamptz);
-- → 1753768800 (timestamp Unix em segundos)
-- Truncar ao limite do dia (útil para agregações diárias)
SELECT date_trunc('day', '2025-07-29 15:42:19+08'::timestamptz);
-- → 2025-07-29 00:00:00+08
A função EXTRACT(EPOCH FROM ...) é particularmente útil quando você precisa passar timestamps para sistemas externos que esperam segundos de época Unix. Por outro lado, você pode converter uma época de volta para timestamp:
SELECT to_timestamp(1753768800);
-- → 2025-07-29 10:00:00+08 (em sessão Asia/Shanghai)
Um ponto sutil, mas importante: a aritmética de intervalos com timestamp (sem fuso horário) ignora completamente as transições de horário de verão, enquanto timestamptz as respeita. Isso significa que adicionar INTERVAL '1 day' a um valor timestamptz que cruza um limite de mudança de horário retornará corretamente o mesmo horário no relógio de parede — não exatamente 24 horas depois.
Considerações de indexação e desempenho
Tanto timestamp quanto timestamptz são armazenados como inteiros de 8 bytes, portanto não há diferença de desempenho entre eles para armazenamento ou indexação. Os índices B-tree funcionam de forma idêntica em ambos os tipos porque a comparação subjacente é simplesmente uma comparação de inteiros.
No entanto, há algumas considerações práticas:
- Consultas de faixa:
WHERE created_at > '2025-07-01'funciona eficientemente com um índice em qualquer um dos tipos. Comtimestamptz, o PostgreSQL converte o literal para UTC antes da comparação, então o índice continua sendo usado. - Chaves de partição: Quando se usa particionamento por faixa em colunas timestamp,
timestamptzé geralmente mais seguro porque os limites de partição são inequívocos (sempre UTC). Comtimestamp, um limite como'2025-07-01 00:00'poderia significar coisas diferentes para sessões diferentes. - Índices funcionais: Se frequentemente consulta apenas por data (ignorando a hora), considere um índice em
date_trunc('day', created_at)para acelerar as consultas de agregação diária.
Problemas comuns e soluções rápidas
1. Diferentes usuários, diferentes relógios
- Causa: os clientes usam diferentes configurações
TimeZonecomtimestamptz - Solução: mantenha tudo em
timestamp+ combine um fuso, ou forceSET TimeZone = 'UTC'na inicialização da conexão
Um padrão comum no código de aplicação é definir o fuso horário uma vez na inicialização do pool de conexões:
-- Na sua configuração de conexão (ex., configuração do pool de pg)
SET timezone = 'UTC';
Isso garante que todas as sessões vejam a mesma representação UTC, e a camada de aplicação gerencia a conversão para hora local na exibição.
2. Armazenar “hora do relógio” mas escolher o tipo errado
- Os calendários de negócio (horários de loja, prazos) devem usar
timestamp - Os fluxos de trabalho transfronteiriços (pedidos, logs) devem armazenar UTC em
timestamptz
O teste é simples: se a pergunta é “em que momento isso aconteceu?” use timestamptz. Se a pergunta é “o que o relógio de parede mostra?” use timestamp.
3. APIs que se desviam
- Sempre envie
timestamptzcomo strings ISO-8601 com o deslocamento (Zou+08:00) - Deixe a interface do usuário formatar localmente
4. Comparar timestamps entre tipos
Misturar timestamp e timestamptz em comparações ou joins é uma fonte comum de bugs sutis:
-- Perigoso: o cast implícito aplica o fuso horário da sessão
SELECT * FROM orders o
JOIN schedules s ON o.created_tz = s.start_ts;
-- O PostgreSQL converte s.start_ts para timestamptz usando o fuso horário da sessão
-- Sessões diferentes podem obter resultados de join diferentes!
Solução: sempre converta explicitamente quando comparar entre tipos, ou padronize em um tipo por domínio.
5. Problemas padrão dos ORMs
Muitos ORMs (Django, SQLAlchemy, ActiveRecord) usam por padrão timestamp sem fuso horário. Revise seus arquivos de migração — se sua app atende usuários em múltiplos fusos horários, mude o padrão para timestamptz. No Django, defina USE_TZ = True nas configurações. No SQLAlchemy, use DateTime(timezone=True).
Tabela de referência rápida: qual devo usar?
Apenas calendário local → timestamp
Qualquer coisa global → timestamptz (armazenar em UTC)
- Relatórios financeiros, horários de aulas →
timestamp - Registros de auditoria, pedidos de e-commerce →
timestamptz
Verifique em segundos com o Go Tools
| Necessidade | Ferramenta | Como fazer |
|---|---|---|
| Inspecionar o valor de época do SQL | Conversor de Época | Cole 1690622400, clique em Converter |
| Comparar dois fusos horários rapidamente | Conversor de Fuso Horário | Insira 10:00 Asia/Shanghai |
| Ordenar JSON em massa com campos de tempo | Formatador JSON | Solte o payload, formate e examine |
Todas as utilidades rodam completamente no seu navegador — os dados nunca saem da sua máquina.
Perguntas frequentes
Qual é a diferença entre timestamp e timestamptz no PostgreSQL?
timestamp (sem fuso horário) armazena um valor de data e hora como está, sem contexto de fuso horário. timestamptz (com fuso horário) converte a entrada para UTC ao armazenar e converte de volta para o fuso horário da sessão na recuperação. Use timestamptz em quase todos os casos — previne os erros relacionados a fusos horários em sistemas distribuídos.
O PostgreSQL realmente armazena o fuso horário no timestamptz?
Não — apesar do nome, o PostgreSQL não armazena o fuso horário em si. Converte a entrada para UTC e armazena unicamente o valor UTC (uma contagem de microssegundos desde 2000-01-01). Na recuperação, converte de UTC para o fuso horário especificado pela configuração timezone da sua sessão. A informação do fuso horário original é descartada.
Como mudo o fuso horário de uma sessão do PostgreSQL?
Execute SET timezone = 'America/New_York'; para mudar o fuso horário da sessão. Isso afeta como os valores timestamptz são exibidos e interpretados. Para padrões em nível de servidor, defina timezone no postgresql.conf. Sempre use nomes de fuso horário IANA (como Asia/Shanghai) em vez de abreviações (como CST) para evitar ambiguidades.
Devo usar timestamp ou timestamptz para armazenar tempos de eventos?
Use timestamptz para praticamente tudo — ações de usuário, chamadas de API, registros de auditoria e eventos agendados. Só use timestamp (sem fuso horário) para tempos abstratos que não estão ligados a um momento específico, como “a loja abre às 09:00” que significa 9 da manhã no fuso horário local, não um instante UTC específico.
Como o PostgreSQL lida com o horário de verão com timestamptz?
O PostgreSQL lida corretamente com o horário de verão ao usar timestamptz porque armazena tudo em UTC internamente. Quando você recupera um valor, o PostgreSQL converte de UTC usando as regras de horário de verão atuais para o fuso horário da sua sessão. Isso significa que o mesmo instante UTC armazenado mostra corretamente diferentes horas locais antes e depois de uma transição de horário de verão.
Para um guia completo sobre timestamps Unix — incluindo o tratamento de precisão, as melhores práticas de fuso horário e exemplos de código em JavaScript, Python e Go — consulte nosso Guia de Timestamp Unix.
Resumo
- Ambos os tipos de tempo do Postgres são contadores de microssegundos; a etiqueta é toda a diferença
- Escolher o errado significa timestamps confusos e cálculos quebrados
- Teste, converta e verifique com as ferramentas certas para economizar horas de depuração