date-fns / Luxon
Los backends fallan silenciosamente con las zonas horarias. Luxon maneja las zonas IANA y el horario de verano (DST); date-fns maneja la aritmética del calendario. Usa ambos deliberadamente o estandariza solo en Luxon.
Busca en todas las páginas de la documentación
Los backends fallan silenciosamente con las zonas horarias. Luxon maneja las zonas IANA y el horario de verano (DST); date-fns maneja la aritmética del calendario. Usa ambos deliberadamente o estandariza solo en Luxon.
Tarjeta de receta de referencia rápida - lista para copiar y pegar.
import { DateTime } from "luxon";
// Almacena ISO UTC en Postgres
const scheduledAtUtc = DateTime.utc(2026, 7, 9, 14, 30).toISO();
// Convierte para correo electrónico dirigido al 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());Cuándo usarlo:
// 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(`invalid slot: ${local.invalidReason}`);
}
return local.toUTC().toISO()!;
}
export function nextBillingRunUtc(
anchorUtc: string,
customerZone: string
): string {
const local = DateTime.fromISO(anchorUtc, { zone: "utc" }).setZone(customerZone);
// Facturar a las 00:05 hora local el día 1
const next = local.plus({ months: 1 }).startOf("month").set({
hour: 0,
minute: 5,
second: 0,
millisecond: 0,
});
return next.toUTC().toISO()!;
}// API: siempre devuelve UTC + sugerencia de visualización 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,
};
});Reglas de la base de datos:
timestamptz para instantes; text o date solo cuando sea verdaderamente local al calendarioAmerica/New_York| Tarea | Librería | Ejemplo |
|---|---|---|
| Conversión de zona IANA | Luxon | setZone("Europe/Berlin") |
| Programación segura para DST | Luxon | plus({ months: 1 }) en zona |
| Días hábiles (UTC) | date-fns | addBusinessDays |
| Duración entre fechas | date-fns | differenceInMinutes |
| Formato para registros | Luxon | toISO() siempre UTC |
// INCORRECTO: se analiza como la zona local del servidor
new Date("2026-07-09 09:00:00");
// INCORRECTO: EST es ambiguo (EST vs EDT)
const tz = "EST";
// CORRECTO: desplazamiento explícito o IANA
DateTime.fromISO("2026-07-09T09:00:00-05:00");
DateTime.now().setZone("America/Chicago");Si cada fecha afecta una zona horaria de usuario, solo Luxon está bien. Añade date-fns cuando necesites sus ayudantes de calendario con tree-shaking y toda la matemática permanezca en UTC.
Fechas de prueba: segundo domingo de marzo y primer domingo de noviembre para las zonas de EE. UU. Afirma la salida UTC, no las cadenas formateadas.
Serializa cadenas ISO 8601 en JSON, no objetos Date (de todos modos se convierten en cadenas UTC). Documenta que los consumidores de la API deben enviar el desplazamiento o Z.
Versiones de la pila: Esta página fue escrita para Node.js 24.18.0 (LTS activa), npm 10+, TypeScript 5.6+, Express 5, Fastify 5 y NestJS 11.
Revisado por Chris St. John·Última actualización: 16 jul 2026