Pular para o conteúdo principal
Tutorial

Timestamps Unix em produção: segundos vs milissegundos, o problema 2038 e bugs de fuso horário

· 12 min de leitura

Todo sistema de produção eventualmente registra, armazena ou transmite um timestamp Unix. E todo sistema de produção eventualmente lança um bug envolvendo um — uma data que renderiza como janeiro de 1970, um evento agendado 49 anos no passado, uma integração de API onde um lado espera milissegundos e o outro envia segundos. Esses bugs são chatos, comuns e totalmente evitáveis uma vez que você conhece o punhado de regras que governam como timestamps realmente funcionam.

Este guia cobre o núcleo prático: reconhecer unidades de timestamp pela contagem de dígitos, o problema 2038 e seu status real hoje, os padrões de bug que realmente ocorrem, e a maneira correta de obter um timestamp nas linguagens que você provavelmente está usando.

O que é (e o que não é) um timestamp Unix

Um timestamp Unix conta o número de segundos desde 1970-01-01 00:00:00 UTC — a «época Unix». Duas propriedades importam para tudo que segue:

  1. Ele não tem fuso horário. O timestamp 1785235837 se refere a um instante específico simultaneamente em todo o planeta. Fusos horários só entram quando você exibe um timestamp para um humano ou constrói um a partir de componentes de relógio local. Se você internalizar uma frase deste artigo, que seja essa: timestamps são instantes UTC; fusos horários são uma questão de renderização.
  2. Ele ignora segundos intercalares. O tempo Unix assume que cada dia tem exatamente 86.400 segundos. Quando um segundo intercalar é inserido, o tempo POSIX efetivamente o repete ou espalha (o comportamento varia por sistema; o «leap smear» do Google distribui ao longo do dia). Para a maior parte do código de aplicação isso é irrelevante; para cálculos de intervalo atravessando um segundo intercalar significa que sua suposição de «exatamente N segundos» pode falhar por um.

Note também que a escolha da época significa que timestamps negativos são válidos: -1 é 1969-12-31 23:59:59 UTC. Código que trata timestamps negativos como erros quebra em datas antes de 1970 — um padrão real de bug para sistemas lidando com dados históricos (datas de nascimento, registros de arquivo).

Segundos, milissegundos, microssegundos: a heurística de contagem de dígitos

Timestamps chegam em unidades diferentes de ecossistemas diferentes, e identificar errado a unidade é o bug mais comum de timestamp. O teste rápido de campo é contar os dígitos:

UnidadeExemplo (meados de 2026)DígitosQuando atinge esta largura
Segundos17852358371010 dígitos desde 2001-09-09; 11 dígitos a partir de 2286
Milissegundos17852358370001313 dígitos desde 2001-09-09
Microssegundos17852358370000001616 dígitos
Nanossegundos178523583700000000019excede alcance de int64 em 2262

Regras práticas para a era atual (2001–2286):

  • 10 dígitos → segundos. Sempre seguro assumir.
  • 13 dígitos → milissegundos. O Date.now() do JavaScript, o System.currentTimeMillis() do Java e a maioria dos sistemas de log/métricas emitem esses.
  • 16 dígitos → microssegundos. O time.time() do Python vezes um milhão, alguns bancos de dados, alguns sistemas de tracing.
  • 19 dígitos → nanossegundos. O time.Now().UnixNano() do Go, e este é uma armadilha: 19 dígitos excede o máximo signed 64-bit (9.223.372.036.854.775.807) após 2262, mas hoje um número de 19 dígitos cabe em int64 — por pouco. Se você armazena timestamps em nanossegundos, não faça aritmética que assuma folga.

Uma verificação rápida de sanidade ao depurar: cole o número em um Conversor de Timestamps e veja se a data parece sensata. Se uma interpretação em «segundos» produz 1970 e uma em «milissegundos» produz uma data recente plausível, você identificou a unidade em dois segundos.

O padrão de bug correspondente: tratar milissegundos como segundos (ou vice-versa). Multiplique um timestamp em segundos por 1000 sem necessidade e 1785235837 vira 1785235837000 segundos — uma data aproximadamente 56.000 anos no futuro, que a maioria dos renderizadores trunca ou embrulha em lixo. Divida um timestamp em milissegundos por 1000 quando já era segundos e você obtém 1970-01-21 — por isso tantos sistemas quebrados exibem datas em janeiro de 1970. Quando você vê 1970 em produção, suspeite de um descompasso de unidade primeiro.

O problema 2038

O time_t Unix clássico era um inteiro de 32 bits com sinal contando segundos. Seu valor máximo, 2.147.483.647, corresponde a 2038-01-19 03:14:07 UTC. Um segundo depois, um contador de 32 bits com sinal transborda para −2.147.483.648, que renderiza como 1901-12-13 20:45:52 UTC. Esse é o problema «Y2038» ou «Epochalypse» — estruturalmente idêntico ao Y2K, mas enraizado em um tipo C específico em vez de em campos de ano de dois dígitos.

Onde ele ainda importa de fato, em 2026:

  • Sistemas embarcados e firmware de 32 bits. Dispositivos com longos ciclos de vida — controladores industriais, sistemas automotivos, dispositivos médicos, infraestrutura — rodando kernels de 32 bits ou ABIs com time_t de 32 bits. Essa é a principal classe de risco residual, precisamente porque esses sistemas são difíceis de patchear e fáceis de esquecer.
  • Formatos de arquivo e protocolos antigos que especificavam contadores de 32 bits em segundos. Alguns são corrigidos reinterpretando o campo como unsigned (empurrando o limite para 2106); outros são simplesmente legado.
  • Bancos de dados com tipos limitados pela época. O exemplo conhecido: o tipo TIMESTAMP do MySQL historicamente ia apenas até 2038-01-19 03:14:07 UTC porque armazenava segundos como um inteiro unsigned de 32 bits. (DATETIME tem alcance até o ano 9999 e não é afetado.) Se seu esquema usa TIMESTAMP, conheça esse limite.

O que já foi corrigido:

  • Linux de 64 bits e sistemas modernos de 64 bits usam um time_t de 64 bits, bom por ~292 bilhões de anos.
  • O trabalho time64 do kernel Linux (concluído no kernel 5.6, 2020) e o suporte a tempo de 64 bits do glibc (_TIME_BITS=64 do glibc 2.34) dão ao userland Linux de 32 bits um caminho de migração, embora binários devam ser reconstruídos com a nova ABI — binários compilados antigos em sistemas de 32 bits permanecem vulneráveis.
  • JavaScript, Python, Go, Java todos usam representações de tempo de 64 bits (ou float64) e não transbordam em nenhum prazo relevante para humanos. Os milissegundos em float64 do JavaScript vão ~285.000 anos em qualquer direção.

Conselho prático: se você está escrevendo código novo em uma plataforma de 64 bits com uma linguagem mainstream, 2038 não é seu problema. Se você mantém firmware embarcado, C legado em alvos de 32 bits, ou esquemas cheios de colunas INT de época e campos MySQL TIMESTAMP, audite agora — sistemas instalados nos anos 2020 ainda estarão rodando em 2038.

Os padrões de bug que realmente acontecem

1. Confusão de unidades (segundos vs milissegundos)

Coberto acima: o mais comum e o mais fácil de identificar (datas em 1970 ou no ano 58000). Conserte por convenção: nomeie suas variáveis e campos com a unidade — created_at_ms, expires_s — e valide nas fronteiras de API. Um timestamp fora de, digamos, 10^910^10 segundos é suspeito para um campo de «segundos» neste século.

2. Hora local confundida com UTC

O inverso da regra «timestamps não têm fuso horário». Manifestação clássica: um servidor em Xangai e um servidor na Virgínia calculam «o mesmo» prazo de forma diferente porque um lado construiu o timestamp a partir de componentes de relógio local. Qualquer caminho de código que vai de campos de data/hora locais → timestamp deve especificar o fuso desses campos explicitamente. new Date(2026, 6, 28) em JavaScript significa meia-noite no fuso horário local do navegador, que é um instante diferente para cada usuário.

3. Bordas do horário de verão

DST cria dois tipos de horários de relógio quebrados:

  • Horários inexistentes. Nos fusos dos EUA, 2026-03-08 02:30 local não existe — relógios saltam 02:00 → 03:00. Um agendador estilo cron configurado para «rodar às 02:30 diariamente» vai pular, rodar duas vezes, ou se comportar de forma dependente da plataforma naquele dia.
  • Horários ambíguos. Em 2026-11-01, 01:30 local acontece duas vezes nos fusos dos EUA quando os relógios retrocedem. Armazenar «01:30» sem offset ou fuso não consegue distinguir qual você quis dizer.

Os consertos robustos: agende jobs recorrentes em UTC, armazene instantes como timestamps e só converta para hora local na exibição. Se você precisa armazenar horários locais futuros (ex., «reunião 9 AM do usuário»), armazene a hora local mais o nome de fuso IANA (America/New_York) e resolva para um timestamp na renderização — porque governos mudam regras de DST, e um timestamp calculado hoje para uma reunião de 2028 pode estar errado após uma mudança de lei.

4. Meses zero-indexados do JavaScript

new Date(2026, 6, 28) é julho 28, não junho 28 — meses vão de 0–11 enquanto dias vão de 1–31. Essa inconsistência produziu uma geração de bugs de off-by-one-month. Mitigações: use Date.UTC(...) quando quiser dizer UTC, use strings ISO (new Date("2026-07-28T00:00:00Z")) para clareza, ou use uma biblioteca (Temporal, quando pousar amplamente, conserta isso de verdade com meses 1-indexados).

5. Precisão de float em segundos

O time.time() do Python retorna um float. Um float64 tem 53 bits de mantissa; nas magnitudes de época atuais (~1.79 × 10^9 segundos), a resolução é fina (~240 nanossegundos), então isso raramente morde em Python — mas o mesmo raciocínio morde em sistemas que carregam timestamps de microssegundos ou nanossegundos como float64: um valor de microssegundos de 16 dígitos precisa de 54 bits para representação exata, então microssegundos em float64 já são imprecisos hoje. Use inteiros para qualquer coisa mais fina que milissegundos.

6. Parsing de string sem offset

new Date("2026-07-28") em JavaScript parseia como meia-noite UTC, mas new Date("2026-07-28 00:00:00") (sem T, sem Z) parseia como meia-noite local — uma virada silenciosa de fuso baseada em detalhes do formato da string. Sempre inclua um Z explícito ou offset numérico em datetimes serializados. Prefira ISO 8601 com offset: 2026-07-28T10:00:00+08:00.

Obtendo o timestamp corretamente, por linguagem

JavaScript / TypeScriptDate.now() retorna milissegundos; divida para segundos:

const ms = Date.now();                      // 1785235837123
const seconds = Math.floor(Date.now() / 1000); // 1785235837
// De um instante específico, sempre especifique UTC explicitamente:
const ts = Date.UTC(2026, 6, 28, 0, 0, 0) / 1000; // meses 0-indexados!

Pythontime.time() retorna segundos em float; use datetime com UTC explícito para qualquer coisa que você vá armazenar:

import time
from datetime import datetime, timezone

seconds = int(time.time())                    # 1785235837
ns = time.time_ns()                           # nanossegundos, inteiro exato
ts = int(datetime.now(timezone.utc).timestamp())

Go — o pacote time te dá cada unidade explicitamente:

now := time.Now()
seconds := now.Unix()       // int64 segundos
millis  := now.UnixMilli()  // int64 milissegundos
nanos   := now.UnixNano()   // int64 nanossegundos — cuide do alcance de int64

Java — evite Date/Calendar legados; use java.time:

long millis  = System.currentTimeMillis();
long seconds = Instant.now().getEpochSecond();
long nanos   = Instant.now().getNano(); // nano-do-segundo, não nanos de época!

Bash — GNU vs macOS (BSD) date diferem no parsing:

date +%s                        # segundos de época atuais (ambos)
date -d @1785235837             # GNU: timestamp → data humana
date -r 1785235837              # macOS/BSD: timestamp → data humana
date -u -d "2026-07-28 00:00:00 UTC" +%s   # GNU: data → timestamp
date -u -j -f "%Y-%m-%d %H:%M:%S" "2026-07-28 00:00:00" +%s  # macOS

Note o tema recorrente: toda API correta ou te dá o valor de época diretamente ou te força a nomear o fuso. Toda API perigosa permite que você omita.

Trabalhando com timestamps interativamente

Ao depurar um timestamp de um log ou resposta de API, não faça as contas na cabeça. Cole no nosso Conversor de Timestamps: ele mostra o instante em UTC e no seu fuso local, e autodetecta segundos vs milissegundos — que é exatamente a checagem de ambiguidade descrita acima. Ele também converte na outra direção (data humana + fuso → timestamp) para construir fixtures de teste.

Resumo

  • Um timestamp Unix é segundos (ou ms/µs/ns) desde 1970-01-01 UTC. Não tem fuso horário e não tem segundos intercalares.
  • A contagem de dígitos identifica a unidade na era moderna: 10 = segundos, 13 = milissegundos, 16 = microssegundos, 19 = nanossegundos.
  • 2038 transborda o time_t de 32 bits com sinal em 2038-01-19 03:14:07 UTC. Sistemas de 64 bits e linguagens mainstream estão seguros; firmware embarcado, binários legados de 32 bits e colunas MySQL TIMESTAMP são o risco residual.
  • Os padrões reais de bug: confusão de unidades, erros local-vs-UTC, bordas de DST, meses zero-indexados de JS, perda de precisão float64 em µs/ns e parsing de string sem offset.
  • Armazene instantes como timestamps inteiros, nomeie campos com unidades, agende em UTC, converta para local só na exibição — e mantenha um Conversor de Timestamps nos favoritos para a depuração que escapar de qualquer jeito.