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.
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
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());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:
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());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:
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' }));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:
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)));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
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}`);
}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:
const digitadaPeloCliente = '15/03/2026';
const entrega = new Date(digitadaPeloCliente);
console.log(entrega.toString());
console.log(entrega.getFullYear());
console.log(entrega.toLocaleDateString('pt-BR'));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:
const entrega = new Date('15/03/2026');
console.log(entrega.toISOString());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:
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'));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:
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'));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:
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');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
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());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:
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());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-DDpara não mudar de dia ao trocar de fuso. - Valide toda data que vem de texto.
Number.isNaN(data.getTime())é o teste;Invalid Datenão lança nada sozinho. - Ao usar
getMonthou 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.
Perguntas frequentes
Por que getMonth devolve 2 quando a data é março?
Preciso de uma biblioteca como date-fns ou Day.js?
Devo guardar a data em qual formato no banco?
Como sei se uma data é inválida?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
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
- MDN — Date — developer.mozilla.org
- MDN — Intl.DateTimeFormat — developer.mozilla.org


