A aritmética de datas é mais difícil do que parece
Publicado a 06/06/2025 · 16 min de leitura · Calculadoras do dia a dia
Lena Hoffmann — Redatora de Ciência e Educação na OneKitly
Matemática · Física
Verificado a partir de 5 fontes
Pergunte quanto é 31 de janeiro mais um mês e não há resposta que a matemática lhe imponha. A maioria dos sistemas trunca para o fim do mês mais curto e devolve 28 de fevereiro de 2026 — ou 29 de fevereiro num ano bissexto como 2024. A aritmética ingénua em dias devolve outra coisa: somar 31 dias dá 3 de março, somar 30 dias dá 2 de março. As três defendem-se, e é precisamente esse o problema. Essa truncatura tem uma consequência que quase ninguém nota: somar meses não é invertível nem associativo. 31 de março menos um mês é 28 de fevereiro, e voltar a somar um mês dá 28 de março, não 31 de março. 31 de janeiro mais um mês mais um mês é 28 de março, mas 31 de janeiro mais dois meses é 31 de março. A aritmética horária parte-se noutra direção. Em Europe/Paris, 29 de março de 2026 dura 23 horas e 25 de outubro dura 25, pelo que somar 86 400 segundos a um compromisso das 9:00 de 28 de março aterra às 10:00 do dia seguinte. E a idade é uma comparação de calendário, não uma divisão: sobre todos os aniversários de 1930 a 2020, dias ÷ 365 erra a idade em 3,49 % dos casos. A regra que resolve tudo: faça aritmética de calendário em campos de calendário, aritmética de instantes em UTC, e nunca misture as duas.
"Um mês depois" não tem uma resposta única, e cada biblioteca de datas teve de escolher uma. Somar meses não é associativo nem invertível, um dia nem sempre tem 24 horas, e a idade não são os dias a dividir por 365,25.
Não há aritmética que imponha uma resposta
Somar um a um número não tem ambiguidade. Somar um mês a uma data tem, porque os meses não são uma unidade — são rótulos de comprimento desigual, de 28 a 31 dias, e o comprimento daquele em que aterra depende daquele de onde partiu. 31 de janeiro mais um mês tem de aterrar algures em fevereiro, e fevereiro não tem dia 31. Alguma coisa tem de ceder. A escolha quase universal é truncar: manter o mês, manter o ano e recuar o dia até ao último válido. Isso dá 28 de fevereiro de 2026 e 29 de fevereiro de 2024. O java.time em Java, a API Temporal do ECMAScript, a soma de intervalos do PostgreSQL e o dateutil em Python comportam-se todos assim, e fazem-no porque a alternativa é pior.
A alternativa é tratar um mês como um número fixo de dias. Escolha 30 e 31 de janeiro mais um mês passa a 2 de março de 2026; escolha 31 e passa a 3 de março. Ambas saltam fevereiro por inteiro, que é exatamente o que o utilizador que pede "um mês depois" não quer. A tabela acima aplica as três definições a cinco datas de partida, e só coincidem quando o dia do mês é suficientemente pequeno para existir em toda a parte. É essa a lição prática: todos os métodos são idênticos do dia 1 ao 28, e todos divergem algures nos dias 29, 30 e 31. Cerca de uma data em cada dez está na zona de perigo, e é por isso que o erro sobrevive tão facilmente aos testes.
Truncar custa-lhe a associatividade e a invertibilidade
Execute isto e veja desaparecer uma propriedade que dava por garantida. 31 de março de 2026 menos um mês trunca para 28 de fevereiro. Volte a somar um mês e obtém 28 de março — três dias aquém de onde partiu. Somar meses não é invertível: subtrair e depois somar não é a identidade. O mesmo defeito aparece como falha de associatividade. 31 de janeiro de 2026 mais um mês mais um mês é 28 de março, porque o valor intermédio foi truncado para 28 de fevereiro e a truncatura é definitiva. 31 de janeiro mais dois meses, calculado de uma só vez, é 31 de março. Duas expressões que deveriam valer o mesmo diferem em três dias.
Isto não é um erro de nenhuma biblioteca em particular — é uma consequência do calendário, e toda a biblioteca que trunca herda-o. A regra prática que daí decorre merece um post-it: nunca construa uma série mensal somando um mês ao resultado anterior. Some sempre n meses à data de âncora original. Uma subscrição que começa a 31 de janeiro e se renova por iteração vai derivar para o dia 28 e ficar lá para sempre; a mesma subscrição ancorada a 31 de janeiro e calculada como início + n meses cai a 28 de fevereiro, 31 de março, 30 de abril, 31 de maio — que é o que o cliente espera e o que o processador de pagamentos vai cobrar. O erro é invisível onze meses por ano e depois chega todo de uma vez.
Um dia não são 24 horas — Europe/Paris, março e outubro de 2026
Pegue nas regras reais da base tz em vez de uma suposição. Em 2026 a Europe/Paris passa de UTC+1 para UTC+2 a 29 de março e volta a 25 de outubro. Meça a distância entre a meia-noite local de 29 de março e a meia-noite local de 30 de março: é de 2026-03-28T23:00Z a 2026-03-29T22:00Z, ou seja 23 horas. Faça o mesmo à volta de 25 de outubro e obtém de 2026-10-24T22:00Z a 2026-10-25T23:00Z, ou seja 25 horas. O dia do calendário e o dia de 86 400 segundos são objetos diferentes, e duas vezes por ano separam-se de forma visível. A America/New_York faz o mesmo noutras datas — 23 horas a 8 de março de 2026 e 25 horas a 1 de novembro.
A consequência cai sobre marcações reais. Um horário das 9:00 de 28 de março de 2026 em Paris é o instante 2026-03-28T08:00Z. Some exatamente 24 horas decorridas e obtém 2026-03-29T08:00Z, que em Paris se lê 10:00 — a reunião andou uma hora para a frente. Faça-o em outubro e anda uma hora para trás: 9:00 de 24 de outubro mais 24 horas são 8:00 de 25 de outubro. Nenhuma das duas é o que significa "à mesma hora amanhã". "À mesma hora amanhã" é uma operação de calendário: incrementar o campo data, manter o campo de relógio de parede e voltar a resolver o instante contra o fuso. Somar uma duração é uma operação de física. Coincidem 363 dias por ano, o que é exatamente o suficiente para a falha parecer aleatória.
O transbordo silencioso: 30 de fevereiro não gera erro nenhum
Em JavaScript, new Date(2026, 1, 30) não lança exceção. Devolve 2 de março de 2026. O construtor aceita qualquer inteiro e normaliza transportando o excesso para o mês seguinte, pelo que um campo de dia 30 num fevereiro de 28 dias passa discretamente a dia 2 do mês seguinte. A mesma normalização transforma o índice de mês 12 em janeiro do ano seguinte e um dia 0 no último dia do mês anterior — que é o truque por trás do idioma habitual para "dias deste mês", new Date(y, m, 0).getDate(). É um comportamento útil quando o quer e uma corrupção silenciosa de dados quando não, e nada no valor devolvido lhe diz em que caso está.
É por isto que validar uma data é mais do que verificar que o analisador não se queixou. Um formulário que aceita 30/02/2026 e guarda 2026-03-02 perdeu o erro do utilizador em vez de o reportar, e o registo diz agora algo que o utilizador nunca escreveu. A verificação defensiva cabe numa linha: construa a data e depois confirme que o ano, o mês e o dia devolvidos são os três que forneceu. Se não forem, a entrada não era uma data real. A outra metade da defesa é conhecer a duração de cada mês antes sequer de construir a entrada — 31, 28 ou 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 — que é o que a calculadora de dias do mês responde para o ano que indicar.
A idade é uma comparação, não uma divisão
O atalho tentador é contar os dias decorridos e dividir por 365,25, com o argumento de que o ano médio dura 365,25 dias. Teste-o. Alguém nascido a 23 de agosto de 2000, consultado a 22 de agosto de 2026, viveu 9 495 dias. Divida por 365 e obtém 26,0137, cuja parte inteira é 26 — mas a pessoa tem 25 e o aniversário é amanhã. Divida por 365,25 e obtém 25,9959, cuja parte inteira é 25, e aí acerta. Então percorra todo o espaço em vez de um exemplo: cada data de nascimento de 1 de janeiro de 1930 a 1 de janeiro de 2020, avaliada a 22 de agosto de 2026, são 32 873 datas. Dias ÷ 365 dá a idade errada em 1 148 delas, ou 3,49 %. Dias ÷ 365,25 é muito melhor mas ainda erra em 44, ou 0,13 %.
As falhas residuais são as mais cruéis: caem no próprio dia do aniversário. Alguém nascido a 22 de agosto de 1932 viveu 34 333 dias até 22 de agosto de 2026, e 34 333 ÷ 365,25 = 93,9986, pelo que a divisão diz 93 na manhã em que faz 94. Nenhum afinamento do divisor corrige isto, porque nenhum divisor único o consegue, e é esse todo o ponto — o calendário não é uma escala uniforme. O algoritmo correto não tem divisão nenhuma. Subtraia o ano de nascimento ao ano atual e depois subtraia mais um se o mês e o dia atuais ainda não tiverem chegado ao mês e ao dia de nascimento. Três comparações de inteiros, exatas em toda a parte, e nunca precisa de saber quanto dura um ano.
A regra: campos de calendário para o calendário, UTC para os instantes
Quase todos os erros de datas são um de dois enganos. Ou uma pergunta de calendário foi respondida com tempo decorrido — "um mês" transformado em 30 dias, "amanhã" em 86 400 segundos, "a idade" numa divisão — ou uma pergunta de instante foi respondida em campos locais, e um carimbo temporal guardado deslocou-se quando o desvio mudou. A correção é decidir, antes de escrever uma linha, que tipo de grandeza está a manipular. Datas de renovação, aniversários, prazos, períodos de faturação e horários de abertura são grandezas de calendário: guarde-as como ano, mês, dia e hora de relógio de parede com um fuso nomeado, e faça a aritmética sobre esses campos. Tempos-limite, ordenação de registos, expiração de cache, limites de taxa e durações são grandezas de instante: guarde-as como carimbos UTC e some segundos.
Onde os dois têm de se encontrar — um lembrete às 9:00 hora local, enviado por um servidor que só entende instantes — converta na fronteira, e só na fronteira. Faça primeiro o passo de calendário no fuso nomeado do utilizador, resolva o resultado para um instante UTC uma única vez e entregue esse instante ao agendador. Guardar o desvio em vez do nome do fuso parte-se assim que as regras políticas mudam, o que acontece várias vezes por ano; a base tz publica versões precisamente porque os governos não param de mexer nas suas transições. E nunca guarde um compromisso local futuro apenas como instante UTC, porque se as regras desse fuso forem alteradas antes da data chegar, o instante que guardou deixará de corresponder às 9:00 da manhã de quem quer que seja.
| Data de partida | + 1 mês, truncado | + 30 dias | + 31 dias |
|---|---|---|---|
| 31 de janeiro de 2026 | 28 de fevereiro de 2026 | 2 de março de 2026 | 3 de março de 2026 |
| 31 de janeiro de 2024 (ano bissexto) | 29 de fevereiro de 2024 | 1 de março de 2024 | 2 de março de 2024 |
| 31 de março de 2026 | 30 de abril de 2026 | 30 de abril de 2026 | 1 de maio de 2026 |
| 31 de agosto de 2026 | 30 de setembro de 2026 | 30 de setembro de 2026 | 1 de outubro de 2026 |
| 30 de novembro de 2026 | 30 de dezembro de 2026 | 30 de dezembro de 2026 | 31 de dezembro de 2026 |
Perguntas frequentes
- Quanto é 31 de janeiro mais um mês?
- Depende da definição que o seu sistema usa, e não há resposta que a matemática imponha. A truncatura — o comportamento do java.time, da API Temporal, dos intervalos do PostgreSQL e do dateutil em Python — mantém o mês e o ano e recua o dia até ao último válido, dando 28 de fevereiro de 2026 ou 29 de fevereiro de 2024. A aritmética de dias fixos dá outra coisa: mais 30 dias é 2 de março de 2026 e mais 31 dias é 3 de março. A truncatura é quase sempre a escolha certa para datas viradas para pessoas, porque quem pede "um mês depois" quer a data correspondente no mês seguinte, não um número fixo de dias. Se fatura, contrata ou agenda, indique nas condições que regra usa, porque os clientes reparam quando uma subscrição de 31 de janeiro se renova a 28 de fevereiro.
- Porque é que subtrair um mês e voltar a somá-lo não devolve a data original?
- Porque a truncatura destrói informação e nada a pode restaurar. 31 de março de 2026 menos um mês tem de aterrar em fevereiro, fevereiro não tem dia 31, portanto o resultado é truncado para 28 de fevereiro. Esse resultado já não se lembra de que veio de um dia 31. Somar um mês a 28 de fevereiro dá então 28 de março, e está três dias aquém de onde começou. O mesmo mecanismo custa-lhe a associatividade: 31 de janeiro mais um mês mais um mês é 28 de março, enquanto 31 de janeiro mais dois meses num só passo é 31 de março. A consequência prática é uma regra a aplicar em toda a parte: construa calendários recorrentes somando n meses à data de âncora original, nunca iterando mês a mês a partir do resultado anterior. A iteração deixa uma única truncatura propagar-se para sempre.
- Um dia tem sempre 24 horas?
- Não, não enquanto dia de calendário local. Em Europe/Paris em 2026, 29 de março dura 23 horas e 25 de outubro dura 25, porque o fuso passa de UTC+1 a UTC+2 e volta. Medidos como instantes, a meia-noite local de 29 de março é 2026-03-28T23:00Z e a de 30 de março é 2026-03-29T22:00Z — 23 horas de diferença. A America/New_York faz o mesmo a 8 de março e 1 de novembro de 2026. Algumas transições nem sequer são de horas inteiras; a ilha de Lord Howe desloca-se 30 minutos. Os fusos sem horário de verão têm dias de 24 horas todo o ano, mas não pode assumir que os seus utilizadores estão num deles. O hábito seguro é tratar "um dia" em sentido de calendário como um incremento do campo data, resolvido contra um fuso nomeado, e reservar os 86 400 segundos para trabalho real de tempo decorrido, feito em UTC.
- Como devo calcular a idade de alguém?
- Com comparações, nunca com uma divisão. Subtraia o ano de nascimento ao ano atual e depois subtraia mais um se o mês atual for anterior ao mês de nascimento, ou se os meses coincidirem e o dia atual for anterior ao dia de nascimento. Isso é exato para qualquer data. As divisões falham de forma mensurável: sobre todos os aniversários de 1930 a 2020 avaliados a 22 de agosto de 2026 — 32 873 datas — dias decorridos ÷ 365 dá a idade errada em 3,49 % dos casos e ÷ 365,25 em 0,13 %. Pior, as falhas residuais concentram-se no próprio dia do aniversário, o único que as pessoas verificam. Alguém nascido a 22 de agosto de 1932 viveu 34 333 dias até 22 de agosto de 2026, e 34 333 ÷ 365,25 = 93,9986, pelo que a divisão reporta 93 na manhã em que faz 94. Os nascidos a 29 de fevereiro exigem uma decisão de política à parte, porque as jurisdições divergem sobre se o aniversário legal num ano comum é 28 de fevereiro ou 1 de março.
- Porque é que o meu formulário aceita 30 de fevereiro sem se queixar?
- Porque a maioria dos construtores de datas normaliza em vez de validar. Em JavaScript, new Date(2026, 1, 30) devolve 2 de março de 2026 sem erro: o campo de dia transborda e o excesso é transportado para o mês seguinte. A mesma regra transforma um índice de mês 12 em janeiro do ano seguinte e um dia 0 no último dia do mês anterior, e é por isso que new Date(y, m, 0).getDate() é o idioma habitual para a duração de um mês. Nada no valor devolvido distingue um transbordo deliberado de uma gralha. A defesa é uma verificação de ida e volta: construa a data e depois confirme que o ano, o mês e o dia que lê de volta são os três que escreveu. Se diferirem, rejeite a entrada. Fazê-lo na fronteira sai muito mais barato do que descobrir mais tarde que uma coluna da base de dados contém uma data que ninguém escreveu.
- Devo guardar as datas em UTC ou em hora local?
- Depende do que o valor significa, e responder "sempre UTC" causa tantos erros quantos evita. Tudo o que regista quando algo aconteceu — uma linha de registo, um pagamento, uma expiração de cache, uma janela de limite de taxa — é um instante, e os instantes vão em UTC. Tudo o que regista quando algo deve acontecer no dia de alguém — um lembrete às 9:00, uma janela de entrega, o horário de uma loja, uma reunião recorrente — é um valor de calendário, e guardá-lo como instante UTC nu é um erro à espera de uma mudança de regras. Os governos alteram as regras do horário de verão várias vezes por ano, e a base tz publica versões para as acompanhar; um compromisso futuro congelado como instante afastar-se-á da hora de parede pretendida se o seu fuso for alterado. Guarde esses como data local, hora local e nome de fuso IANA, e resolva para instante apenas no momento de agir.
Artigos que podem interessar-lhe
Todos os guias →Ferramentas relacionadas
Fontes
- IANA — Time Zone Database (tz database)
- ISO — ISO 8601-1:2019 — Date and time: representations for information interchange
- IETF — RFC 3339 — Date and Time on the Internet: Timestamps
- IETF — RFC 5545 — Internet Calendaring and Scheduling Core Object Specification (iCalendar)
- Ecma International — ECMAScript Temporal — calendar and duration arithmetic
Detetaste um erro neste artigo?