Datas e horários com Claude Code: guia prático
Guia prático para tratar fusos, DST, Intl, testes e armazenamento de datas com Claude Code.
Por que datas e horários precisam de mais que um prompt
Bugs de data e hora passam fácil no code review e aparecem com força em produção. Uma tela pode funcionar no Brasil ou em Portugal e quebrar no horário de verão dos EUA, no fechamento de mês europeu, nos offsets de 30 minutos da Índia ou na diferença entre o timezone do servidor e o do navegador. Se você pedir apenas “implemente tratamento de datas” ao Claude Code, ele pode gerar formatação bonita sem definir política de armazenamento, contrato de API e testes de fronteira.
Use Claude Code como par para revisar a especificação temporal, não só como gerador de código. Este guia cobre reservas, cobrança, notificações, SLA de suporte e painéis administrativos que cruzam fusos horários. Para uma estratégia de testes mais ampla, veja Claude Code testing strategies. Para mudanças de schema, combine com database migration with Claude Code.
Antes de implementar, confira fontes oficiais. Para formatação, use MDN Intl.DateTimeFormat. Para Temporal, veja TC39 Temporal proposal e MDN Temporal. Para PostgreSQL, consulte PostgreSQL Date/Time Types. Para checks automáticos depois das edições, use Claude Code hooks.
Defina vocabulário e política de armazenamento
Antes de pedir código, alinhe as palavras do time. Sem isso, o review vira frases vagas como “salvamos em UTC, então está seguro” ou “o produto é local, então basta usar o timezone do país”.
| Termo | Significado simples | Regra prática |
|---|---|---|
| instant | Um momento único no mundo, como 2026-06-02T00:00:00Z | Salvar como timestamp baseado em UTC |
| local date | Data de calendário de uma pessoa ou regra de negócio | Manter como YYYY-MM-DD, separada do horário |
| wall clock time | Hora que a pessoa vê no relógio, como reunião às 09:00 | Guardar com um timezone IANA |
| IANA timezone | Nome regional como America/Sao_Paulo ou America/New_York | Não substituir por offset fixo |
| DST | Horário de verão; alguns dias têm 23 ou 25 horas | Testar dias de transição |
Em projetos reais, coloque estas regras em AGENTS.md ou CLAUDE.md antes da implementação.
- Timestamps de eventos já ocorridos na API devem ser strings ISO 8601 com
Zou offset explícito. - Reservas futuras devem manter
localDate,localTimeetimeZoneem campos separados. - A UI deve passar sempre
localeetimeZoneparaIntl.DateTimeFormat; nada de depender do ambiente.
A fronteira mental deve ficar assim.
flowchart LR
A["Client: local date/time input"] --> B["API contract: localDate + localTime + timeZone"]
B --> C["Server: validate and convert when needed"]
C --> D["Database: instant in timestamptz + original timeZone"]
D --> E["UI: format with Intl.DateTimeFormat"]
E --> A
Monte uma base segura com Intl.DateTimeFormat
Para exibição, comece pela API padrão Intl.DateTimeFormat. A MDN a documenta como API embutida para formatação de data e hora sensível ao idioma, com suporte a timeZone explícito. Também diga ao Claude Code para não usar toLocaleString() sem argumentos e para não transformar YYYY-MM-DD em Date quando o valor significa uma data local de calendário.
Este módulo não tem dependências. Salve como src/lib/date-policy.ts e reutilize na UI, API e testes.
// src/lib/date-policy.ts
export const TIME_POLICY = {
defaultLocale: 'pt-BR',
defaultTimeZone: 'America/Sao_Paulo',
} as const;
type FormatOptions = {
locale?: string;
timeZone?: string;
includeWeekday?: boolean;
};
function toDate(input: string | Date): Date {
const date = input instanceof Date ? input : new Date(input);
if (!Number.isFinite(date.getTime())) {
throw new Error(`Invalid date value: ${String(input)}`);
}
return date;
}
export function toUtcIso(input: string | Date): string {
if (typeof input === 'string' && !/(Z|[+-]\d{2}:?\d{2})$/i.test(input)) {
throw new Error('Timestamp must include Z or an explicit UTC offset.');
}
return toDate(input).toISOString();
}
function dateParts(date: Date, timeZone: string): Record<string, string> {
const parts = new Intl.DateTimeFormat('en-US', {
timeZone,
year: 'numeric',
month: '2-digit',
day: '2-digit',
}).formatToParts(date);
return Object.fromEntries(
parts
.filter((part) => part.type !== 'literal')
.map((part) => [part.type, part.value]),
);
}
export function dayKeyInTimeZone(
input: string | Date,
timeZone = TIME_POLICY.defaultTimeZone,
): string {
const parts = dateParts(toDate(input), timeZone);
return `${parts.year}-${parts.month}-${parts.day}`;
}
export function formatInstant(
input: string | Date,
{
locale = TIME_POLICY.defaultLocale,
timeZone = TIME_POLICY.defaultTimeZone,
includeWeekday = true,
}: FormatOptions = {},
): string {
return new Intl.DateTimeFormat(locale, {
timeZone,
weekday: includeWeekday ? 'short' : undefined,
year: 'numeric',
month: 'short',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
hourCycle: 'h23',
timeZoneName: 'short',
}).format(toDate(input));
}
O objetivo não é esconder toda lógica de datas em utilitários. O objetivo é separar instant, chave de data local e rótulo de exibição. Antes de Claude Code adicionar outro helper, peça que explique a qual categoria o requisito pertence.
Converta horários locais futuros explicitamente
Intl.DateTimeFormat é ótimo para saída, mas não é a ferramenta certa para converter 2026-11-01 01:30 America/New_York em um instant UTC seguro. Escolha uma biblioteca para o projeto e valide a documentação atual. Luxon e seus API docs documentam APIs explícitas de timezone. date-fns é forte para utilitários funcionais, mas requisitos de timezone precisam ser checados na documentação oficial atual. Temporal está em Stage 4 no TC39, mas runtime e polyfill ainda precisam de decisão.
Em uma reserva, o usuário informa data e hora locais; o servidor converte para o instant armazenado.
// src/lib/schedule-time.ts
import { DateTime } from 'luxon';
type LocalScheduleInput = {
localDate: string; // YYYY-MM-DD
localTime: string; // HH:mm
timeZone: string; // IANA time zone, for example "America/New_York"
};
export function scheduleToUtcIso(input: LocalScheduleInput): string {
const rawLocal = `${input.localDate}T${input.localTime}`;
const local = DateTime.fromISO(rawLocal, { zone: input.timeZone });
if (!local.isValid) {
throw new Error(local.invalidExplanation ?? `Invalid local time: ${rawLocal}`);
}
const roundTrip = local.setZone(input.timeZone).toFormat("yyyy-MM-dd'T'HH:mm");
if (roundTrip !== rawLocal) {
throw new Error(`Nonexistent local time in ${input.timeZone}: ${rawLocal}`);
}
const iso = local.toUTC().toISO({ suppressMilliseconds: true });
if (!iso) {
throw new Error(`Could not convert local time to UTC: ${rawLocal}`);
}
return iso;
}
Esse helper não resolve sozinho a hora 01:00 repetida quando o DST termina. O produto precisa escolher: offset mais cedo, offset mais tarde ou confirmação do usuário. Peça ao Claude Code testes para horários locais inexistentes, horários ambíguos, fim de mês e ano bissexto.
Separe cliente, servidor e banco de dados
O erro mais comum é passar um valor para Date sem decidir se a interpretação pertence ao navegador ou ao servidor. Inputs do browser, payloads de API, colunas de banco, emails e CSVs são fronteiras diferentes.
Revise o design com pelo menos estes três casos.
- Sistema de reservas: entrada usa o horário local do local físico, armazenamento usa UTC instant, exibição usa o timezone do visitante ou do local.
- Fechamento de cobrança: “último dia do mês às 23:59” é regra de data local no timezone do contrato; não calcule somando blocos de 24 horas.
- SLA de suporte: dias úteis, feriados e horário comercial variam por região; salve o instant do prazo e o contexto local legível.
A documentação oficial do PostgreSQL explica que timestamp with time zone é convertido para UTC e o timezone original não é preservado. Portanto, timestamptz sozinho não mostra que o usuário escolheu America/New_York. Se o produto precisa disso, salve em uma coluna separada.
create table scheduled_events (
id uuid primary key,
title text not null,
starts_at timestamptz not null,
original_time_zone text not null check (original_time_zone <> ''),
local_date date not null,
local_time time not null,
created_at timestamptz not null default now()
);
create index scheduled_events_starts_at_idx
on scheduled_events (starts_at);
Não assuma que inserir 2026-06-02T09:00:00+09:00 em timestamp without time zone mantém o offset com significado. PostgreSQL define o tipo primeiro; para without time zone, a indicação de timezone é ignorada. Adicione “o tipo da coluna bate com o contrato da API?” ao checklist de review do Claude Code.
Escreva testes de DST com instants fixos
Testes não devem depender de “hoje”, relógio atual ou timezone da máquina. Fixe a entrada como UTC instant e confira a data local ou rótulo esperado. Com Vitest, comece por este arquivo. Para padrões mais amplos, veja Claude Code Vitest avançado.
// src/lib/date-policy.test.ts
import { describe, expect, it } from 'vitest';
import { dayKeyInTimeZone, formatInstant, toUtcIso } from './date-policy';
describe('date/time policy', () => {
it('requires an explicit offset for API timestamps', () => {
expect(() => toUtcIso('2026-06-02T09:00:00')).toThrow(/offset/);
expect(toUtcIso('2026-06-02T09:00:00+09:00')).toBe('2026-06-02T00:00:00.000Z');
});
it('calculates a local day key across the UTC date boundary', () => {
expect(dayKeyInTimeZone('2026-03-31T15:01:00Z', 'Asia/Tokyo')).toBe('2026-04-01');
expect(dayKeyInTimeZone('2026-04-01T00:30:00Z', 'America/Los_Angeles')).toBe('2026-03-31');
});
it('formats a DST transition instant in the requested time zone', () => {
const label = formatInstant('2026-03-08T07:30:00Z', {
locale: 'en-US',
timeZone: 'America/New_York',
});
expect(label).toMatch(/03:30|3:30/);
expect(label).toMatch(/EDT|GMT-4/);
});
});
O teste aceita EDT ou GMT-4 porque os dados ICU podem variar entre ambientes de CI. Já a chave de data local e a conversão UTC são regra de negócio, então devem ser estritas.
Evite regressões com prompts e hooks
Passe as restrições para Claude Code antes da implementação. Um prompt longo é aceitável quando lista as fronteiras perigosas.
Before implementing date/time changes, read date-policy.ts and the database schema.
Constraints:
- API timestamps for completed events must be ISO strings with Z or an explicit offset
- Future bookings must keep localDate, localTime, and timeZone separate
- Intl.DateTimeFormat must always receive locale and timeZone
- Do not use new Date('YYYY-MM-DD') for local calendar dates
- Add Vitest cases for DST start, DST end, month end, and leap day
- Explain the PostgreSQL timestamp/timestamptz difference in the review notes
Done when:
- npm test -- --run date-time passes
- Updated API response examples are added to README
Em equipes, use Claude Code hooks para rodar checks determinísticos depois de edições. A documentação oficial mostra que hooks executa comandos shell em eventos do ciclo de vida. Para o fluxo completo, veja Claude Code hooks guide.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "npm test -- --run date-time"
}
]
}
]
}
}
Escolha bibliotecas pelo tipo de tempo
Não escolha biblioteca de datas só por popularidade. Escolha pelo tipo de tempo que o produto manipula. Intl.DateTimeFormat serve para exibição localizada, date-fns para utilitários focados, Luxon para agendamentos com timezone IANA e Temporal para designs que separam Instant, PlainDate e ZonedDateTime. Mesmo com Temporal em Stage 4, confirme suporte de runtime e polyfill antes de pedir para Claude Code reescrever produção.
| Opção | Melhor uso | Cuidado |
|---|---|---|
| Intl | Exibição localizada com timezone explícito | Não converte horário local em instant |
| date-fns | Matemática de datas, imports por função, utilitários pequenos | Confira docs oficiais para timezone |
| Luxon | Agendamento com IANA timezone, tempo relativo, ISO | Horários ambíguos de DST precisam de regra de produto |
| Temporal | Separar Instant, PlainDate, ZonedDateTime | Compatibilidade e polyfill importam |
Do ponto de vista de conteúdo e monetização, uma lista genérica de bibliotecas é fraca. O leitor precisa saber o que salvar, onde converter e o que testar. Se sua equipe quer colocar essa política em CLAUDE.md, hooks, PR review e migrations, a página de treinamento e consultoria Claude Code é o próximo passo natural. Para uso individual, adicione o prompt acima ao cheatsheet gratuito e use como proteção repetível.
Nota de verificação prática
Nota do Masa: a checagem mais útil foi fixar um UTC instant, formatá-lo em Tóquio, Nova York e Los Angeles, e validar a chave de data local ao cruzar fronteiras de dia. O bug mais fácil de reproduzir foi tratar YYYY-MM-DD como data local do usuário, converter para Date e ver o dia anterior em outro timezone. A política deste artigo mantém datas locais como strings e converte explicitamente apenas instants salvos e rótulos de exibição, o que torna os diffs do Claude Code muito mais fáceis de revisar.
PDF grátis: cheatsheet do Claude Code
Informe seu e-mail e baixe uma página com comandos, hábitos de revisão e workflows seguros.
Cuidamos dos seus dados e não enviamos spam.
Sobre o autor
Masa
Engenheiro focado em workflows práticos com Claude Code.
Artigos relacionados
Claude Code Free PDF Funnel Checklist: transformar tráfego em cadastros e cliques de produto
Checklist para levar leitores ao PDF grátis, produtos Gumroad e consultoria com Claude Code.
Workflow Obsidian para CLAUDE.md com Claude Code
Transforme notas de trabalho do Obsidian em notas operacionais CLAUDE.md para preservar contexto.
Claude Code Revenue CTA Routing: artigos para PDF, Gumroad e consultoria
Um fluxo com Claude Code para levar leitores ao PDF grátis, Gumroad ou consultoria conforme intenção.