Resumo
A gestão do tempo em JavaScript tem sido historicamente uma fonte de erros. O objeto Date, herdado do Java em 1995, carrega defeitos de design que geram bugs de forma recorrente. A API Temporal (Stage 3 no TC39, já disponível no Chrome 144 e Firefox 139) resolve esses problemas com tipos imutáveis, fusos horários reais, precisão de nanossegundos e suporte para calendários internacionais.
Este artigo analisa por que Date falha, como o Temporal resolve e o que você precisa para migrar.
1. O problema com Date
1.1 Um erro com história
Em maio de 1995, Brendan Eich criou o JavaScript em 10 dias. Para manipular datas, copiou diretamente java.util.Date do Java 1.0. O problema: o próprio Java reconheceu que essa classe era defeituosa e a substituiu dois anos depois. O JavaScript, no entanto, ficou atrelado a ela por compatibilidade com a web.
1.2 Defeitos principais de Date
Mutabilidade
Date é mutável. Qualquer método set* modifica o objeto original, o que causa bugs difíceis de rastrear quando múltiplas partes do código compartilham a mesma referência:
const date = new Date('2026-03-15T10:00:00');
const copy = date;
copy.setFullYear(2030);
console.log(date.getFullYear()); // 2030 — a original também mudou!
Para evitar isso, é necessário fazer cópias defensivas constantemente:
const safeCopy = new Date(date.getTime());
Parsing inconsistente
Date.parse() interpreta as strings de forma diferente dependendo do navegador e do motor:
const date = new Date('2025-01-01');
// Em um navegador: é interpretado como UTC (meia-noite UTC)
// Em outro: é interpretado como hora local (meia-noite local)
// Resultado: possível erro de "um dia de diferença"
Sem aritmética temporal
Não há forma nativa de somar “um mês” corretamente. O desenvolvedor precisa fazer isso manualmente:
const date = new Date('2026-01-31');
date.setMonth(date.getMonth() + 1);
console.log(date.toISOString()); // 2026-03-03 — pulou fevereiro inteiro
Sem fusos horários
Date só conhece dois contextos: UTC e o fuso local do sistema. Não pode representar “10:00 AM em Tóquio” a partir de um computador em Nova York sem truques com offsets que falham durante mudanças de horário de verão.
1.3 A resposta do TC39
O comitê TC39 projetou o Temporal como um namespace completamente novo, livre das restrições do Date. Seu design incorpora lições do Moment.js, Luxon, date-fns, java.time e NodaTime. O objetivo: tornar difícil escrever código incorreto por design.
2. Conceitos fundamentais do Temporal
2.1 Tempo exato vs. tempo de relógio
A distinção mais importante no Temporal:
-
Tempo exato: um ponto preciso na linha temporal universal, independente da localização. Dois eventos no mesmo instante são simultâneos, não importa se ocorreram em Londres ou Sydney. É modelado com
Temporal.Instant. -
Tempo de relógio (wall-clock): o que mostra um calendário ou relógio local. “15 de março às 09:00” não define um momento exato até que seja associado a um fuso horário. É modelado com as classes
Plain*.
Date tentava ser ambos ao mesmo tempo e falhava nos dois. Temporal força a ser explícito.
2.2 Imutabilidade
Todas as classes do Temporal são imutáveis. Métodos como .add(), .subtract() ou .with() sempre retornam uma nova instância:
const today = Temporal.PlainDate.from('2026-02-10');
const tomorrow = today.add({ days: 1 });
console.log(today.toString()); // 2026-02-10 — sem alterações
console.log(tomorrow.toString()); // 2026-02-11
Isso elimina bugs de mutação, facilita a depuração e permite detecção eficiente de mudanças em frameworks reativos como React ou Vue.
2.3 Precisão de nanossegundos
Date opera com milissegundos (10⁻³ s). Temporal usa nanossegundos (10⁻⁹ s), seis ordens de magnitude mais preciso. Para manipular esses valores sem perder precisão numérica, Temporal usa BigInt:
const now = Temporal.Now.instant();
console.log(now.epochNanoseconds); // 1739188800000000000n (BigInt)
3. Tipos de dados da API
3.1 Temporal.Instant
Representa um ponto exato na linha temporal: nanossegundos desde a Época Unix. Sem fuso horário, sem calendário.
Uso típico: timestamps em bancos de dados, logs de servidor, ordenação cronológica.
const now = Temporal.Now.instant();
// A partir de um Date legacy
const legacyDate = new Date();
const instant = legacyDate.toTemporalInstant();
// Serialização: sempre em UTC
console.log(now.toString()); // 2026-02-10T16:26:10.123456789Z
Um Instant não pode mostrar informações humanas (dia da semana, mês) sem ser projetado em um fuso horário.
3.2 Temporal.ZonedDateTime
A classe mais completa. Combina um instante exato + fuso horário + calendário.
const meeting = Temporal.ZonedDateTime.from({
year: 2026,
month: 3,
day: 15,
hour: 10,
minute: 0,
timeZone: 'America/Lima',
});
console.log(meeting.toString());
// 2026-03-15T10:00:00-05:00[America/Lima]
// Ciente das mudanças de horário de verão
const nextDay = meeting.add({ days: 1 });
Características chave:
- Ciente das regras do horário de verão (DST)
- Usa identificadores IANA (
America/Lima), não apenas offsets - Formato RFC 9557 que preserva todas as informações
3.3 Classes Plain: tempo civil sem fuso horário
PlainDate
Data sem hora nem fuso horário. Ideal para aniversários, feriados, vencimentos:
const birthday = Temporal.PlainDate.from('1995-04-15');
const daysUntilToday = birthday.until(Temporal.Now.plainDateISO()).total({ unit: 'day' });
PlainTime
Hora do dia sem data nem fuso. Ideal para horários recorrentes:
const opening = Temporal.PlainTime.from('09:00');
const closing = Temporal.PlainTime.from('17:30');
const duration = opening.until(closing);
console.log(duration.toString()); // PT8H30M
PlainDateTime
Data + hora, sem fuso horário. Equivale ao “que diz o relógio na parede”:
const event = Temporal.PlainDateTime.from('2026-06-15T14:30');
const withZone = event.toZonedDateTime('America/Bogota');
Nota: não use para ordenar eventos globais. “2026-01-01 00:00” em Tóquio ocorre antes do que em Londres, mas sem fuso horário não podem ser comparados.
PlainYearMonth e PlainMonthDay
Tipos especializados para dados incompletos:
// Vencimento de cartão: apenas ano e mês
const expiration = Temporal.PlainYearMonth.from('2028-10');
// Aniversário: apenas mês e dia
const christmas = Temporal.PlainMonthDay.from('12-25');
3.4 Temporal.Duration
Representa magnitudes de tempo em componentes humanos. Segue o formato ISO 8601:
const duration = Temporal.Duration.from({ years: 1, months: 2, days: 10, hours: 2 });
console.log(duration.toString()); // P1Y2M10DT2H
// A aritmética respeita o calendário
const jan31 = Temporal.PlainDate.from('2026-01-31');
const oneMonthLater = jan31.add({ months: 1 });
console.log(oneMonthLater.toString()); // 2026-02-28 — lida corretamente com os meses
// Comparar durações
const d1 = Temporal.Duration.from({ hours: 1, minutes: 30 });
const d2 = Temporal.Duration.from({ minutes: 90 });
console.log(Temporal.Duration.compare(d1, d2)); // 0 — são iguais
4. Fusos horários e horário de verão (DST)
4.1 Identificadores IANA vs. offsets
Temporal distingue entre um identificador de fuso (Europe/Madrid) e um offset numérico (+01:00):
// ❌ Perigoso: o offset muda com DST
const withOffset = '2026-03-29T02:30:00+01:00';
// ✅ Seguro: Temporal recalcula o offset segundo as regras da zona
const zdt = Temporal.ZonedDateTime.from({
year: 2026,
month: 7,
day: 15,
hour: 10,
timeZone: 'Europe/Madrid',
});
console.log(zdt.offset); // +02:00 (verão)
4.2 Ambiguidades em transições DST
Quando os relógios mudam, ocorrem situações especiais:
- Lacuna (gap): ao adiantar relógios, uma hora “não existe” (ex. de 02:00 pula para 03:00)
- Sobreposição (overlap): ao atrasar relógios, uma hora “ocorre duas vezes”
Temporal gerencia isso com o parâmetro disambiguation:
// Hora que não existe (lacuna na Espanha, último domingo de março)
const nonExistent = Temporal.ZonedDateTime.from(
{ year: 2026, month: 3, day: 29, hour: 2, minute: 30, timeZone: 'Europe/Madrid' },
{ disambiguation: 'compatible' } // avança para 03:30 (por padrão)
);
// Validação estrita: lança erro se a hora for ambígua
try {
Temporal.ZonedDateTime.from(
{ year: 2026, month: 3, day: 29, hour: 2, minute: 30, timeZone: 'Europe/Madrid' },
{ disambiguation: 'reject' }
);
} catch (e) {
console.log(e); // RangeError
}
4.3 Comparativo Date vs. Temporal em DST
| Cenário | Date (legacy) | Temporal |
|---|---|---|
| Definição de zona | Implícita (local ou UTC) | Explícita (objeto TimeZone) |
| Aritmética DST | Soma milissegundos cegos | Ajusta offset segundo regras |
| Hora inexistente | Ajuste silencioso e imprevisível | Configurável (disambiguation) |
| Hora repetida | Retorna a primeira ocorrência | Configurável (earlier, later) |
5. Calendários internacionais
Date só suporta o calendário Gregoriano. Temporal integra suporte completo para calendários internacionais, alinhado com a API Intl.
5.1 Calendários disponíveis
Cada objeto PlainDate, PlainDateTime e ZonedDateTime inclui um campo calendar. Os calendários suportados incluem: hebrew, islamic, chinese, japanese, buddhist, persian, entre outros.
5.2 Aritmética segundo calendário
A operação “somar um mês” tem significados diferentes segundo o calendário:
// Calendário judaico: pode ter 12 ou 13 meses
const hebrewDate = Temporal.PlainDate.from({
year: 5782,
month: 5,
day: 1,
calendar: 'hebrew',
});
const nextMonth = hebrewDate.add({ months: 1 });
// Calendário japonês com eras
const japaneseDate = Temporal.PlainDate.from({
era: 'reiwa',
eraYear: 8,
month: 2,
day: 10,
calendar: 'japanese',
});
console.log(japaneseDate.toString()); // 2026-02-10[u-ca=japanese]
5.3 Persistência cultural
O calendário é preservado na serialização:
const date = Temporal.PlainDate.from({
year: 5782,
month: 5,
day: 1,
calendar: 'hebrew',
});
console.log(date.toString()); // 2022-01-03[u-ca=hebrew]
// Ao deserializar, reconstrói-se com o calendário correto
6. Serialização com RFC 9557
6.1 O problema do ISO 8601
O formato ISO 8601 (YYYY-MM-DDTHH:mm:ssZ) perde informação ao converter para UTC. Se as regras de fuso horário mudarem antes de um evento futuro, a hora salva em UTC pode ficar desatualizada.
6.2 O formato RFC 9557
Temporal usa um formato estendido que preserva todas as informações:
2026-03-15T10:00:00-05:00[America/Lima][u-ca=iso8601]
│ │ │ │ │
│ │ │ │ └─ Calendário
│ │ │ └──────────────── Fuso horário (fonte de verdade)
│ │ └───────────────────────── Offset (referência rápida)
│ └────────────────────────────────── Hora civil (o que o usuário vê)
└───────────────────────────────────────────────── Data
6.3 Resolução de conflitos
Se o offset armazenado não coincidir com o fuso horário (por exemplo, após uma mudança política), Temporal oferece opções:
const str = '2026-11-01T01:30:00-04:00[America/New_York]';
// use: confia no offset (preserva instante exato)
// ignore: confia no fuso horário (preserva hora local)
// prefer: usa offset se for válido, senão recalcula
// reject: lança erro se houver contradição
const zdt = Temporal.ZonedDateTime.from(str, { offset: 'prefer' });
7. Migração e ecossistema
7.1 Suporte em navegadores (2025-2026)
- Chrome / Edge: suporte completo a partir da versão 144 (janeiro de 2026)
- Firefox: suporte a partir da versão 139 (maio de 2025)
- Safari: implementação ativa no WebKit, disponível no Technical Preview
7.2 Polyfills
Para ambientes sem suporte nativo:
| Polyfill | Tamanho | Conformidade | Recomendação |
|---|---|---|---|
@js-temporal/polyfill | ~50 KB gzip | 100% da spec | Testes e validação |
fullcalendar/temporal-polyfill | ~20 KB gzip | Suficiente para produção | Apps sensíveis ao tamanho |
// Carregamento condicional do polyfill
if (!globalThis.Temporal) {
await import('@js-temporal/polyfill');
}
7.3 Temporal vs. bibliotecas existentes
Temporal vs. Moment.js: Moment é mutável, pesado e está em modo de manutenção. Temporal é imutável, nativo e seu sucessor direto.
Temporal vs. date-fns: date-fns opera sobre Date, herdando suas limitações. Provavelmente evoluirá para oferecer utilitários sobre objetos Temporal.
7.4 Guia de migração
A migração pode ser gradual. Temporal e Date coexistem:
// Passo 1: Converter nas bordas da aplicação
const legacyDate = new Date();
const instant = legacyDate.toTemporalInstant();
const zdt = instant.toZonedDateTimeISO('America/Bogota');
// Passo 2: Toda a lógica interna usa Temporal
const inOneWeek = zdt.add({ weeks: 1 });
const dateOnly = zdt.toPlainDate();
// Passo 3: Converter de volta apenas quando necessário
const backToDate = new Date(inOneWeek.epochMilliseconds);
8. Conclusão
Temporal não é uma simples melhoria do Date — é uma correção necessária após três décadas. Tipos imutáveis, fusos horários reais, precisão de nanossegundos e suporte multicultural eliminam categorias inteiras de bugs.
Com suporte nativo já disponível nos principais navegadores em 2026, o momento de iniciar a transição é agora. Comece convertendo nas bordas, migre a lógica interna progressivamente e planeje a remoção do Date e de suas bibliotecas satélite.
Fontes: proposta oficial TC39, documentação MDN, análise de implementação nos navegadores e avaliações de polyfills comunitários. Os exemplos correspondem ao estágio Stage 3 vigente em fevereiro de 2026.