Conversor de timestamp e fuso horário
Converte timestamp Unix para data legível e entre fusos, detectando sozinho se o número está em segundos ou em milissegundos. O deslocamento é calculado para a data informada — é assim que se vê o horário de verão brasileiro, que existiu até 2019 e some depois. Tudo no navegador, sem enviar nada.
Conversor
| No fuso escolhido | |
|---|---|
| Em UTC | |
| Deslocamento | |
| Relativo a agora | |
| Timestamp (segundos) | |
| Timestamp (milissegundos) | |
| ISO 8601 no fuso | |
| ISO 8601 em UTC |
O ID de fuso que quebra no servidor
Este é o erro que aparece só em produção. O Windows identifica o fuso de Brasília como E. South America Standard Time; o Linux, como America/Sao_Paulo. Do .NET 6 em diante os dois funcionam nas duas plataformas, porque o runtime usa o ICU para traduzir — e é justamente por isso que o problema some do radar.
Até alguém ligar o InvariantGlobalization, que é prática comum em contêiner para reduzir a imagem. Sem o ICU, cada plataforma volta a aceitar só o identificador nativo. Medimos no .NET 10, no Windows:
// Os dois IDs identificam o MESMO fuso, e no .NET 6+ os dois funcionam nas duas
// plataformas — o ICU traduz um para o outro.
TimeZoneInfo.FindSystemTimeZoneById("America/Sao_Paulo"); // IANA
TimeZoneInfo.FindSystemTimeZoneById("E. South America Standard Time"); // Windows
// 🔴 MENOS num caso, e ele é comum em contêiner:
// <InvariantGlobalization>true</InvariantGlobalization>
//
// Ligado, o ICU sai — e com ele o mapeamento IANA. Medido no .NET 10, no Windows:
// "E. South America Standard Time" -> funciona (vem do registro do Windows)
// "America/Sao_Paulo" -> TimeZoneNotFoundException
//
// Em Linux é o espelho: o ID do Windows é que não existe.
// Ou seja: o mesmo código passa em desenvolvimento e estoura no contêiner.Vale conferir o .csproj dos seus serviços hoje. O sintoma é uma TimeZoneNotFoundException que não acontece em nenhuma máquina de desenvolvimento.
O horário de verão que ainda assombra
O Brasil não tem horário de verão desde 2019 — o Decreto 9.772/2019 o revogou, e o último período terminou em fevereiro daquele ano. O problema não é o presente, é o passado: datas anteriores continuam com o deslocamento antigo.
Experimente na ferramenta acima: 1545739200 (25/12/2018, meio-dia UTC) cai às 10:00 em São Paulo, com deslocamento -02:00. Já 1577275200 (25/12/2019, mesmo horário) cai às 09:00, com -03:00. Sistema que guardou hora local sem o deslocamento tem uma hora de erro nesses registros — permanentemente.
Timestamp em C#
// Timestamp Unix: use DateTimeOffset, não DateTime. O DateTimeOffset carrega o
// deslocamento junto, então não há ambiguidade sobre "que hora é esta".
var instante = DateTimeOffset.FromUnixTimeSeconds(1516239022);
// 2018-01-18 01:30:22 +00:00
var segundos = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var milissegundos = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
// Para converter para o horário de São Paulo:
var fuso = TimeZoneInfo.FindSystemTimeZoneById("America/Sao_Paulo");
var emSaoPaulo = TimeZoneInfo.ConvertTime(instante, fuso);O DateTime.Kind e a hora que volta errada do banco
// DateTime.Kind é a origem de metade dos bugs de fuso em .NET.
new DateTime(2026, 8, 28, 12, 0, 0).Kind; // Unspecified — nem UTC, nem local
DateTime.Now.Kind; // Local
DateTime.UtcNow.Kind; // Utc
// O problema: quase todo provedor de banco devolve Unspecified na leitura. O valor
// veio de uma coluna que guardava UTC, mas o .NET não sabe disso — e a primeira
// ToLocalTime() aplica o deslocamento em cima de uma hora que JÁ era local.
//
// A regra que evita o problema inteiro:
// grave sempre em UTC, trafegue DateTimeOffset, e converta só na exibição.Perguntas frequentes
Por que TimeZoneInfo.FindSystemTimeZoneById funciona na minha máquina e falha no servidor?
Quase sempre por causa do identificador do fuso. O Windows usa nomes próprios, como E. South America Standard Time, e o Linux usa os nomes IANA, como America/Sao_Paulo. A partir do .NET 6 os dois funcionam nas duas plataformas, porque o runtime usa o ICU para traduzir — mas isso deixa de valer quando o projeto liga InvariantGlobalization, que é comum em contêiner para reduzir a imagem. Sem o ICU, cada plataforma volta a aceitar só o próprio identificador, e o mesmo código que passa em desenvolvimento estoura com TimeZoneNotFoundException em produção.
O Brasil ainda tem horário de verão?
Não desde 2019. O Decreto 9.772/2019 revogou o horário de verão brasileiro, e o último período foi o de novembro de 2018 a fevereiro de 2019. Na prática isso significa que São Paulo ficou fixo em -03:00 — mas datas ANTERIORES continuam tendo o deslocamento antigo, e é aí que mora o erro. Uma data de dezembro de 2018 está em -02:00; a mesma data de dezembro de 2019 está em -03:00. Sistema que guardou hora local sem o deslocamento tem uma hora de diferença nesses registros, para sempre.
Qual a diferença entre DateTime, DateTimeOffset e DateOnly?
DateTime guarda data e hora com um Kind que diz apenas se é UTC, local ou indeterminado — e Unspecified é o valor mais comum, porque é o que o banco devolve. DateTimeOffset guarda data, hora e o deslocamento em relação ao UTC, então o instante é sempre inequívoco, e é o tipo certo para representar um momento no tempo. DateOnly, do .NET 6 em diante, guarda só a data, e é o tipo certo para data de nascimento e vencimento — casos em que a hora não existe e forçar uma cria bug de fuso do nada.
O timestamp que eu tenho está em segundos ou em milissegundos?
Pelo tamanho. Um timestamp em segundos para a data de hoje tem 10 dígitos; em milissegundos, 13. Um número com 13 dígitos interpretado como segundos joga a data para o ano 50000, e é o erro mais comum ao integrar com JavaScript, que trabalha em milissegundos por padrão, ou com APIs que seguem a convenção do Unix, em segundos. Esta ferramenta detecta pela grandeza e diz qual unidade assumiu.
Por que minha data volta do banco com uma hora de diferença?
O suspeito mais provável é o DateTime.Kind. A coluna guardava UTC, mas o provedor devolve o valor com Kind igual a Unspecified — o .NET não tem como saber que aquilo era UTC. Aí alguém chama ToLocalTime, que aplica o deslocamento assumindo que a hora era local, e o resultado fica deslocado. A correção estrutural é gravar sempre em UTC, trafegar DateTimeOffset em vez de DateTime, e converter para o fuso do usuário apenas no momento de exibir.
O que é o timestamp Unix e por que 1970?
É a contagem de segundos desde a meia-noite de 1º de janeiro de 1970 em UTC, chamada de época Unix. A data foi escolhida por conveniência quando o sistema foi criado, e virou a convenção usada por praticamente toda API, log e banco de dados. A vantagem é que um timestamp é um número único e sem ambiguidade de fuso — o problema de fuso só aparece quando ele é convertido para uma data legível.
Outras ferramentas
Todas em ferramentas para desenvolvedor — rodam no navegador, sem cadastro e sem anúncio.
Receba os próximos artigos
Conteúdo técnico de .NET direto no seu e-mail. Sem spam, e você sai quando quiser.