Trabalho Avançado com Datas e Horas em TypeScript: da Tipagem a Operações Complexas
\n\nTrabalhar com datas e horas é uma das tarefas mais frequentes e, ao mesmo tempo, traiçoeiras no desenvolvimento web. Em um nível avançado, o simples uso de new Date() já não é suficiente. O TypeScript fornece um poderoso sistema de tipos, e bibliotecas modernas (Luxon, Date-fns) permitem escrever código seguro, previsível e eficiente. Neste artigo, abordaremos técnicas avançadas: tipagem rigorosa, trabalho com fusos horários, formatação e parsing complexos, além da criação de utilitários próprios.
1. Tipagem Rigorosa de Datas: Tipos Personalizados e Branded Types
\n\nO tipo Date no TypeScript não protege contra o envio de valores incorretos. Por exemplo, uma função que espera uma data de nascimento pode receber um timestamp atual. A solução é usar Branded Types (tipos marcados) ou Nominal Typing.
// Branded Type para data de nascimento\ntype BirthDate = Date & { __brand: 'BirthDate' };\n\nfunction createBirthDate(date: Date): BirthDate {\n // Validação: a data não pode estar no futuro\n if (date.getTime() > Date.now()) {\n throw new Error('A data de nascimento não pode estar no futuro');\n }\n return date as BirthDate;\n}\n\nfunction calculateAge(birthDate: BirthDate): number {\n const diff = Date.now() - birthDate.getTime();\n return Math.floor(diff / (1000 * 60 * 60 * 24 * 365.25));\n}\n\n// Uso\nconst rawDate = new Date('1990-05-15');\nconst birth = createBirthDate(rawDate);\nconsole.log(calculateAge(birth)); // OK\n\n// Erro: Argument of type 'Date' is not assignable to parameter of type 'BirthDate'\n// console.log(calculateAge(new Date())); \n\n\nEssa abordagem garante que apenas uma data de nascimento validada seja passada para a função calculateAge. Para cenários mais complexos, você pode criar tipos para timestamps (UnixTimestamp), intervalos (TimeInterval), etc.
2. Formatação e Parsing Avançados com Intl.DateTimeFormat
\n\nO objeto nativo Intl.DateTimeFormat permite formatar datas de forma flexível sem bibliotecas externas. Em um nível avançado, podemos criar formatadores reutilizáveis e lidar com localizações complexas.
// Criando um formatador com opções\nconst formatter = new Intl.DateTimeFormat('pt-BR', {\n year: 'numeric',\n month: 'long',\n day: 'numeric',\n weekday: 'long',\n hour: '2-digit',\n minute: '2-digit',\n timeZone: 'America/Sao_Paulo',\n});\n\n// Formatação\nconsole.log(formatter.format(new Date())); \n// "sexta-feira, 25 de outubro de 2024, 14:30"\n\n// Parsing (utilitário personalizado)\nfunction parseLocalizedDate(dateStr: string, locale: string): Date | null {\n const parts = dateStr.match(/(\\d{4})-(\\d{2})-(\\d{2})/);\n if (!parts) return null;\n \n const [, year, month, day] = parts;\n return new Date(parseInt(year), parseInt(month) - 1, parseInt(day));\n}\n\n\nDica: Para desempenho, crie formatadores uma vez e reutilize-os. Evite criar uma nova instância a cada chamada.
\n\n3. Trabalho com Fusos Horários e a Biblioteca Luxon
\n\nO Date nativo lida mal com fusos horários. O Luxon é uma biblioteca moderna do criador do Moment.js, que se integra perfeitamente com o TypeScript.
import { DateTime, Settings } from 'luxon';\n\n// Definindo o fuso horário padrão\nSettings.defaultZone = 'America/Sao_Paulo';\n\n// Criando uma data com fuso explícito\nconst meeting = DateTime.fromISO('2024-11-01T15:00:00', { zone: 'America/New_York' });\nconsole.log(meeting.toISO()); // 2024-11-01T15:00:00.000-04:00\n\n// Convertendo para outro fuso\nconst saoPauloTime = meeting.setZone('America/Sao_Paulo');\nconsole.log(saoPauloTime.toFormat('dd.MM.yyyy HH:mm')); // 01.11.2024 16:00\n\n// Trabalhando com intervalos\nconst start = DateTi