Cómo formatear tiempo relativo en React Router v7
Formatea marcas de tiempo como frases de 'hace 2 días'
Problema
Mostrar marcas de tiempo como frases de tiempo relativo como "hace 2 días" o "en 3 horas" hace que el contenido se sienta inmediato y contextual. Sin embargo, estas frases siguen reglas gramaticales complejas que varían drásticamente entre idiomas. El inglés coloca "ago" después de cantidades pasadas e "in" antes de las futuras, pero otros idiomas pueden flexionar la unidad de tiempo en sí, reordenar palabras o usar estructuras gramaticales completamente diferentes. Construir manualmente estas frases con concatenación de cadenas produce resultados incorrectos en todos los idiomas excepto en el que codificaste, rompiendo la experiencia del usuario para audiencias internacionales.
Solución
Usa formateo de tiempo relativo consciente de la configuración regional para convertir diferencias de marcas de tiempo en frases gramaticalmente correctas. Calcula la diferencia de tiempo entre una marca de tiempo dada y el momento actual, luego formatea esa diferencia usando las reglas de configuración regional del usuario. Esto asegura que las expresiones de tiempo pasado y futuro sigan la gramática, el orden de palabras y los patrones de flexión correctos para cada idioma sin manipulación manual de cadenas.
Pasos
1. Crea un helper para calcular valores de tiempo relativo
El componente FormattedRelativeTime requiere un value numérico y una prop unit. Construye una función helper que calcule la diferencia de tiempo y seleccione una unidad apropiada.
export function getRelativeTimeValue(date: Date | number) {
const now = Date.now();
const timestamp = typeof date === "number" ? date : date.getTime();
const diffInSeconds = Math.round((timestamp - now) / 1000);
const minute = 60;
const hour = minute * 60;
const day = hour * 24;
const week = day * 7;
const month = day * 30;
const year = day * 365;
const absDiff = Math.abs(diffInSeconds);
if (absDiff < minute) {
return { value: diffInSeconds, unit: "second" as const };
} else if (absDiff < hour) {
return {
value: Math.round(diffInSeconds / minute),
unit: "minute" as const,
};
} else if (absDiff < day) {
return { value: Math.round(diffInSeconds / hour), unit: "hour" as const };
} else if (absDiff < week) {
return { value: Math.round(diffInSeconds / day), unit: "day" as const };
} else if (absDiff < month) {
return { value: Math.round(diffInSeconds / week), unit: "week" as const };
} else if (absDiff < year) {
return { value: Math.round(diffInSeconds / month), unit: "month" as const };
} else {
return { value: Math.round(diffInSeconds / year), unit: "year" as const };
}
}
Esta función convierte una marca de tiempo en un valor numérico con signo y la unidad de tiempo más apropiada según la magnitud de la diferencia.
2. Crea un componente de tiempo relativo usando FormattedRelativeTime
Usa el componente FormattedRelativeTime de react-intl, que renderiza tiempo relativo formateado y puede actualizarse opcionalmente a intervalos.
import { FormattedRelativeTime } from "react-intl";
import { getRelativeTimeValue } from "./getRelativeTimeValue";
interface RelativeTimeProps {
date: Date | number;
updateIntervalInSeconds?: number;
}
export function RelativeTime({
date,
updateIntervalInSeconds,
}: RelativeTimeProps) {
const { value, unit } = getRelativeTimeValue(date);
return (
<FormattedRelativeTime
value={value}
unit={unit}
numeric="auto"
updateIntervalInSeconds={updateIntervalInSeconds}
/>
);
}
La opción numeric="auto" permite al formateador usar frases como "ayer" en lugar de "hace 1 día" cuando sea apropiado. La prop opcional updateIntervalInSeconds controla con qué frecuencia el componente se vuelve a renderizar para mantener actualizado el tiempo relativo.
3. Usa el componente en una ruta de React Router
Renderiza el componente de tiempo relativo en cualquier componente de ruta donde necesites mostrar marcas de tiempo.
import { RelativeTime } from "./RelativeTime";
interface Post {
id: string;
title: string;
content: string;
createdAt: number;
}
export function PostDetail({ post }: { post: Post }) {
return (
<article>
<h1>{post.title}</h1>
<time dateTime={new Date(post.createdAt).toISOString()}>
<RelativeTime date={post.createdAt} />
</time>
<p>{post.content}</p>
</article>
);
}
El componente formatea automáticamente la marca de tiempo según la configuración regional del usuario, produciendo frases como "hace 2 días" en español, "2 days ago" en inglés o "il y a 2 jours" en francés.
4. Formatea el tiempo relativo de forma imperativa con useIntl
Para casos en los que necesites la cadena formateada directamente (como establecer atributos de elementos), usa la función formatRelativeTime del hook useIntl.
import { useIntl } from "react-intl";
import { getRelativeTimeValue } from "./getRelativeTimeValue";
interface CommentProps {
author: string;
text: string;
timestamp: number;
}
export function Comment({ author, text, timestamp }: CommentProps) {
const intl = useIntl();
const { value, unit } = getRelativeTimeValue(timestamp);
const relativeTime = intl.formatRelativeTime(value, unit, {
numeric: "auto",
});
return (
<div aria-label={`Comment by ${author}, posted ${relativeTime}`}>
<strong>{author}</strong>
<p>{text}</p>
</div>
);
}
Este enfoque te proporciona una cadena formateada que puedes usar en atributos, concatenar con otro texto o pasar a APIs que no sean de React, manteniendo el soporte completo de configuración regional.