A API Temporal e o Futuro das Datas em JavaScript

Uma visão técnica sobre o Temporal, a nova API nativa do JavaScript que substitui o objeto Date por tipos imutáveis, fusos horários reais e precisão de nanossegundos.

A API Temporal e o Futuro das Datas em JavaScript
10 min de leitura
Nesta página

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árioDate (legacy)Temporal
Definição de zonaImplícita (local ou UTC)Explícita (objeto TimeZone)
Aritmética DSTSoma milissegundos cegosAjusta offset segundo regras
Hora inexistenteAjuste silencioso e imprevisívelConfigurável (disambiguation)
Hora repetidaRetorna a primeira ocorrênciaConfigurá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:

PolyfillTamanhoConformidadeRecomendação
@js-temporal/polyfill~50 KB gzip100% da specTestes e validação
fullcalendar/temporal-polyfill~20 KB gzipSuficiente para produçãoApps 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.

Arraste para mover o diagrama. Use + e − para dar zoom, as setas para movê-lo e 0 para redefinir a visualização.

Vamos trabalhar juntos

Engenheiro de Software e desenvolvedor Senior Full-Stack, trabalho remotamente a partir de Santa Cruz, Bolívia. Aberto a novas oportunidades.