date-fns - Biblioteca modular y tree-shakable de utilidades de fechas para formateo, análisis y manipulación
Receta
npm install date-fnsimport { format, parseISO, addDays, differenceInDays } from "date-fns";
// Formatea una fecha
format(new Date(), "MMMM d, yyyy"); // "April 6, 2026"
// Analiza cadena ISO
const date = parseISO("2026-04-06T12:00:00Z");
// Suma días
const nextWeek = addDays(new Date(), 7);
// Diferencia entre fechas
const days = differenceInDays(new Date("2026-12-31"), new Date()); // días hasta fin de añoCuándo usarlo: Necesitas formatear, analizar, comparar o manipular fechas con una biblioteca ligera y tree-shakable que use objetos Date nativos.
Ejemplo funcional
// app/components/ActivityFeed.tsx
"use client";
import {
format,
formatDistanceToNow,
isToday,
isYesterday,
isThisWeek,
parseISO,
} from "date-fns";
interface Activity {
id: string;
action: string;
timestamp: string; // ISO string
user: string;
}
const ACTIVITIES: Activity[] = [
{ id: "1", action: "pushed to main", timestamp: new Date().toISOString(), user: "Alice" },
{ id: "2", action: "opened PR #42", timestamp: new Date(Date.now() - 3600000).toISOString(), user: "Bob" },
{ id: "3", action: "merged PR #41", timestamp: new Date(Date.now() - 86400000).toISOString(), user: "Carol" },
{ id: "4", action: "created issue", timestamp: new Date(Date.now() - 259200000).toISOString(), user: "Dave" },
{ id: "5", action: "deployed v2.1", timestamp: "2026-03-20T10:30:00Z", user: "Eve" },
];
function formatTimestamp(isoString: string): string {
const date = parseISO(isoString);
if (isToday(date)) {
return formatDistanceToNow(date, { addSuffix: true }); // "2 hours ago"
}
if (isYesterday(date)) {
return `Yesterday at ${format(date, "h:mm a")}`; // "Yesterday at 3:30 PM"
}
if (isThisWeek(date)) {
return format(date, "EEEE 'at' h:mm a"); // "Monday at 10:30 AM"
}
return format(date, "MMM d, yyyy"); // "Mar 20, 2026"
}
export default function ActivityFeed() {
return (
<div className="max-w-lg mx-auto p-6">
<h2 className="text-xl font-bold mb-4">Actividad</h2>
<ul className="space-y-3">
{ACTIVITIES.map((activity) => (
<li key={activity.id} className="flex justify-between items-center border-b pb-2">
<div>
<span className="font-medium">{activity.user}</span>{" "}
<span className="text-gray-600">{activity.action}</span>
</div>
<time
dateTime={activity.timestamp}
className="text-sm text-gray-400 whitespace-nowrap"
>
{formatTimestamp(activity.timestamp)}
</time>
</li>
))}
</ul>
</div>
);
}Lo que esto demuestra:
- Formateo de marca de tiempo inteligente con tiempo relativo para eventos recientes
parseISOpara análisis seguro de cadenas ISOisToday,isYesterday,isThisWeekpara formateo condicionalformatDistanceToNowpara salida de estilo "hace X horas"- Elemento semántico
<time>con atributodateTime
Análisis profundo
Cómo funciona
- date-fns opera sobre objetos JavaScript Date nativos, no una clase contenedora personalizada
- Cada función es un módulo ES independiente, permitiendo tree-shaking por bundlers (solo las funciones importadas se incluyen en el bundle)
formatusa tokens de Unicode Technical Standard #35:yyyy(año),MM(mes),dd(día),HH(24h),hh(12h),mm(minutos),ss(segundos)parseISOse prefiere sobrenew Date(string)porque maneja cadenas ISO 8601 consistentemente entre navegadores- Las funciones de comparación (
isBefore,isAfter,isEqual) comparan marcas de tiempo, no cadenas de fecha - Todas las funciones son puras y no mutan la fecha de entrada
Variaciones
Aritmética de fechas:
import {
addDays,
addMonths,
addHours,
subWeeks,
startOfMonth,
endOfMonth,
eachDayOfInterval,
} from "date-fns";
const tomorrow = addDays(new Date(), 1);
const nextMonth = addMonths(new Date(), 1);
const twoHoursLater = addHours(new Date(), 2);
const lastWeek = subWeeks(new Date(), 1);
// Obtén todos los días del mes actual (útil para calendarios)
const monthStart = startOfMonth(new Date());
const monthEnd = endOfMonth(new Date());
const daysInMonth = eachDayOfInterval({ start: monthStart, end: monthEnd });Soporte de locales:
import { format, formatDistanceToNow } from "date-fns";
import { fr } from "date-fns/locale";
import { ja } from "date-fns/locale";
format(new Date(), "EEEE d MMMM yyyy", { locale: fr });
// "lundi 6 avril 2026"
formatDistanceToNow(new Date(Date.now() - 7200000), {
addSuffix: true,
locale: ja,
});
// "約2時間前"Rango de fecha y comparación:
import {
isWithinInterval,
isBefore,
isAfter,
differenceInCalendarDays,
differenceInBusinessDays,
areIntervalsOverlapping,
} from "date-fns";
const eventStart = new Date("2026-04-10");
const eventEnd = new Date("2026-04-15");
// Comprueba si una fecha está en rango
isWithinInterval(new Date("2026-04-12"), {
start: eventStart,
end: eventEnd,
}); // true
// Días de negocio entre fechas (excluye fines de semana)
differenceInBusinessDays(eventEnd, eventStart); // 3
// Comprueba intervalos superpuestos
areIntervalsOverlapping(
{ start: eventStart, end: eventEnd },
{ start: new Date("2026-04-13"), end: new Date("2026-04-20") }
); // trueAnálisis de cadenas de fecha personalizadas:
import { parse, isValid } from "date-fns";
const date = parse("04/06/2026", "MM/dd/yyyy", new Date());
console.log(isValid(date)); // true
const invalid = parse("not-a-date", "MM/dd/yyyy", new Date());
console.log(isValid(invalid)); // falseNotas de TypeScript
- Todas las funciones date-fns aceptan
Dateonumber(marca de tiempo) como argumentos de fecha - Los tipos de retorno son explícitos:
formatdevuelvestring,addDaysdevuelveDate,differenceInDaysdevuelvenumber - Los tipos de locale se exportan desde
date-fns/locale - No se necesita paquete
@typesadicional; los tipos están incluidos endate-fns
import type { Locale } from "date-fns";
import { fr } from "date-fns/locale";
function formatDate(date: Date | number, locale: Locale = fr): string {
return format(date, "PPP", { locale });
}Trampa potencial
-
Confusión de zona horaria — Los objetos Date siempre están en la zona horaria local.
formatgenera salida de hora local, no UTC. Solución: Usadate-fns-tzpara formateo consciente de zona horaria, o usaformatInTimeZonedesdedate-fns-tz. -
Sensibilidad de mayúsculas en tokens de formato —
MMes mes,mmson minutos.DDno es válido (usadd). Solución: Consulta la tabla de tokens de formato. Tokens comunes:yyyy-MM-dd HH:mm:ss. -
parseISO vs new Date —
new Date("2026-04-06")se analiza como UTC medianoche, pero se muestra en hora local, lo que puede cambiar la fecha. Solución: Siempre usaparseISOpara cadenas ISO para obtener comportamiento consistente. -
Trampa Date mutable — Aunque las funciones date-fns son puras, si pasas la misma instancia
Datea varios lugares después de mutarla en otro lugar, obtendrás resultados inesperados. Solución: Crea nuevas instanciasDateo usa los valores de retorno de funciones date-fns. -
Tamaño del bundle con locales — Importar todos los locales agrega un tamaño de bundle significativo. Solución: Importa solo los locales que necesitas:
import { fr } from "date-fns/locale", noimport * as locales from "date-fns/locale". -
Desincronización de fecha servidor/cliente — Las fechas renderizadas en el servidor usan la zona horaria del servidor, causando desincronizaciones de hidratación. Solución: Formatea fechas solo en el lado del cliente, o pasa cadenas pre-formateadas desde el servidor, o usa
suppressHydrationWarningen el elemento<time>.
Alternativas
| Biblioteca | Mejor para | Compensación |
|---|---|---|
| date-fns | API funcional tree-shakable | Sin soporte de zona horaria sin date-fns-tz |
| dayjs | Remplazo drop-in de Moment.js (2KB) | API basada en plugins, mutable |
| Temporal (proposal) | API de fecha JS nativa futura | Aún no disponible en todos los runtimes |
| Luxon | Soporte completo de zona horaria e i18n | Bundle más grande (alrededor de 20KB) |
| Moment.js | Solo proyectos legacy | Deprecado, 67KB minificado, mutable |
Preguntas frecuentes
¿Por qué debo usar parseISO en lugar de new Date(string)?
new Date("2026-04-06")se analiza como UTC medianoche pero se muestra en hora local, lo que puede cambiar la fecha por un díaparseISOmaneja cadenas ISO 8601 consistentemente entre todos los navegadores- Devuelve una fecha inválida para cadenas no-ISO en lugar de adivinar
¿Cómo formatea una fecha como "hace 2 horas" o "hace 3 días"?
import { formatDistanceToNow } from "date-fns";
formatDistanceToNow(someDate, { addSuffix: true });
// "2 hours ago" or "in 3 days"La opción addSuffix antepone "in" o añade "ago" automáticamente.
Trampa: ¿Por qué MM me da meses pero mm me da minutos?
- Los tokens de formato son sensibles a mayúsculas en date-fns (estándar Unicode TR#35)
MM= mes (01-12),mm= minutos (00-59)DDno es válido -- usaddpara día del mes- Patrón seguro común:
yyyy-MM-dd HH:mm:ss
¿Cómo genero todos los días de un mes para un componente de calendario?
import { startOfMonth, endOfMonth, eachDayOfInterval } from "date-fns";
const start = startOfMonth(new Date());
const end = endOfMonth(new Date());
const days = eachDayOfInterval({ start, end });¿Cómo agrego soporte de locale para formateo de fecha no inglés?
import { format } from "date-fns";
import { fr } from "date-fns/locale";
format(new Date(), "EEEE d MMMM yyyy", { locale: fr });
// "lundi 6 avril 2026"Importa solo los locales que necesitas para mantener el tamaño del bundle pequeño.
¿Cuál es la diferencia entre differenceInDays y differenceInCalendarDays?
differenceInDayscalcula basado en períodos completos de 24 horasdifferenceInCalendarDayscuenta límites de día de calendario cruzados- Ejemplo: 11:00 PM a 1:00 AM es 0 con
differenceInDayspero 1 condifferenceInCalendarDays
¿Cómo compruebo si una fecha cae dentro de un rango?
import { isWithinInterval } from "date-fns";
isWithinInterval(new Date("2026-04-12"), {
start: new Date("2026-04-10"),
end: new Date("2026-04-15"),
}); // trueTrampa: ¿Por qué las fechas renderizadas en el servidor causan desincronizaciones de hidratación?
- El servidor formatea fechas en la zona horaria del servidor
- El cliente re-renderiza en la zona horaria local del usuario, produciendo texto diferente
- Solución: formatea fechas solo en el cliente, pasa cadenas pre-formateadas desde el servidor, o usa
suppressHydrationWarningen el elemento<time>
¿Cómo manejo zonas horarias con date-fns?
- date-fns mismo no tiene soporte de zona horaria -- los objetos Date usan la zona horaria local
- Instala
date-fns-tzpara formateo consciente de zona horaria - Usa
formatInTimeZonepara mostrar fechas en una zona horaria específica - Siempre almacena y transmite fechas como cadenas ISO UTC
¿Cómo escribo una función que acepte tanto Date como número de marca de tiempo en TypeScript?
import type { Locale } from "date-fns";
import { format } from "date-fns";
function formatDate(
date: Date | number,
pattern: string = "PPP",
locale?: Locale
): string {
return format(date, pattern, { locale });
}Todas las funciones date-fns aceptan Date | number como argumentos de fecha.
¿Cómo analizo un formato de cadena de fecha personalizado?
import { parse, isValid } from "date-fns";
const date = parse("04/06/2026", "MM/dd/yyyy", new Date());
console.log(isValid(date)); // trueSiempre valida con isValid ya que parse devuelve un Date inválido para entrada incorrecta.
¿tree-shakea date-fns efectivamente y cómo lo verifico?
- date-fns usa exportaciones de módulo ES por lo que los bundlers pueden eliminar funciones no utilizadas
- Importa funciones individuales:
import { format } from "date-fns"incluye soloformaty sus dependencias - Evita
import * as dateFns from "date-fns"que anula el tree-shaking - Comprueba tu analizador de bundle para confirmar que solo las funciones importadas se incluyen
Relacionado
- React Hooks — Custom hooks para formateo de fechas
- Lodash — Funciones de utilidad que complementan date-fns
- TanStack Query — Almacena en caché respuestas de API dependientes de fecha