Marcas temporais Unix, segundos intercalares e o problema de 2038
Publicado a 20/05/2025 · 18 min de leitura · Ferramentas para programadores
Daniel Okonkwo — Programador front-end e redator de Tecnologia na OneKitly
Desempenho web · Formatos de ficheiro
Verificado a partir de 7 fontes
O tempo Unix é o número de segundos desde 1970-01-01T00:00:00Z, calculado como se cada dia contivesse exatamente 86 400 segundos. Os segundos intercalares ficam de fora, pelo que não é uma contagem de segundos físicos decorridos: 86 400 segundos Unix separam 2016-12-31T00:00:00Z de 2017-01-01T00:00:00Z, ainda que tenham passado realmente 86 401 segundos SI, porque a UTC inseriu 23:59:60 pelo meio. É essa a troca deliberada: o contador perde o acordo com a física e ganha a propriedade de que dividir por 86 400 dá sempre a data UTC certa, sem qualquer tabela de segundos intercalares. Foram inseridos vinte e sete segundos intercalares desde 1972, o último a 2016-12-31, e um relógio POSIX não consegue representar :60, pelo que repete ou congela um segundo. O problema de 2038 é à parte: um contador de 32 bits com sinal esgota-se em 2 147 483 647, que é 2038-01-19T03:14:07Z. Um segundo depois salta para −2 147 483 648, ou 1901-12-13T20:45:52Z. Um time_t de 64 bits vai até 9 223 372 036 854 775 807 segundos, cerca de 292 mil milhões de anos, e encerra o assunto para sempre. O erro mais comum não é 2038 mas confundir segundos com milissegundos: para 2026-08-15, o valor em segundos lido como milissegundos dá 1970-01-21 e o valor em milissegundos lido como segundos dá o ano 58589.
O tempo Unix conta segundos desde 1970 ignorando deliberadamente os segundos intercalares, razão pela qual não é uma contagem de segundos físicos decorridos. Eis o que essa troca compra, o que um segundo intercalar faz ao contador, o instante exato em que um contador de 32 bits com sinal transborda, e o erro do fator 1000 que o atira para 1970 ou para o ano 58589.
O que o contador conta realmente
O tempo Unix, ou tempo POSIX, é definido como o número de segundos decorridos desde 1970-01-01T00:00:00Z sob o pressuposto de que cada dia contém exatamente 86 400 segundos. A definição é aritmética, não observacional: a especificação dá uma fórmula construída a partir do ano, do dia do ano, da hora, do minuto e do segundo, e essa fórmula não tem termo nenhum para segundos intercalares, porque dentro dela não existem.
A consequência merece ser dita sem rodeios, porque é aí que começa quase toda a confusão: uma marca temporal Unix não é uma contagem de segundos físicos. É uma representação comprimida de uma data e hora do calendário UTC. Duas marcas separadas por 86 400 estão exatamente a um dia UTC de distância por construção, sempre, independentemente do que um relógio de césio tenha medido nesse intervalo.
Uma ressalva, porque a propriedade é muitas vezes exagerada. Vale em UTC, não em hora local. Medido sobre dados reais de fusos, de meia-noite local a meia-noite local em Europe/Paris deu 82 800 segundos Unix a 2026-03-29 e 90 000 a 2026-10-25, contra 86 400 num dia normal; America/New_York deu 82 800 a 2026-03-08. A hora de verão não muda o contador — muda quantos tiques cabem num dia de calendário local, que é exatamente por isso que as marcas temporais devem ser guardadas como instantes UTC e convertidas só no momento de mostrar.
A troca: sem segundos intercalares, sem tabela
Excluir os segundos intercalares parece um defeito até se olhar para a alternativa. Se o contador seguisse segundos físicos, converter uma marca em data exigiria conhecer cada segundo intercalar inserido entre a época e essa marca. Os segundos intercalares são anunciados pelo IERS com alguns meses de antecedência, com base em medições da rotação da Terra, pelo que essa tabela não pode ser calculada, apenas distribuída. Cada dispositivo precisaria dela, atualizada, e qualquer um com uma tabela desatualizada calcularia uma data de calendário diferente a partir do mesmo número.
O desenho escolheu determinismo em vez de exatidão física, e a recompensa é que a aritmética de marcas funciona em todo o lado sem estado partilhado. Um telemóvel que nunca teve ligação calcula exatamente a mesma data a partir de 1786752000 que um relógio de centro de dados. Dividir por 86 400 é correto. Somar um dia é somar 86 400. Ordenar por marca ordena por tempo. Nada disso sobreviveria a uma definição em segundos físicos.
O preço paga-se num único sítio: qualquer cálculo de duração física que atravesse um segundo intercalar erra pelo número de segundos intercalares atravessados. Entre 2016-12-31T00:00:00Z e 2017-01-01T00:00:00Z a diferença Unix é 86 400, enquanto decorreram realmente 86 401 segundos SI. Para quase todas as aplicações esse erro é irrelevante. Para telemetria de satélites, sequenciação de negócios financeiros com granularidade abaixo do segundo e metrologia física não é, e esses domínios usam TAI ou tempo GPS — escalas monótonas sem qualquer segundo intercalar.
O que um segundo intercalar faz ao contador
A UTC insere um segundo intercalar permitindo que um minuto contenha 61 segundos, com o segundo extra rotulado 23:59:60. Foram inseridos vinte e sete desde 1972 — TAI menos UTC valia 10 segundos a 1972-01-01 e vale 37 desde 2017-01-01, e a diferença é exatamente a contagem. Todos foram positivos; um segundo intercalar negativo, que retire um segundo, é permitido pela norma e nunca foi usado.
Um relógio POSIX não tem representação para o segundo numerado 60, pelo que algo tem de ceder. O comportamento clássico é repetir: o contador emite duas vezes o mesmo valor, um segundo não é monótono, e qualquer código que assuma marcas estritamente crescentes vê um duplicado. Alguns sistemas congelam antes o contador durante um segundo. Ambos são visíveis para as aplicações, e ambos causaram falhas reais em sistemas que tratavam uma marca como chave única ou como número de sequência estritamente crescente.
A resposta pragmática para a qual os grandes operadores convergiram é o alisamento: espalhar o segundo extra por uma janela de horas para que nenhum relógio repita nem pare, ao custo de todos os relógios da janela ficarem ligeiramente errados. Isto importa cada vez menos, porque em novembro de 2022 a 27.ª Conferência Geral de Pesos e Medidas resolveu deixar de inserir segundos intercalares até 2035, permitindo que a UTC se afaste do tempo solar para além do limite atual. O mecanismo ainda não foi decidido, mas a direção está: o segundo intercalar vai reformar-se.
2038, calculado com exatidão
Um inteiro de 32 bits com sinal vai de −2 147 483 648 a 2 147 483 647. Interpretado como segundos desde a época, o máximo é 2038-01-19T03:14:07Z. Guardar 2 147 483 648 numa casa real de 32 bits com sinal e relê-la dá −2 147 483 648, que é 1901-12-13T20:45:52Z. Guardar 2 147 483 649 dá 1901-12-13T20:45:53Z. A falha não é uma queda nem um erro: é uma data 136 anos no passado, entregue em silêncio e usada.
Dois factos vizinhos merecem ser conhecidos. Um contador de 32 bits sem sinal chega a 4 294 967 295, que é 2106-02-07T06:28:15Z — solução comum em firmware embebido, que apenas adia o problema e ainda torna irrepresentáveis as datas anteriores a 1970. E um time_t de 64 bits com sinal chega a 9 223 372 036 854 775 807 segundos, cerca de 292 mil milhões de anos, aproximadamente vinte e uma vezes a idade atual do universo. Isso não é um adiamento: é uma correção permanente.
A data a temer não é 2038 mas hoje, porque os primeiros sistemas a partir são os que calculam instantes futuros. Desde 2026-08-15 o transbordo está a 360 731 647 segundos — 4 175 dias, ou 11,43 anos. Um horizonte de dez anos a partir dessa data acaba a 2036-08-14 e ainda cabe. Um de quinze acaba a 2041-08-14 e não cabe. Tudo o que guarde um vencimento, um plano de amortização, uma janela de retenção ou uma validade de certificado superior a cerca de onze anos e meio já produz valores que um campo de 32 bits não consegue conter.
O que está e o que não está em risco em 2026
Os sistemas operativos de 64 bits generalistas estão bem, e há anos. Na máquina usada para este artigo, sizeof(time_t) vale 8 bytes e o comando date -u -r 2147483648 imprime Tue Jan 19 03:14:08 UTC 2038 sem transbordo nenhum. O mesmo vale para qualquer Linux, macOS e Windows de 64 bits atual. O JavaScript nunca esteve exposto: um Date guarda uma contagem float64 de milissegundos e a especificação limita o seu alcance a mais ou menos 8 640 000 000 000 000 milissegundos, o que vai de −271821-04-20 a +275760-09-13.
A exposição que resta é estreita mas real, e está sobretudo nos campos, não nos processadores. Os tipos de coluna com alcance de 32 bits documentado são os mais comuns: o TIMESTAMP do MySQL está especificado para terminar a 2038-01-19 03:14:07 UTC, enquanto o seu tipo DATETIME não é afetado. Os formatos de rede e de ficheiro que especificam um campo de 32 bits não podem ser alargados sem mudança de versão. O firmware embebido em microcontroladores de 32 bits usa frequentemente um contador de 32 bits de propósito, por razões de memória. E qualquer base de código que guarde um segundo de época num tipo explicitamente de 32 bits — uma coluna int32, um registo binário de largura fixa, uma struct de C compilada para um alvo de 32 bits — carrega o limite seja qual for o sistema operativo por baixo.
A auditoria prática é curta. Procure colunas int32 e INTEGER que guardem segundos de época; verifique o alcance documentado de cada tipo de coluna temporal que usa; localize registos binários de largura fixa e qualquer struct compilada para um alvo de 32 bits; e teste com o valor 2147483648 em vez de esperar. Se um campo não puder ser alargado, guardar uma cadeia ISO 8601 ou uma contagem de milissegundos de 64 bits são ambas alternativas válidas, e ambas custam mais bytes do que poupam discussões.
Segundos ou milissegundos: o erro do fator 1000
É de longe o erro de marcas temporais mais comum, e é puramente um problema de unidades. As ferramentas Unix, a maioria das API e a definição POSIX usam segundos. O JavaScript, o Java e imensas API web usam milissegundos. Os dois são o mesmo número escalado por 1 000, e nenhum leva rótulo no formato de transporte, pelo que um desencontro é invisível até uma data ser mostrada.
Ambos os sentidos produzem um resultado absurdo, e essa é a boa notícia. Tome 2026-08-15T00:00:00Z: em segundos é 1786752000, em milissegundos 1786752000000. Dê o valor em segundos a algo que espera milissegundos e obtém 1970-01-21T16:19:12Z — três semanas depois da época, porque 1,79 mil milhões de milissegundos são apenas cerca de vinte dias. Dê o valor em milissegundos a algo que espera segundos e obtém o ano 58589, em concreto +058589-12-01T00:00:00Z. Ambos estão tão longe do plausível que uma única verificação os apanha.
A heurística que funciona: uma marca atual em segundos tem dez dígitos, e em milissegundos treze. Dez dígitos continuarão corretos até 2286. Melhor do que uma heurística é dar ao campo um nome que torne a unidade impossível de ler mal — expiresAtSeconds em vez de expiresAt — ou passar uma cadeia RFC 3339 pela fronteira e analisá-la à chegada, o que é autodescritivo e custa umas dezenas de bytes.
Guardar o tempo para que sobreviva
Guarde o instante, não a representação. Um instante é um ponto na linha do tempo e fica totalmente especificado por uma marca UTC com largura suficiente — um segundo de época de 64 bits, uma contagem de milissegundos de 64 bits, ou uma cadeia RFC 3339 terminada em Z. Uma representação é o que uma pessoa num lugar concreto veria, e depende de regras de fuso que os governos alteram com poucas semanas de aviso. Guardar a representação é guardar uma resposta que pode tornar-se falsa retroativamente.
Uma exceção merece ser nomeada, porque guardar em UTC é muitas vezes vendido como conselho universal. Um compromisso futuro num lugar com nome não é um instante: é uma hora de relógio de parede numa jurisdição, e se essa jurisdição mexer nos relógios, o instante correto muda. Uma reunião às 09:00 em Berlim no próximo novembro deve ser guardada como data local, hora local e identificador de fuso IANA, e resolvida em instante só quando for preciso. Guarde UTC para o que aconteceu, e hora local mais nome de fuso para o que está agendado.
Além disso, três hábitos removem quase toda a dor restante. Dê a cada campo de marca uma unidade no nome, para que um leitor nunca tenha de adivinhar entre segundos e milissegundos. Use um tipo de 64 bits em todo o lado, incluindo na coluna da base de dados, para que 2038 seja uma curiosidade histórica e não um prazo. E nunca trate uma marca como identificador único nem número de sequência monótono, porque o tratamento de segundos intercalares, as correções de relógio e as migrações de máquinas virtuais podem todos fazer o mesmo valor aparecer duas vezes ou fazer o tempo recuar por instantes.
| Representação | Unidade | Mais antiga | Mais recente | Onde ainda se encontra |
|---|---|---|---|---|
| time_t de 32 bits com sinal | Segundos | 1901-12-13T20:45:52Z | 2038-01-19T03:14:07Z | Alvos embebidos de 32 bits, colunas int32, registos binários de largura fixa |
| Contador de 32 bits sem sinal | Segundos | 1970-01-01T00:00:00Z | 2106-02-07T06:28:15Z | Soluções de firmware; nenhuma data anterior a 1970 possível |
| time_t de 64 bits com sinal | Segundos | Cerca de 292 mil milhões de anos antes de 1970 | Ano 292 277 026 596 (2^63 − 1 segundos) | Toda a versão atual de 64 bits de Linux, macOS e Windows |
| Date do JavaScript | Milissegundos (float64) | −271821-04-20 | +275760-09-13 (±8 640 000 000 000 000 ms) | Navegadores e Node; nunca tiveram problema de 2038 |
| TIMESTAMP do MySQL | Segundos | 1970-01-01 00:00:01 UTC | 2038-01-19 03:14:07 UTC | Muito difundido; DATETIME é a alternativa não afetada |
| Cadeia RFC 3339 | Texto, autodescritivo | Sem limite inferior no formato | Sem limite superior no formato | API e registos; custa bytes, elimina a ambiguidade segundos/milissegundos |
Perguntas frequentes
- Uma marca temporal Unix está em UTC ou no meu fuso local?
- Não é nenhuma das duas, a rigor, e essa é a forma útil de pensar nisso. Uma marca Unix identifica um ponto na linha do tempo. Não carrega fuso nenhum, porque não precisa — o número 1786752000 refere-se ao mesmo momento em toda a Terra. O que é verdade é que convertê-la em data legível exige um fuso, e que a época está ancorada em 1970-01-01T00:00:00 UTC, pelo que converter sem fuso indicado dá UTC. É por isso que uma marca é a coisa certa para guardar e transmitir e a coisa errada para mostrar: a camada de armazenamento precisa de um instante inequívoco, e a de apresentação precisa de fuso, de configuração regional e de calendário. O erro habitual é converter para hora local a meio de um encadeamento e depois guardar o resultado, o que fixa o fuso de uma máquina e desloca em silêncio cada valor pelo seu desvio. Converta uma vez, no último momento possível, na interface.
- O que acontece exatamente às 03:14:07 de 19 de janeiro de 2038?
- Em qualquer sistema que guarde a marca num inteiro de 32 bits com sinal, o contador chega a 2 147 483 647 e o incremento seguinte transborda. Simulado numa casa real de 32 bits com sinal, guardar 2 147 483 648 relê-se como −2 147 483 648, que converte para 1901-12-13T20:45:52Z. O comportamento não é uma exceção nem uma queda: o valor passa simplesmente a ser uma data 136 anos no passado e é usado. O que isso provoca depende do código acima. As ordenações invertem-se. Os cálculos de idade e duração ficam enormemente negativos. Certificados e sessões parecem expirados há muito, ou não expirar nunca. Os agendadores do tipo cron disparam continuamente ou param. Os registos caem na partição errada. O perigo vem exatamente de nada levantar um erro: cada camada recebe um número bem formado e comporta-se corretamente para o número que lhe deram. É também por isso que testar é fácil — ponha 2147483648 num campo hoje e releia-o, em vez de esperar pela data.
- Os meus servidores são de 64 bits. Estou a salvo do problema de 2038?
- O seu sistema operativo está, a sua aplicação talvez não. Numa máquina moderna de 64 bits sizeof(time_t) vale 8 bytes — verificado aqui — e a linha de comandos imprime Tue Jan 19 03:14:08 UTC 2038 para o valor 2147483648, sem transbordar. Mas o núcleo raramente é onde o limite vive. A exposição está nos campos que escolheu: uma coluna INTEGER ou int32 com segundos de época, um protocolo ou formato de ficheiro com campo de marca fixo de 32 bits, uma struct compilada para um alvo embebido de 32 bits, um registo binário de largura fixa escrito há anos. O tipo de coluna TIMESTAMP do MySQL é um exemplo documentado, especificado para acabar a 2038-01-19 03:14:07 UTC independentemente de quantos bits o servidor tenha, ao passo que o seu tipo DATETIME não é afetado. Uma segunda exposição, menos óbvia, é qualquer equipamento de terceiros no parque — impressoras, câmaras, controladores, sensores — cujo firmware é de 32 bits por desenho e talvez nunca seja atualizado. A auditoria vale a pena agora e não em 2037, porque as falhas começam pelos valores futuros e 2026 já está dentro da janela de onze anos e meio.
- Como sei se um número está em segundos ou em milissegundos?
- Conte os dígitos. Uma marca atual em segundos tem dez dígitos e continuará a ter dez até 2286; o mesmo instante em milissegundos tem treze. Para 2026-08-15T00:00:00Z os dois valores são 1786752000 e 1786752000000. Na dúvida, converta e olhe para o resultado, porque ambos os erros produzem algo obviamente absurdo: ler o valor em segundos como milissegundos dá 1970-01-21T16:19:12Z, três semanas depois da época, e ler o valor em milissegundos como segundos dá o ano 58589. Uma data de janeiro de 1970 ou de um futuro distante é quase sempre este erro e não dados maus. A correção duradoura não é detetar mas nomear. Chame ao campo expiresAtSeconds ou createdAtMillis para que a unidade viaje com o valor, ou envie uma cadeia RFC 3339 como 2026-08-15T00:00:00Z entre serviços — é autodescritiva, ordena bem como texto, sobrevive a ser colada num registo e custa uns vinte bytes.
- Os segundos intercalares tornam errados os meus cálculos de duração?
- Tecnicamente sim, e na prática quase nunca o suficiente para contar. Subtrair duas marcas Unix dá a diferença do contador, que omite todo o segundo intercalar pelo meio. Em torno do segundo intercalar de 2016, o contador diz que passaram 86 400 segundos entre 2016-12-31T00:00:00Z e 2017-01-01T00:00:00Z, quando decorreram 86 401 segundos SI. Como só foram inseridos vinte e sete segundos intercalares, o erro máximo possível numa duração que abranja todo o período desde 1972 é de vinte e sete segundos — irrelevante para faturação, duração de sessão, expiração de cache, latência ao milissegundo ou qualquer medida de negócio. Torna-se relevante na telemetria de satélites, na física de alta precisão e na sequenciação financeira abaixo do segundo, e esses campos usam TAI ou tempo GPS, sem qualquer segundo intercalar. Há, contudo, um perigo prático muito maior: medir tempo decorrido com o relógio de parede. Correções NTP, migrações de máquinas virtuais e mudanças manuais podem mover um relógio de parede bem mais do que um segundo, para a frente ou para trás. Para medir durações use um relógio monótono — o que a sua linguagem expõe como algo do género performance.now ou um relógio estável — e reserve as marcas de relógio de parede para registar quando algo aconteceu.
- Devo guardar as marcas em UTC ou com fuso horário?
- Depende de se está a registar algo que aconteceu ou a agendar algo que vai acontecer. Para tudo o que é passado — uma linha de registo, uma encomenda, um pagamento, um assento de auditoria — guarde o instante em UTC e converta só ao mostrar. O instante é um facto e nunca muda; a forma como se representa na hora local de um utilizador é uma questão de apresentação que pode ser recalculada a qualquer momento. Para tudo o que está agendado no futuro, a UTC é a escolha errada, e é o caso que mais vezes falha. Uma reunião às 09:00 em Berlim no próximo novembro não é um instante: é uma hora de relógio de parede numa jurisdição. Se a Alemanha alterar as suas regras horárias até lá, o instante correto muda, e um valor UTC guardado hoje passaria a ser um compromisso à hora local errada. Guarde a data local, a hora local e o identificador de fuso IANA como Europe/Berlin, e resolva em instante quando precisar. Os governos alteram mesmo as regras de fuso, normalmente com poucas semanas de aviso, e a base tz é atualizada várias vezes por ano para as acompanhar.
Artigos que podem interessar-lhe
Todos os guias →Ferramentas relacionadas
Fontes
- The Open Group / IEEE — POSIX Base Specifications — Seconds Since the Epoch (the formula that excludes leap seconds)
- IETF — RFC 3339 — Date and Time on the Internet: Timestamps
- IERS — International Earth Rotation and Reference Systems Service — Bulletin C, leap second announcements and the TAI−UTC value
- BIPM — 27th General Conference on Weights and Measures (2022), Resolution 4 on the future of the leap second
- ITU — Recommendation ITU-R TF.460 — Standard-frequency and time-signal emissions, the definition of UTC and 23:59:60
- Oracle — MySQL Reference Manual — The DATE, DATETIME, and TIMESTAMP Types (TIMESTAMP ends 2038-01-19 03:14:07 UTC)
- MDN Web Docs — Date — the ±8,640,000,000,000,000 millisecond range of a JavaScript Date
Detetaste um erro neste artigo?