Formatação de datas correta por fuso no JavaScript sem bibliotecas
O Intl.DateTimeFormat integrado do JavaScript trata a formatação de fusos IANA sem moment.js ou date-fns. Aprenda as opções timeZone explícitas, a exibição segura quanto ao horário de verão, formatToParts, os limites da conversão de relógio e quando o Temporal ou uma biblioteca de fusos ainda é útil.
Por que Date não tem propriedade de fuso
Um Date do JavaScript é sempre uma contagem de milissegundos UTC. Não há fuso armazenado dentro do objeto. Quando você chama .toString() ou .toLocaleString() sem opções, o JavaScript usa o fuso local do runtime a partir do sistema operacional. O mesmo código produz saídas diferentes em um servidor em Nova York e em um notebook em Tóquio, mesmo que o timestamp subjacente seja idêntico.
Formatar com Intl.DateTimeFormat
Intl.DateTimeFormat é a API integrada para formatação ciente de localidade e fuso. Ela aceita identificadores de fuso IANA, trata as transições de horário de verão automaticamente e está disponível em navegadores modernos e no Node.js. O segredo é passar a opção timeZone explicitamente em vez de confiar no padrão do runtime.
- new Intl.DateTimeFormat('en-US', { timeZone: 'America/New_York', dateStyle: 'full', timeStyle: 'long' }).format(date)
- date.toLocaleString('en-GB', { timeZone: 'Europe/London', hour12: false })
- date.toLocaleString('ja-JP', { timeZone: 'Asia/Tokyo' })
- new Intl.DateTimeFormat('en-US', { timeZone: 'UTC', hour12: false }).format(date)
Extrair partes individuais com formatToParts
Use formatToParts() para obter os componentes individuais da data como objetos {type, value} e montar strings de data personalizadas. Isso é melhor do que dividir uma string de data localizada porque pontuação, ordem e escritas diferem por localidade.
- const parts = new Intl.DateTimeFormat('en-US', { timeZone: 'America/Chicago', year: 'numeric', month: '2-digit', day: '2-digit' }).formatToParts(date)
- parts.find(p => p.type === 'year').value → '2023'
- parts.find(p => p.type === 'month').value → '11'
- Object.fromEntries(parts.map(p => [p.type, p.value])) → { year, month, day, hour, minute, second }
Converter hora de relógio em UTC (o sentido difícil)
Ir de uma hora de relógio em um fuso dado para UTC é mais difícil com a API Date clássica. Formatar um instante em America/New_York é fácil; construir o instante representado por 2026-03-08 02:30 em America/New_York não é, pois essa hora local pode ser pulada ou repetida durante as transições de horário de verão.
- O Temporal chegou ao Estágio 4 em 2026, mas o suporte nativo dos navegadores ainda não é universal
- Para uso em produção em todos os navegadores hoje, o polyfill do Temporal ou date-fns-tz toDate() ainda são escolhas práticas
- Evite aritmética manual de offset UTC — as transições de horário de verão ocorrem em horas locais diferentes em anos diferentes
- O zonedToEpochMs() deste site usa correção de offset de uma iteração — veja src/timeUtils.ts
Escolher o identificador de fuso correto
Use nomes de fuso IANA como America/New_York, Europe/London, Asia/Tokyo ou UTC. Evite offsets fixos como UTC-5 para a hora voltada ao usuário porque os offsets mudam com o horário de verão. America/New_York pode ser UTC-5 em janeiro e UTC-4 em julho; o nome IANA permite à plataforma aplicar a regra histórica correta para a data selecionada.
- Bom: America/Los_Angeles — inclui regras de horário de verão históricas e futuras
- Bom: Europe/Berlin — trata o horário de verão da Europa Central automaticamente
- Bom: Asia/Shanghai — exibição estável UTC+8 sem horário de verão
- Use UTC para logs, armazenamento de API, timestamps de banco e comparação entre regiões
- Use o fuso IANA do usuário para a exibição final na interface do produto
FAQ sobre formatação de fusos
- O JavaScript pode formatar uma data em outro fuso sem uma biblioteca?
- Sim. Use Intl.DateTimeFormat ou toLocaleString com uma opção timeZone, como America/New_York ou Asia/Tokyo.
- O Intl.DateTimeFormat trata o horário de verão?
- Sim. Quando você usa um identificador de fuso IANA, o runtime aplica o offset correto para aquele fuso e data.
- Devo armazenar offsets de fuso ou nomes de fuso?
- Armazene UTC para os instantes dos eventos. Se você precisa do contexto local do usuário, armazene um nome de fuso IANA como America/New_York, não apenas um offset numérico.
- Como obtenho o fuso do usuário no JavaScript?
- Use Intl.DateTimeFormat().resolvedOptions().timeZone, que retorna um nome IANA como America/New_York. Armazene esse nome, não um offset numérico, quando precisar reconstruir depois a hora local do usuário.