Cómo formatear tiempo relativo en Next.js (Pages Router) v16

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 patrones gramaticales complejos 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 usar diferentes órdenes de palabras, flexionar unidades de tiempo según la cantidad o emplear estructuras gramaticales completamente diferentes. Construir manualmente estas frases con concatenación de cadenas produce resultados incorrectos en todos los idiomas excepto en el que fue escrito, rompiendo la experiencia del usuario para audiencias internacionales.

Solución

Usa el formateo de tiempo relativo de react-intl para convertir diferencias de tiempo numéricas en frases gramaticalmente correctas para la configuración regional del usuario. Calcula la diferencia de tiempo entre una marca de tiempo y el momento actual, determina la unidad apropiada (segundos, minutos, horas, días) y pasa ambos valores a las APIs de formateo de react-intl. La API subyacente Intl.RelativeTimeFormat maneja automáticamente la gramática específica de la configuración regional, el orden de las palabras y las reglas de flexión.

Pasos

1. Crea una función auxiliar para calcular valores de tiempo relativo

La diferencia de tiempo debe convertirse en un valor numérico y una unidad apropiada. Construye una función que compare una marca de tiempo con el tiempo actual y seleccione la mejor unidad.

export function getRelativeTimeValue(date: Date | number) {
  const now = Date.now();
  const timestamp = typeof date === "number" ? date : date.getTime();
  const diffInSeconds = (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: Math.round(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 devuelve un valor numérico con signo (negativo para el pasado, positivo para el futuro) y la unidad más apropiada según la magnitud de la diferencia.

2. Formatea el tiempo relativo con el hook useIntl

La función formatRelativeTime devuelve una cadena de tiempo relativo formateada y espera un valor numérico, una unidad y opciones que se ajusten a Intl.RelativeTimeFormatOptions. Usa la función auxiliar del paso 1 para obtener el valor y la unidad, luego formatéalo.

import { useIntl } from "react-intl";
import { getRelativeTimeValue } from "@/lib/relative-time";

export default function CommentTimestamp({ date }: { date: Date }) {
  const intl = useIntl();
  const { value, unit } = getRelativeTimeValue(date);

  return (
    <time dateTime={date.toISOString()}>
      {intl.formatRelativeTime(value, unit)}
    </time>
  );
}

El método formatRelativeTime produce frases apropiadas para la configuración regional como "hace 2 días" en español o "il y a 2 jours" en francés.

3. Usar FormattedRelativeTime para formato declarativo

FormattedRelativeTime implementa funciones avanzadas como actualización en tiempo real y utiliza la API formatRelativeTime con props para valor, unidad y opciones de formato relativo. Para componentes que necesitan actualizaciones automáticas, usar la forma de componente.

import { FormattedRelativeTime } from "react-intl";
import { getRelativeTimeValue } from "@/lib/relative-time";

export default function LiveTimestamp({ date }: { date: Date }) {
  const { value, unit } = getRelativeTimeValue(date);

  return (
    <time dateTime={date.toISOString()}>
      <FormattedRelativeTime
        value={value}
        unit={unit}
        numeric="auto"
        updateIntervalInSeconds={10}
      />
    </time>
  );
}

La prop updateIntervalInSeconds controla la frecuencia con la que el componente se vuelve a renderizar, manteniendo actualizada la frase mostrada a medida que pasa el tiempo.

4. Personalizar el estilo de formato

El componente acepta RelativeTimeFormatOptions incluyendo las propiedades numeric y style. Ajustar estas opciones para controlar el formato de salida.

import { FormattedRelativeTime } from "react-intl";
import { getRelativeTimeValue } from "@/lib/relative-time";

export default function CompactTimestamp({ date }: { date: Date }) {
  const { value, unit } = getRelativeTimeValue(date);

  return (
    <FormattedRelativeTime
      value={value}
      unit={unit}
      numeric="auto"
      style="narrow"
    />
  );
}

Establecer numeric="auto" produce frases como "ayer" en lugar de "hace 1 día" cuando sea apropiado, y style="narrow" crea formas más cortas como "hace 2 m" en lugar de "hace 2 meses".