Voltar para o blog

Datas e fuso: por que o vencimento volta um dia

Equipe AltiviaCloud · 6 de setembro de 2026 · 4 min de leitura

Meia-noite em UTC convertida para São Paulo é 21h do dia anterior. A conversão está certa — o erro foi guardar um dia como se fosse um instante.

Bug de fuso horário tem uma característica cruel: ele funciona no seu computador, passa nos testes, e aparece em produção como "a data está um dia errada" — geralmente relatado por um cliente, geralmente no fim de semana.

E quase sempre a causa é a mesma.

O clássico: meia-noite que volta um dia

Um vencimento gravado como 2026-09-02T00:00:00.000Z. Você exibe convertendo para o fuso de São Paulo, que é três horas atrás.

Meia-noite menos três horas é 21h do dia anterior. A tela mostra 01/09. O banco diz 02/09.

O cliente liga dizendo que o serviço venceu um dia antes. Você olha o banco, vê a data certa, e não entende a reclamação.

Isso não é erro de conversão — a conversão está correta. O erro é ter guardado um dia como se fosse um instante.

A distinção que resolve a maior parte

Instante é um momento no tempo. "Quando o pedido foi criado." Existe igual para todo mundo no planeta.

Data de calendário é um dia. "O vencimento é dia 2 de setembro." Não tem hora, e não deveria virar um instante.

Guardar instante em UTC é o certo. Guardar data de calendário em UTC produz exatamente o bug acima, porque qualquer conversão a desloca.

Na prática:

  • Instante — timestamp com fuso, sempre em UTC, convertido só na exibição.
  • Data de calendário — tipo date, ou texto no formato 2026-09-02, sem hora e sem conversão nenhuma.

Se você precisa comparar um vencimento que é data de calendário com "agora", a comparação é feita no fuso do negócio, não em UTC.

Consulta manual morde igual

Não é só o código de exibição. Uma consulta que converte na hora de agrupar produz o mesmo deslocamento.

Somar pedidos "por dia" convertendo o instante para o fuso local está certo — é o que o cliente entende por dia. Mas somar comparando com um limite que já é meia-noite UTC não: você inclui ou exclui um dia inteiro por engano.

A regra prática: em consulta de vencimento, use o valor cru em UTC. Em consulta de agrupamento por dia de negócio, converta explicitamente, sempre para o mesmo fuso.

O que não pode é misturar os dois na mesma consulta.

Guarde o instante, formate na borda

Toda a lógica interna trabalha em UTC. A conversão para o fuso do usuário acontece no último passo, na hora de mostrar.

Isso vale para log, para API e para banco. O momento em que uma data vira texto formatado é o momento em que ela deixa de ser calculável — então esse momento deve ser o mais tarde possível.

Se a sua API devolve "02/09/2026", quem consome precisa adivinhar o fuso. Devolva no formato ISO, com o deslocamento explícito, e deixe a formatação para quem exibe.

Horário de verão e outras surpresas

Fuso não é um número fixo. Regras mudam, países adotam e abandonam horário de verão, e um mesmo lugar pode ter tido deslocamentos diferentes ao longo dos anos.

Duas consequências práticas:

Nunca guarde o deslocamento como número. -3 hoje pode ser -2 amanhã. Guarde o nome do fuso — America/Sao_Paulo — e deixe a biblioteca resolver.

Cuidado com "adicionar 24 horas". Em dia de mudança de horário, adicionar 24 horas não cai no mesmo horário do dia seguinte. Se você quer "amanhã, mesma hora", isso é uma operação de calendário, não de segundos.

O servidor não deve depender do fuso da máquina

Código que usa a data local do servidor produz resultados diferentes conforme onde ele roda. Isso quebra de forma especialmente confusa quando o ambiente de desenvolvimento está em um fuso e produção em outro — ou quando a máquina de produção está fora do país.

Defina o fuso explicitamente nas operações que dependem dele, em vez de herdar do sistema operacional.

Testes que pegam isso antes

Três casos que revelam quase todo bug de fuso:

  • uma data às 23h no fuso local, que em UTC já é o dia seguinte;
  • uma data às 00h em UTC, que no fuso local ainda é o dia anterior;
  • um instante no dia de mudança de horário de verão.

Se a suíte só testa datas do meio da tarde, ela nunca vai encontrar o problema.

Quando esse tipo de erro aparece em produção, o que permite reconstruir o que houve é o registro — e o que registrar em log de integração trata de guardar o suficiente para isso sem gravar o que não pode.

Se a sua integração é com a API de números virtuais, o prazo de cada pedido chega no campo expiresAt — é nele que essa disciplina de fuso se aplica primeiro.