date-fns / Luxon
Backends falham silenciosamente em fusos horários. Luxon lida com zonas IANA e horário de verão (DST); date-fns lida com aritmética de calendário. Use ambos deliberadamente ou padronize apenas em Luxon.
Busque em todas as páginas da documentação
Backends falham silenciosamente em fusos horários. Luxon lida com zonas IANA e horário de verão (DST); date-fns lida com aritmética de calendário. Use ambos deliberadamente ou padronize apenas em Luxon.
Cartão de receita de referência rápida - pronto para copiar e colar.
import { DateTime } from "luxon";
// Armazene ISO UTC no Postgres
const scheduledAtUtc = DateTime.utc(2026, 7, 9, 14, 30).toISO();
// Converta para e-mail voltado para o cliente (zona explícita)
const display = DateTime.fromISO(scheduledAtUtc, { zone: "utc" })
.setZone("America/New_York")
.toFormat("ff ZZZZ");
// "Jul 9, 2026, 10:30 AM EDT"import { addBusinessDays, differenceInCalendarDays } from "date-fns";
const shipDate = addBusinessDays(new Date("2026-07-09"), 3);
const daysUntilDue = differenceInCalendarDays(dueDate, new Date());Quando usar isso:
// src/scheduling/appointments.ts
import { DateTime } from "luxon";
import { z } from "zod";
const bookSchema = z.object({
slotLocal: z.string().datetime({ offset: true }),
timeZone: z.string(), // IANA: America/Chicago
});
export function toUtcStorage(input: z.infer<typeof bookSchema>): string {
const local = DateTime.fromISO(input.slotLocal, { zone: input.timeZone });
if (!local.isValid) {
throw new Error(`slot inválido: ${local.invalidReason}`);
}
return local.toUTC().toISO()!;
}
export function nextBillingRunUtc(
anchorUtc: string,
customerZone: string
): string {
const local = DateTime.fromISO(anchorUtc, { zone: "utc" }).setZone(customerZone);
// Cobrar às 00:05 local no dia 1º
const next = local.plus({ months: 1 }).startOf("month").set({
hour: 0,
minute: 5,
second: 0,
millisecond: 0,
});
return next.toUTC().toISO()!;
}// API: sempre retorne UTC + dica de exibição opcional
app.get("/appointments/:id", async (req) => {
const row = await db.getAppointment(req.params.id);
const startsAtUtc = row.starts_at; // timestamptz
const forUser = DateTime.fromISO(startsAtUtc, { zone: "utc" })
.setZone(row.user_timezone)
.toISO();
return {
startsAtUtc,
startsAtLocal: forUser,
timeZone: row.user_timezone,
};
});Regras do banco de dados:
timestamptz para instantes; text ou date apenas quando verdadeiramente local do calendárioAmerica/New_York| Tarefa | Biblioteca | Exemplo |
|---|---|---|
| Conversão de zona IANA | Luxon | setZone("Europe/Berlin") |
| Agendamento seguro contra DST | Luxon | plus({ months: 1 }) na zona |
| Dias úteis (UTC) | date-fns | addBusinessDays |
| Duração entre datas | date-fns | differenceInMinutes |
| Formato para logs | Luxon | toISO() sempre UTC |
// ERRADO: analisa como zona local do servidor
new Date("2026-07-09 09:00:00");
// ERRADO: EST é ambíguo (EST vs EDT)
const tz = "EST";
// CERTO: offset explícito ou IANA
DateTime.fromISO("2026-07-09T09:00:00-05:00");
DateTime.now().setZone("America/Chicago");Se cada data tocar um fuso horário do usuário, apenas Luxon é suficiente. Adicione date-fns quando precisar de seus helpers de calendário tree-shakeable e toda a matemática permanecer em UTC.
Datas de fixture: segundo domingo de março e primeiro domingo de novembro para zonas dos EUA. Afirme a saída UTC, não as strings formatadas.
Serializar strings ISO 8601 em JSON, não objetos Date (eles se tornam strings UTC de qualquer maneira). Documente que os consumidores da API devem enviar offset ou Z.
Versões do Stack: Esta página foi escrita para Node.js 24.18.0 (Active LTS), npm 10+, TypeScript 5.6+, Express 5, Fastify 5 e NestJS 11.
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026