Trabalhando com datas
Nesta aula, você aprenderá a trabalhar com datas em JavaScript, explorando o objeto Date nativo, suas principais limitações e como formatar datas de forma eficiente. Além disso, apresentaremos a proposta da nova API Temporal, que promete revolucionar a manipulação de datas no futuro.
Trabalhar com datas é uma das tarefas mais comuns e, ao mesmo tempo, uma das mais problemáticas em JavaScript. O objeto Date nativo, embora poderoso, carrega consigo décadas de decisões de design que hoje são consideradas confusas e propensas a erros. Nesta aula, vamos explorar o Date a fundo, entender suas limitações, aprender a formatar datas corretamente e vislumbrar o futuro com a nova API Temporal, que está em processo de padronização.
Se você já tentou manipular datas em JavaScript, provavelmente já se deparou com comportamentos estranhos, como meses começando em 0, fuso horário local vs UTC, ou a dificuldade de formatar datas de forma personalizada. Vamos desmistificar esses pontos e fornecer soluções práticas que você pode usar nos seus projetos hoje.
Objeto Date e suas dores
O objeto Date é a única forma nativa de representar datas e horas em JavaScript. Ele é baseado no padrão Unix (milissegundos desde 1 de janeiro de 1970, UTC). Apesar de sua utilidade, ele apresenta várias peculiaridades que podem causar confusão.
Um dos primeiros pontos de atenção é que os meses são indexados a partir de 0 (janeiro = 0, fevereiro = 1, etc.). Isso contrasta com o dia do mês, que começa em 1. Essa inconsistência é fonte de muitos bugs, especialmente para quem está acostumado com outros formatos. Além disso, o objeto Date é mutável, o que significa que métodos como setHours() alteram a data original, podendo causar efeitos colaterais indesejados.
Outro problema é a questão do fuso horário. O Date armazena internamente o instante em UTC, mas a maioria dos métodos (como getHours() e getMinutes()) retornam valores no fuso horário local do sistema. Isso pode gerar inconsistências quando se trabalha com datas em diferentes regiões, exigindo cuidado extra ao serializar ou comparar datas.
Vejamos um exemplo de como criar e manipular datas:
// Criando uma data específica (ano, mês-1, dia, hora, minuto, segundo, ms)
const data = new Date(2025, 0, 15, 10, 30, 0); // 15 de janeiro de 2025, 10:30
console.log(data.toString()); // "Wed Jan 15 2025 10:30:00 GMT-0300 (Horário Padrão de Brasília)"
// Obtendo o mês (0-11)
console.log(data.getMonth()); // 0 (janeiro)
// Obtendo o dia do mês (1-31)
console.log(data.getDate()); // 15
// Obtendo o ano
console.log(data.getFullYear()); // 2025
// Mutabilidade: altera a data original
const outraData = new Date(2025, 0, 15);
outraData.setDate(20); // agora é 20 de janeiro
console.log(outraData.getDate()); // 20
Para mitigar a mutabilidade, é comum criar cópias usando new Date(data.getTime()) ou new Date(data). Também é importante lembrar que, ao comparar datas, a conversão implícita para número pode causar confusão. Por exemplo, data1 == data2 compara as referências, não os valores. Para comparação correta, use getTime() ou operadores de comparação (que convertem para número).
const data1 = new Date(2025, 0, 15);
const data2 = new Date(2025, 0, 15);
console.log(data1 == data2); // false (são objetos diferentes)
console.log(data1.getTime() === data2.getTime()); // true (mesmo instante)
console.log(data1 < data2); // false (comparação numérica)
Outra dor comum é a diferença entre UTC e local. Para evitar problemas, é recomendável usar métodos UTC, como getUTCFullYear(), getUTCMonth(), etc., quando você precisa de consistência entre servidores e clientes.
Formatação
Formatar datas é uma necessidade frequente em aplicações. O JavaScript oferece algumas opções nativas, mas elas são limitadas. O método toString() retorna uma representação completa, mas muitas vezes queremos um formato específico, como dd/mm/aaaa ou dd de mês de aaaa.
O método toLocaleDateString() permite formatar de acordo com o locale, mas a personalização é restrita às opções suportadas. Por exemplo:
const data = new Date(2025, 4, 25); // 25 de maio de 2025
console.log(data.toLocaleDateString('pt-BR')); // "25/05/2025"
console.log(data.toLocaleDateString('pt-BR', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' })); // "domingo, 25 de maio de 2025"
No entanto, para formatos mais customizados, como 2025-05-25 (ISO), é necessário escrever manualmente:
function formatarISO(data) {
const ano = data.getFullYear();
const mes = String(data.getMonth() + 1).padStart(2, '0');
const dia = String(data.getDate()).padStart(2, '0');
return `${ano}-${mes}-${dia}`;
}
console.log(formatarISO(data)); // "2025-05-25"
Essa abordagem manual é propensa a erros, especialmente com fuso horário. Uma alternativa é usar toISOString(), que já retorna no formato ISO 8601, mas sempre em UTC:
console.log(data.toISOString()); // "2025-05-25T00:00:00.000Z"
Para aplicações mais complexas, bibliotecas como date-fns ou Moment.js (agora legado) oferecem funções de formatação poderosas. No entanto, para projetos modernos, é recomendável usar Intl.DateTimeFormat, que é nativo e tem bom suporte a locales e opções de formatação.
const formatador = new Intl.DateTimeFormat('pt-BR', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
timeZone: 'America/Sao_Paulo'
});
console.log(formatador.format(data)); // "25/05/2025 21:00" (exemplo)
O Intl também permite formatar datas relativas, como "há 5 dias", usando Intl.RelativeTimeFormat.
Bibliotecas modernas (Temporal, introdução)
Diante das dificuldades do objeto Date, a comunidade e o TC39 (comitê que padroniza o JavaScript) estão desenvolvendo uma nova API chamada Temporal. Ela visa fornecer uma manipulação de datas e horas mais segura, imutável e consistente, resolvendo muitos dos problemas existentes.
O Temporal oferece tipos como Temporal.PlainDate (data sem hora), Temporal.PlainTime (hora sem data), Temporal.PlainDateTime (data e hora sem fuso), Temporal.ZonedDateTime (data e hora com fuso), entre outros. A API é imutável, ou seja, métodos como with() retornam novos objetos, evitando efeitos colaterais.
Um exemplo de uso futuro:
// Exemplo hipotético (Temporal ainda não está disponível em todos os ambientes)
const data = Temporal.PlainDate.from('2025-05-25');
const novaData = data.with({ day: 30 });
console.log(novaData.toString()); // "2025-05-30"
Embora o Temporal ainda esteja em estágio de proposta (Stage 3), já existem polyfills e bibliotecas que implementam parte de sua funcionalidade, como @js-temporal/polyfill. É uma boa ideia acompanhar seu progresso, pois, quando for oficialmente adotado, simplificará muito o trabalho com datas.
Enquanto isso, para projetos sérios, é recomendável usar bibliotecas como date-fns (leve e modular) ou Luxon (da equipe do Moment.js) para manipulação e formatação de datas, pois elas oferecem APIs mais amigáveis e consistentes.
Boas práticas e observações finais
Ao trabalhar com datas em JavaScript, algumas boas práticas podem evitar dores de cabeça:
- Sempre use UTC para armazenar e transmitir datas (por exemplo, em APIs) e converta para o fuso local apenas para exibição.
- Evite usar
new Date(string)com formatos não padronizados, pois o parsing pode variar entre navegadores. - Prefira bibliotecas para manipulação complexa, em vez de reinventar a roda.
- Considere usar
Intlpara formatação, pois é nativo e eficiente. - Fique atento ao futuro do
Temporale comece a experimentar com polyfills se possível.
Com essas ferramentas e conhecimentos, você estará preparado para lidar com datas de forma robusta em seus projetos.
Referências
- MDN - Date
- MDN - Intl.DateTimeFormat
- Proposta Temporal - Documentação
- date-fns - Biblioteca de datas
- Luxon - Biblioteca de datas
- MDN - Intl.RelativeTimeFormat
Exercícios
- Exercício 1: Crie uma função que receba uma data no formato 'AAAA-MM-DD' e retorne o dia da semana por extenso em português (ex.: 'segunda-feira'). Use
Intl.DateTimeFormat. - Exercício 2: Escreva uma função que calcule a diferença em dias entre duas datas (ignorando horas). Use
Date.UTCpara evitar problemas de fuso. - Exercício 3: Implemente uma função que adicione um número de meses a uma data, preservando o dia do mês (se possível). Por exemplo, 31/01 + 1 mês deve resultar em 28/02 (ou 29/02 em ano bissexto). Use o objeto
Datee lide com os casos extremos. - Exercício 4: Crie uma função que formate uma data no formato 'dd/mm/aaaa hh:mm' no fuso horário de São Paulo, usando
Intl.DateTimeFormatcom opções. - Exercício 5: Pesquise sobre a proposta
Temporale escreva um pequeno exemplo de como você usariaTemporal.PlainDatepara representar uma data de aniversário e calcular a idade atual (considerando ano bissexto). Use um polyfill se necessário.
function diaDaSemana(dataISO) {
const data = new Date(dataISO + 'T12:00:00'); // meio-dia para evitar problemas de fuso
const formatador = new Intl.DateTimeFormat('pt-BR', { weekday: 'long' });
return formatador.format(data);
}
console.log(diaDaSemana('2025-05-25')); // "domingo"function diferencaEmDias(data1, data2) {
const msPorDia = 24 * 60 * 60 * 1000;
const utc1 = Date.UTC(data1.getFullYear(), data1.getMonth(), data1.getDate());
const utc2 = Date.UTC(data2.getFullYear(), data2.getMonth(), data2.getDate());
return Math.round((utc2 - utc1) / msPorDia);
}
console.log(diferencaEmDias(new Date(2025, 4, 25), new Date(2025, 4, 30))); // 5function adicionarMeses(data, meses) {
const novoMes = data.getMonth() + meses;
const novoAno = data.getFullYear() + Math.floor(novoMes / 12);
const mesFinal = ((novoMes % 12) + 12) % 12;
const ultimoDia = new Date(novoAno, mesFinal + 1, 0).getDate();
const dia = Math.min(data.getDate(), ultimoDia);
return new Date(novoAno, mesFinal, dia);
}
console.log(adicionarMeses(new Date(2025, 0, 31), 1).toString()); // "Fri Feb 28 2025 ..."function formatarDataHora(data) {
const formatador = new Intl.DateTimeFormat('pt-BR', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
timeZone: 'America/Sao_Paulo'
});
return formatador.format(data);
}
console.log(formatarDataHora(new Date())); // ex.: "25/05/2025 15:30"// Supondo que Temporal esteja disponível (ou use polyfill)
const aniversario = Temporal.PlainDate.from('1990-05-25');
const hoje = Temporal.Now.plainDateISO();
let idade = hoje.year - aniversario.year;
if (hoje.month < aniversario.month || (hoje.month === aniversario.month && hoje.day < aniversario.day)) {
idade--;
}
console.log(idade);