1

31 de janeiro + 1 mês: o bug que encontrei comparando JavaScript e Python

Enquanto eu desenvolvia uma calculadora de datas para o meu site, notei uma coisa estranha nos testes: a mesma conta dava resultados diferentes em JavaScript e em Python.

A conta era simples: 31 de janeiro de 2026 + 1 mês.

  • Python (relativedelta): 28/02/2026
  • JavaScript (setMonth): 03/03/2026

Fevereiro não tem dia 31, e cada linguagem resolve isso de um jeito. Fui olhar outras ferramentas:

RespostaQuem dá
28/02 (último dia do mês)Excel, Python, Java, C#, date-fns, PostgreSQL, MySQL
03/03 (o dia "transborda")JavaScript, PHP
01/03 (dia seguinte)Código Civil, art. 132, para prazos

Imagine o sistema gerando a cobrança com uma regra e a planilha do financeiro conferindo com outra. O vencimento não bate, e ninguém entende por quê.

Por que o JavaScript dá 3 de março

O setMonth muda o mês e mantém o dia 31. Como 31 de fevereiro não existe, o Date conta os dias que sobram para março:

const d = new Date(Date.UTC(2026, 0, 31));
d.setUTCMonth(1);
console.log(d.toISOString().slice(0, 10)); // 2026-03-03

O modify('+1 month') do PHP faz o mesmo. Não é bug: está na documentação. Só não é o que a maioria espera.

Como resolvi

Escolhi a regra mais comum, a do último dia do mês, e escrevi a função sem depender do setMonth:

function somarMeses(data, meses) {
  const [a, m, d] = data.split('-').map(Number);
  const total = a * 12 + (m - 1) + meses;
  const ano = Math.floor(total / 12);
  const mes = (total % 12) + 1;
  const ultimoDia = new Date(Date.UTC(ano, mes, 0)).getUTCDate();
  const dia = Math.min(d, ultimoDia);
  return `${ano}-${String(mes).padStart(2, '0')}-${String(dia).padStart(2, '0')}`;
}

somarMeses('2026-01-31', 1); // 2026-02-28
somarMeses('2024-01-31', 1); // 2024-02-29

O Date.UTC(ano, mes, 0) pede o "dia 0" do mês seguinte, que é o último dia do mês desejado. Depois testei a função contra o relativedelta em centenas de datas sorteadas, e o resultado foi sempre igual.

O que aprendi

  1. Antes de somar meses, decida a regra e use a mesma no sistema, na planilha e no banco.
  2. Evite setMonth e modify('+1 month') para datas de calendário.
  3. "Vence em 30 dias" e "vence em 1 mês" não são a mesma coisa: a partir de 1º de fevereiro, dão 3 e 1º de março.
  4. Para prazos legais, o Código Civil manda usar o dia seguinte.

Você já tropeçou nisso? Conta nos comentários como resolveu.

Carregando publicação patrocinada...