Pular para o conteúdo
Cursos

DevClub

LógicaFront-endBack-endMobile

IA Club

IA na prática
Estudar programaçãoEstudar IA
LiçãoIniciantecódigo testado

Datas em JavaScript: formatar data dd/mm/aaaa

Por que getMonth devolve 2 em março, por que a data ISO volta um dia no fuso de Brasília e como formatar em dd/mm/aaaa sem cair no Invalid Date.

Rodolfo Mori6 min de leitura

Um objeto Date representa um instante no tempo como a quantidade de milissegundos desde 1º de janeiro de 1970 em UTC. Ele não guarda “horário de São Paulo” ou “formato brasileiro”; fuso e formato entram quando o programa lê ou exibe esse instante.

Pense numa transmissão assistida em cidades diferentes. O acontecimento é um só, mas cada relógio mostra uma hora local. O timestamp é o acontecimento; Intl.DateTimeFormat e toLocaleDateString são os relógios. No JavaScript, o mesmo valor pode aparecer como 14:30 em São Paulo e 13:30 em Manaus sem que o instante tenha mudado.

Para mostrar apenas a data em português do Brasil, o caminho curto é data.toLocaleDateString('pt-BR'). Antes de confiar na saída, porém, precisamos tratar três armadilhas: mês base zero, string ISO curta interpretada em UTC e objeto Invalid Date que não lança erro sozinho. Todos os exemplos rodam em America/Sao_Paulo.

Criar uma data e ler cada pedaço

js
const compra = new Date(2026, 2, 15, 14, 30);

console.log(compra.toString());
console.log('getFullYear:', compra.getFullYear());
console.log('getMonth:', compra.getMonth());
console.log('getDate:', compra.getDate());
console.log('getDay:', compra.getDay());
Sun Mar 15 2026 14:30:00 GMT-0300 (Brasilia Standard Time) getFullYear: 2026 getMonth: 2 getDate: 15 getDay: 0

Eu escrevi 2 e recebi março. O mês é base zero: janeiro é 0, dezembro é 11. Não é capricho da linguagem — é herança direta da struct tm do C, que o JavaScript copiou em 1995 e nunca mais conseguiu mudar sem quebrar a web.

Só o mês tem essa regra. O dia do mês é base um, o ano é o ano. E getDay não é irmão de getDate: ele devolve o dia da semana, também base zero, começando no domingo — daí o 0 para 15 de março de 2026.

método devolve faixa exemplo em 15/03/2026
getDate dia do mês 1 a 31 15
getDay dia da semana 0 (dom) a 6 (sáb) 0
getMonth mês 0 (jan) a 11 (dez) 2
getFullYear ano com quatro dígitos 2026

Escrever getMonth() + 1 na hora de exibir e mes - 1 na hora de construir é a disciplina que evita 90% dos bugs de data em código brasileiro.

A string ISO curta volta um dia

Esta é a armadilha que mais aparece em bug report de cliente:

js
const soData = new Date('2026-03-15');
const dataComHora = new Date('2026-03-15T00:00:00');

console.log('2026-03-15        ->', soData.getDate(), soData.toString());
console.log('2026-03-15T00:00  ->', dataComHora.getDate(), dataComHora.toString());
console.log('mesmo instante?', soData.getTime() === dataComHora.getTime());
2026-03-15 -> 14 Sat Mar 14 2026 21:00:00 GMT-0300 (Brasilia Standard Time) 2026-03-15T00:00 -> 15 Sun Mar 15 2026 00:00:00 GMT-0300 (Brasilia Standard Time) mesmo instante? false

A mesma data virou dia 14 num caso e dia 15 no outro. A especificação manda interpretar a forma só com data como meia-noite em UTC; a forma com hora e sem fuso, como meia-noite local. Meia-noite em Londres é 21h do dia anterior em São Paulo — e o getDate obedece ao relógio local.

Formatar em dd/mm/aaaa

O caminho curto usa a localização do próprio runtime:

js
const entrega = new Date(2026, 2, 15, 14, 30);

console.log(entrega.toLocaleDateString('pt-BR'));
console.log(entrega.toLocaleString('pt-BR'));
console.log(entrega.toLocaleDateString('pt-BR', { dateStyle: 'long' }));
console.log(entrega.toLocaleDateString('pt-BR', { weekday: 'long', day: '2-digit', month: 'long' }));
15/03/2026 15/03/2026, 14:30:00 15 de março de 2026 domingo, 15 de março

Três formatos diferentes sem uma única concatenação, e com o “de março” em português correto. toLocaleDateString só a data, toLocaleString data e hora.

Quando você precisa do controle total — montar um nome de arquivo, por exemplo — a versão manual é curta e vale conhecer:

js
function paraDDMMAAAA(data) {
  const dia = String(data.getDate()).padStart(2, '0');
  const mes = String(data.getMonth() + 1).padStart(2, '0');
  const ano = data.getFullYear();
  return `${dia}/${mes}/${ano}`;
}

console.log(paraDDMMAAAA(new Date(2026, 0, 5)));
console.log(paraDDMMAAAA(new Date(2026, 11, 31)));
05/01/2026 31/12/2026

O padStart(2, '0') é o que transforma 5 em 05. Sem ele, metade das datas do ano sai com um dígito e a coluna da tabela dança.

Aproveite para reparar no getMonth() + 1: sem ele, janeiro sairia como 00 e dezembro como 11.

getDay na prática

js
const DIAS = ['domingo', 'segunda', 'terça', 'quarta', 'quinta', 'sexta', 'sábado'];
const pedidos = [
  { numero: 8412, criadoEm: new Date(2026, 2, 15) },
  { numero: 8413, criadoEm: new Date(2026, 2, 16) },
  { numero: 8414, criadoEm: new Date(2026, 2, 21) },
];

for (const pedido of pedidos) {
  const d = pedido.criadoEm;
  console.log(`#${pedido.numero} dia ${d.getDate()} · ${DIAS[d.getDay()]} · mês ${d.getMonth() + 1}`);
}
#8412 dia 15 · domingo · mês 3 #8413 dia 16 · segunda · mês 3 #8414 dia 21 · sábado · mês 3

O array DIAS começa no domingo porque getDay() começa no domingo. Trocar a ordem “para começar na segunda” e esquecer de ajustar o índice é um erro que passa despercebido até alguém reclamar que o pedido de sábado apareceu como sexta.

Erros comuns: Invalid Date

Passe uma data no formato brasileiro para o construtor e observe o que acontece — ou melhor, o que não acontece:

js
const digitadaPeloCliente = '15/03/2026';
const entrega = new Date(digitadaPeloCliente);

console.log(entrega.toString());
console.log(entrega.getFullYear());
console.log(entrega.toLocaleDateString('pt-BR'));
Invalid Date NaN Invalid Date

Nenhuma exceção. Você recebeu um objeto Date legítimo — typeof diz 'object', instanceof Date diz true — só que ele carrega NaN no lugar do instante. O programa segue, o valor contamina tudo que encosta nele, e o erro só estoura mais tarde, longe da causa:

js
const entrega = new Date('15/03/2026');

console.log(entrega.toISOString());
file:///private/tmp/checkout/data-invalida-quebra.mjs:3 console.log(entrega.toISOString()); ^ RangeError: Invalid time value at Date.toISOString (<anonymous>) at file:///private/tmp/checkout/data-invalida-quebra.mjs:3:21 Node.js v24.16.0

RangeError: Invalid time value é a forma tardia de o programa dizer que a data já estava quebrada lá atrás, quando alguém digitou 15/03/2026 num campo de texto. O construtor Date não entende dd/mm/aaaa: ele até tenta o formato americano mm/dd/aaaa, e por isso 03/15/2026 funcionaria — o que é pior ainda, porque 05/03/2026 seria lido como 3 de maio sem reclamar de nada.

A saída é converter você mesmo, validando:

js
function lerDataBrasileira(texto) {
  const partes = /^(\d{2})\/(\d{2})\/(\d{4})$/.exec(texto);
  if (!partes) return null;

  const [, dia, mes, ano] = partes.map(Number);
  const data = new Date(ano, mes - 1, dia);

  const valida =
    data.getDate() === dia && data.getMonth() === mes - 1 && data.getFullYear() === ano;
  return valida ? data : null;
}

console.log(lerDataBrasileira('15/03/2026')?.toLocaleDateString('pt-BR'));
console.log(lerDataBrasileira('31/02/2026'));
console.log(lerDataBrasileira('2026-03-15'));
15/03/2026 null null

A segunda checagem existe porque o construtor normaliza silenciosamente: 31 de fevereiro vira 3 de março, sem erro nenhum. Comparar o que saiu com o que entrou é o único jeito de pegar isso. O ?. da primeira linha é o operador da lição sobre optional chaining, aqui evitando quebrar quando a função devolve null.

Somar dias e medir prazo

setDate aceita valores fora da faixa e acerta o calendário sozinho, incluindo ano bissexto e virada de ano:

js
function somarDias(data, dias) {
  const nova = new Date(data);
  nova.setDate(nova.getDate() + dias);
  return nova;
}

const compra = new Date(2026, 1, 25);

console.log(compra.toLocaleDateString('pt-BR'));
console.log(somarDias(compra, 3).toLocaleDateString('pt-BR'));
console.log(somarDias(compra, 10).toLocaleDateString('pt-BR'));
console.log(somarDias(new Date(2026, 11, 28), 7).toLocaleDateString('pt-BR'));
25/02/2026 28/02/2026 07/03/2026 04/01/2027

Repare no new Date(data) dentro da função: sem essa cópia, setDate mutaria a data original e o chamador levaria um susto. Date é objeto, e a regra de referência é a mesma de qualquer objeto — assunto de variáveis em JavaScript.

Para a diferença entre duas datas, subtrair devolve milissegundos:

js
const UM_DIA = 24 * 60 * 60 * 1000;

const compra = new Date(2026, 2, 15);
const entrega = new Date(2026, 3, 2);

console.log(entrega - compra);
console.log(Math.round((entrega - compra) / UM_DIA), 'dias');

const sabado = new Date(2018, 10, 3);
const segunda = new Date(2018, 10, 5);
console.log((segunda - sabado) / UM_DIA);
console.log(Math.round((segunda - sabado) / UM_DIA), 'dias');
1555200000 18 dias 1.9583333333333333 2 dias

O último par é o motivo de o Math.round estar ali. Em 4 de novembro de 2018 começou o horário de verão em São Paulo: aquele dia teve 23 horas, e a conta crua deu 1,958. Com Math.floor, o prazo de dois dias apareceria como um.

Guardar em UTC, exibir em pt-BR

js
const criadoEm = new Date(2026, 2, 15, 14, 30);

const paraOBanco = criadoEm.toISOString();
console.log('salvo:', paraOBanco);

const doBanco = new Date(paraOBanco);
console.log('lido :', doBanco.toLocaleString('pt-BR'));
console.log('igual?', doBanco.getTime() === criadoEm.getTime());
salvo: 2026-03-15T17:30:00.000Z lido : 15/03/2026, 14:30:00 igual? true

Esse é o ciclo correto: o Z no fim diz “isto é UTC”, a volta é exata, e a apresentação local acontece só na última linha. É também o formato que o JSON.stringify gera sozinho, como mostra a lição de JSON em JavaScript.

Quando o fuso de exibição não é o da máquina — um painel de logística que mostra sempre o horário de Manaus, por exemplo — use um formatador explícito:

js
const formatador = new Intl.DateTimeFormat('pt-BR', {
  dateStyle: 'short',
  timeStyle: 'short',
  timeZone: 'America/Sao_Paulo',
});

const criadoEm = new Date('2026-03-15T17:30:00.000Z');

console.log(formatador.format(criadoEm));
console.log(new Intl.DateTimeFormat('pt-BR', { timeZone: 'America/Manaus', timeStyle: 'short' }).format(criadoEm));
console.log(criadoEm.toISOString());
15/03/2026, 14:30 13:30 2026-03-15T17:30:00.000Z

O mesmo instante aparece como 14:30 em São Paulo e 13:30 em Manaus, e o valor guardado não mudou. Criar o Intl.DateTimeFormat uma vez e reaproveitar também é mais rápido que chamar toLocaleDateString dentro de um laço grande — a montagem do formatador é a parte cara.

Três regras para não sofrer

  • Guarde instante, exiba local. Para acontecimentos com hora definida, use ISO em UTC no banco e na API; formate apenas na camada de tela. Uma data civil sem horário, como aniversário ou vencimento, é outro tipo de dado e pode ser guardada como YYYY-MM-DD para não mudar de dia ao trocar de fuso.
  • Valide toda data que vem de texto. Number.isNaN(data.getTime()) é o teste; Invalid Date não lança nada sozinho.
  • Ao usar getMonth ou o construtor numérico, ajuste o mês. Some 1 na saída e subtraia 1 na entrada, porque essa API numera janeiro como zero.

Faça um teste com o mesmo ISO em dois fusos: crie new Date('2026-03-15T17:30:00.000Z') e formate explicitamente para America/Sao_Paulo e America/Manaus. As horas devem ser diferentes e getTime() deve continuar igual. Depois teste uma entrada inválida e confirme que Number.isNaN(data.getTime()) a detecta antes da formatação.

A trilha de JavaScript continua em classes, onde Date deixa de ser um caso especial e vira só mais um objeto com métodos no protótipo. O guia completo de JavaScript tem a ordem inteira.

  • datas
  • date
  • intl
  • fuso horario
  • formatacao

Perguntas frequentes

Por que getMonth devolve 2 quando a data é março?
Porque o mês é um índice base zero, herdado da biblioteca de tempo do C dos anos 1970 — janeiro é 0 e dezembro é 11. O dia do mês, por outro lado, é base um. Some 1 ao getMonth sempre que for exibir, e subtraia 1 sempre que for construir.
Preciso de uma biblioteca como date-fns ou Day.js?
Para formatar e exibir, não: Intl.DateTimeFormat e toLocaleDateString resolvem em pt-BR e já vêm no runtime. Para aritmética de calendário pesada — semanas úteis, recorrência, diferença em meses — uma biblioteca economiza bugs de horário de verão.
Devo guardar a data em qual formato no banco?
Em UTC, no formato ISO 8601 que o toISOString produz. Formato brasileiro é apresentação, não armazenamento: converta para dd/mm/aaaa só na hora de mostrar na tela, com o fuso do usuário.
Como sei se uma data é inválida?
Teste com Number.isNaN(data.getTime()). O objeto existe e é uma Date de verdade mesmo quando é inválido; comparar com null ou undefined não detecta nada. Imprimir mostra Invalid Date, mas o código precisa do teste.

Dúvidas e comentários

Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.

Todo o código deste artigo foi executado em Node 24.16.0, e as saídas exibidas são as reais — como produzimos este conteúdo.

Fontes consultadas

  1. MDN — Date — developer.mozilla.org
  2. MDN — Intl.DateTimeFormat — developer.mozilla.org

Continue por aqui