Timezone: o bug que dorme no seu código esperando a hora errada

Tem vídeo desse conteúdo no canal Veja o passo a passo completo em vídeo.
Assistir no YouTube →
Timezone: o bug que dorme no seu código esperando a hora errada

Poucas coisas na programação parecem tão inofensivas quanto uma data. É só um dia, um mês, um ano. Até que chega o horário de verão, um usuário no Acre, um servidor em outro continente e um relatório mensal que insiste em fechar um dia errado. Aí você descobre que tempo é, provavelmente, um dos assuntos mais escorregadios que existem.

A boa notícia é que quase toda dor de fuso horário vem de um punhado de erros repetidos. Entender esses erros já te coloca à frente da maioria.

O pecado original: guardar hora local

O erro mais comum é salvar no banco a hora “que apareceu na tela” do usuário. Alguém no Brasil cria um registro às 15h, e você guarda 15h. Um mês depois, um usuário em Portugal olha o mesmo dado e vê 15h também — só que, para ele, o evento aconteceu às 19h. Qual das duas é a verdade? Nenhuma, porque você jogou fora a informação que amarrava aquele número a um ponto no tempo.

A regra que resolve 90% dos problemas é quase um mantra:

Guarde tudo em UTC. Converta para o fuso do usuário só na hora de mostrar. O banco fala uma língua só; a tela fala a língua de quem está olhando.

UTC não tem horário de verão, não muda, não depende de onde o servidor está. É um referencial neutro. Você armazena o instante absoluto e deixa a tradução para a borda do sistema, junto do usuário.

Offset não é fuso horário

Aqui mora uma confusão sutil e cara. “-03:00” é um offset: a diferença em relação ao UTC naquele instante. “America/Sao_Paulo” é um fuso horário: uma região com um histórico de regras, incluindo quando entrou e saiu do horário de verão ao longo dos anos.

A diferença importa quando você lida com o futuro. Se você agenda um lembrete para “3 de dezembro às 9h em São Paulo” e guarda só o offset de hoje, uma futura mudança nas regras de horário de verão pode fazer o lembrete disparar na hora errada. Para eventos futuros, guarde o fuso nomeado, não o número. Deixe a biblioteca resolver o offset quando a hora chegar.

Não reinvente o cálculo de tempo

Existe uma tentação recorrente de “só somar 3 horas” na mão. Resista. As regras de fuso horário do mundo mudam com frequência — governos criam e cancelam horário de verão, países mudam de fuso. Ninguém mantém isso de cabeça. O que mantém é o tz database, uma base pública que praticamente todas as linguagens usam por baixo dos panos.

// A ideia, em qualquer linguagem moderna:
instante = agoraEmUTC()                 // ponto absoluto
exibicao = instante.para("America/Sao_Paulo")  // tradução

// O que você NÃO quer fazer:
hora_errada = agora() + 3.horas         // frágil e sazonal

Use as ferramentas de data e hora com suporte a fuso que a sua plataforma oferece. Elas conhecem as exceções que você não conhece — e conhecem as que ainda vão existir, via atualização da base.

O clássico do relatório que fecha errado

Um caso que pega muita gente boa: “vendas do dia”. Se o servidor está em UTC e você conta “de 00h a 23h59” sem converter, o seu dia começa às 21h do dia anterior no horário de Brasília. As vendas das últimas três horas da noite caem no dia seguinte. O número fecha, ninguém percebe, e o relatório mente com convicção.

A correção é definir explicitamente o que é “um dia” para o seu negócio — quase sempre, o dia no fuso do usuário ou da empresa — e calcular as bordas a partir disso, convertendo para UTC antes de consultar o banco.

O tipo da coluna também conta

De nada adianta a disciplina de UTC se a coluna do banco joga a informação fora. Prefira tipos que guardam o instante com noção de fuso — no PostgreSQL, por exemplo, timestamptz em vez de timestamp. O primeiro registra um ponto absoluto no tempo; o segundo guarda um “número de relógio” solto, sem âncora, que reabre exatamente o problema que a gente estava tentando evitar.

E desconfie do relógio do servidor. Basear-se na hora local da máquina onde o código roda é pedir para quebrar no dia em que ela for reprovisionada em outra região, ou em que o container subir com o fuso padrão do sistema. Deixe o fuso ser uma decisão explícita da aplicação, e não um acidente de infraestrutura.

Um checklist para dormir tranquilo

  • Armazene instantes em UTC, sempre.
  • Converta para o fuso do usuário apenas na exibição.
  • Para eventos futuros, guarde o fuso nomeado, não o offset.
  • Nunca faça aritmética manual de fuso; use a biblioteca.
  • Ao agregar por “dia”, deixe explícito o fuso das bordas.

Tempo é um daqueles temas que parecem simples justamente porque a gente convive com ele o dia inteiro. No código, ele é cheio de exceções que você não vê até uma delas te acordar de madrugada. Trate datas com o respeito que elas merecem desde o começo, e você troca um bug sazonal e irritante por um problema que simplesmente nunca aparece.

Leia também

Prefere ver em vídeo?

Esse artigo virou um vídeo completo no canal, com diagramas animados e demo ao vivo.

Assistir no YouTube →