¿Cómo se formatean los tiempos relativos como hace 3 días o en 2 horas?

Utiliza Intl.RelativeTimeFormat para mostrar tiempos como hace 3 días o en 2 horas en cualquier idioma, con pluralización y localización automáticas.

Introducción

Los feeds de redes sociales, las secciones de comentarios y los registros de actividad muestran marcas de tiempo como "hace 5 minutos", "hace 2 horas" o "en 3 días". Estas marcas de tiempo relativas ayudan a los usuarios a entender rápidamente cuándo ocurrió algo sin tener que interpretar una fecha absoluta.

Si estas frases se escriben directamente en inglés, se asume que todos los usuarios hablan inglés y siguen sus reglas gramaticales. Cada idioma tiene formas distintas para expresar el tiempo relativo. En español, se dice "hace 3 días" en vez de "3 days ago". En japonés, se utiliza "3日前", con una estructura totalmente diferente. Además, cada idioma tiene reglas propias de pluralización para decidir cuándo usar el singular o el plural.

JavaScript ofrece la API Intl.RelativeTimeFormat para gestionar automáticamente el formato de los tiempos relativos. En esta lección aprenderás a formatear correctamente los tiempos relativos en cualquier idioma usando esta API incorporada.

Por qué el formateo de tiempos relativos necesita internacionalización

Cada idioma expresa el tiempo relativo de manera diferente. En inglés, la unidad de tiempo va antes de "ago" para el pasado y después de "in" para el futuro. Otros idiomas usan órdenes de palabras distintos, diferentes preposiciones o estructuras gramaticales completamente distintas.

const rtfEnglish = new Intl.RelativeTimeFormat('en');
console.log(rtfEnglish.format(-3, 'day'));
// "3 days ago"

const rtfSpanish = new Intl.RelativeTimeFormat('es');
console.log(rtfSpanish.format(-3, 'day'));
// "hace 3 días"

const rtfJapanese = new Intl.RelativeTimeFormat('ja');
console.log(rtfJapanese.format(-3, 'day'));
// "3 日前"

Cada idioma genera frases que suenan naturales según sus propias convenciones. No necesitas conocer ni mantener esas reglas ni archivos de traducción: la API se encarga de todos los detalles automáticamente.

Las reglas de pluralización también varían mucho entre los idiomas. En inglés se diferencia entre "1 day" y "2 days". El árabe tiene hasta seis formas plurales dependiendo del número. En japonés la forma es siempre igual, sin importar la cantidad. La API Intl.RelativeTimeFormat aplica automáticamente la pluralización correcta según el idioma.

La API Intl.RelativeTimeFormat

El constructor Intl.RelativeTimeFormat crea un formateador que convierte valores numéricos y unidades de tiempo en cadenas localizadas. Pasas un identificador de idioma como primer argumento, y luego llamas al método format() con un valor y una unidad.

const rtf = new Intl.RelativeTimeFormat('en-US');

console.log(rtf.format(-1, 'day'));
// "1 day ago"

console.log(rtf.format(2, 'hour'));
// "in 2 hours"

El método format() recibe dos parámetros. El primero es un número que representa la cantidad de tiempo. El segundo es una cadena que especifica la unidad de tiempo.

Los números negativos indican tiempos pasados, mientras que los positivos indican tiempos futuros. Esta convención hace que la API sea intuitiva de usar una vez entiendes cómo funciona el signo.

Formateando tiempos pasados y futuros

El signo del valor determina si el tiempo está en el pasado o en el futuro. Los valores negativos producen tiempos en pasado, mientras que los positivos en futuro.

const rtf = new Intl.RelativeTimeFormat('en-US');

console.log(rtf.format(-5, 'minute'));
// "5 minutes ago"

console.log(rtf.format(5, 'minute'));
// "in 5 minutes"

console.log(rtf.format(-2, 'week'));
// "2 weeks ago"

console.log(rtf.format(2, 'week'));
// "in 2 weeks"

Este patrón funciona de manera consistente con todas las unidades de tiempo y todos los idiomas. La API selecciona automáticamente la estructura gramatical adecuada según si el valor es positivo o negativo.

Unidades de tiempo disponibles

La API admite ocho unidades de tiempo que cubren la mayoría de necesidades en el formateo de tiempo relativo. Puedes usar tanto la forma singular como plural y ambas funcionan igual.

const rtf = new Intl.RelativeTimeFormat('en-US');

console.log(rtf.format(-30, 'second'));
// "30 seconds ago"

console.log(rtf.format(-15, 'minute'));
// "15 minutes ago"

console.log(rtf.format(-6, 'hour'));
// "6 hours ago"

console.log(rtf.format(-3, 'day'));
// "3 days ago"

console.log(rtf.format(-2, 'week'));
// "2 weeks ago"

console.log(rtf.format(-4, 'month'));
// "4 months ago"

console.log(rtf.format(-1, 'quarter'));
// "1 quarter ago"

console.log(rtf.format(-2, 'year'));
// "2 years ago"

La API acepta tanto formas singulares como day y plurales como days. Ambas producen el mismo resultado. La unidad de trimestre es útil para aplicaciones empresariales que trabajan con períodos fiscales.

Uso de lenguaje natural con auto numérico

La opción numeric controla si el formateador usa números o alternativas con lenguaje natural. El valor predeterminado es always, que siempre muestra números.

const rtfAlways = new Intl.RelativeTimeFormat('en-US', {
  numeric: 'always'
});

console.log(rtfAlways.format(-1, 'day'));
// "1 day ago"

console.log(rtfAlways.format(0, 'day'));
// "in 0 days"

console.log(rtfAlways.format(1, 'day'));
// "in 1 day"

Al establecer numeric en auto se obtiene una redacción más natural para ciertos valores.

const rtfAuto = new Intl.RelativeTimeFormat('en-US', {
  numeric: 'auto'
});

console.log(rtfAuto.format(-1, 'day'));
// "yesterday"

console.log(rtfAuto.format(0, 'day'));
// "today"

console.log(rtfAuto.format(1, 'day'));
// "tomorrow"

Esta opción hace que las interfaces resulten más conversacionales. Los usuarios ven "ayer" en lugar de "hace 1 día", lo que se percibe de manera más natural. La opción auto funciona con todas las unidades de tiempo y en todos los idiomas, ya que cada idioma aporta sus propias alternativas idiomáticas.

Eligiendo un estilo de formato

La opción style controla el nivel de detalle de la salida. Los tres estilos disponibles son long, short y narrow.

const rtfLong = new Intl.RelativeTimeFormat('en-US', {
  style: 'long'
});
console.log(rtfLong.format(-2, 'hour'));
// "2 hours ago"

const rtfShort = new Intl.RelativeTimeFormat('en-US', {
  style: 'short'
});
console.log(rtfShort.format(-2, 'hour'));
// "2 hr. ago"

const rtfNarrow = new Intl.RelativeTimeFormat('en-US', {
  style: 'narrow'
});
console.log(rtfNarrow.format(-2, 'hour'));
// "2h ago"

El estilo long es el predeterminado y funciona bien en la mayoría de las interfaces. El estilo short ahorra espacio en diseños móviles o en tablas. El estilo narrow ofrece la salida más compacta para diseños con espacio extremadamente limitado.

Cómo calcular diferencias de tiempo

La API Intl.RelativeTimeFormat da formato a los valores pero no los calcula. Debes calcular tú mismo la diferencia de tiempo y luego pasar el resultado al formateador.

Para calcular una diferencia de tiempo, resta la fecha objetivo de la fecha actual y luego convierte el resultado de milisegundos a la unidad deseada.

const rtf = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' });

function formatDaysAgo(date) {
  const now = new Date();
  const diffInMs = date - now;
  const diffInDays = Math.round(diffInMs / (1000 * 60 * 60 * 24));

  return rtf.format(diffInDays, 'day');
}

const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);

console.log(formatDaysAgo(yesterday));
// "yesterday"

const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);

console.log(formatDaysAgo(tomorrow));
// "tomorrow"

Esta función calcula la diferencia en días entre la fecha objetivo y el momento actual. La operación divide los milisegundos entre el número de milisegundos de un día y redondea al número entero más cercano.

La resta date - now produce un valor negativo para fechas pasadas y un valor positivo para fechas futuras. Esto coincide con la convención de signos que espera el método format().

Cómo crear una función utilitaria completa

Para un formateador de tiempo relativo de uso general, debes elegir la unidad de tiempo más adecuada según la magnitud de la diferencia de tiempo.

const rtf = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' });

const units = {
  year: 24 * 60 * 60 * 1000 * 365,
  month: 24 * 60 * 60 * 1000 * 365 / 12,
  week: 24 * 60 * 60 * 1000 * 7,
  day: 24 * 60 * 60 * 1000,
  hour: 60 * 60 * 1000,
  minute: 60 * 1000,
  second: 1000
};

function formatRelativeTime(date) {
  const now = new Date();
  const diffInMs = date - now;
  const absDiff = Math.abs(diffInMs);

  for (const [unit, msValue] of Object.entries(units)) {
    if (absDiff >= msValue || unit === 'second') {
      const value = Math.round(diffInMs / msValue);
      return rtf.format(value, unit);
    }
  }
}

const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
console.log(formatRelativeTime(fiveMinutesAgo));
// "5 minutes ago"

const threeDaysAgo = new Date(Date.now() - 3 * 24 * 60 * 60 * 1000);
console.log(formatRelativeTime(threeDaysAgo));
// "3 days ago"

const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000);
console.log(formatRelativeTime(tomorrow));
// "tomorrow"

Esta función recorre las unidades de tiempo de mayor a menor, seleccionando la primera unidad en la que la diferencia absoluta supera el valor de milisegundos de la unidad. El uso de segundos como alternativa garantiza que la función siempre devuelva un resultado.

Las definiciones de las unidades usan valores aproximados. Los meses se calculan como 1/12 de un año en lugar de tener en cuenta las diferentes longitudes de los meses. Esta aproximación funciona bien para mostrar tiempos relativos, ya que los valores aproximados suelen ser más útiles que la precisión exacta.

Formateo según el idioma del usuario

En vez de establecer una configuración regional específica, puedes usar el idioma preferido por el usuario desde el navegador.

const userLocale = navigator.language;
const rtf = new Intl.RelativeTimeFormat(userLocale, { numeric: 'auto' });

const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
console.log(rtf.format(-1, 'day'));
// Output varies by user's locale
// For en-US: "yesterday"
// For es-ES: "ayer"
// For fr-FR: "hier"
// For de-DE: "gestern"

Este método muestra los tiempos relativos según las preferencias de idioma de cada usuario, sin que tengas que seleccionar la configuración regional manualmente. El navegador indica el idioma preferido y la API aplica los formatos adecuados.

Ver la misma hora en diferentes idiomas

El mismo valor de tiempo relativo produce resultados distintos según la configuración regional. Cada idioma sigue sus propias reglas de orden de palabras, gramática y pluralización.

const threeDaysAgo = -3;

const rtfEnglish = new Intl.RelativeTimeFormat('en-US');
console.log(rtfEnglish.format(threeDaysAgo, 'day'));
// "3 days ago"

const rtfSpanish = new Intl.RelativeTimeFormat('es-ES');
console.log(rtfSpanish.format(threeDaysAgo, 'day'));
// "hace 3 días"

const rtfFrench = new Intl.RelativeTimeFormat('fr-FR');
console.log(rtfFrench.format(threeDaysAgo, 'day'));
// "il y a 3 jours"

const rtfGerman = new Intl.RelativeTimeFormat('de-DE');
console.log(rtfGerman.format(threeDaysAgo, 'day'));
// "vor 3 Tagen"

const rtfJapanese = new Intl.RelativeTimeFormat('ja-JP');
console.log(rtfJapanese.format(threeDaysAgo, 'day'));
// "3 日前"

const rtfArabic = new Intl.RelativeTimeFormat('ar-SA');
console.log(rtfArabic.format(threeDaysAgo, 'day'));
// "قبل 3 أيام"

Cada idioma genera resultados que suenan naturales y que los hablantes nativos usarían en una conversación. La API se encarga de toda la complejidad de distintas estructuras gramaticales, sistemas de escritura y direcciones del texto.

Reutilizar formateadores para mejorar el rendimiento

Crear una instancia de Intl.RelativeTimeFormat implica cargar datos del idioma y procesar opciones. Si vas a formatear varias marcas de tiempo, crea el formateador una vez y reutilízalo.

const rtf = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' });

const timestamps = [
  new Date(Date.now() - 5 * 60 * 1000),      // 5 minutes ago
  new Date(Date.now() - 2 * 60 * 60 * 1000), // 2 hours ago
  new Date(Date.now() - 24 * 60 * 60 * 1000) // 1 day ago
];

timestamps.forEach(date => {
  const diffInMs = date - new Date();
  const diffInMinutes = Math.round(diffInMs / (60 * 1000));
  console.log(rtf.format(diffInMinutes, 'minute'));
});

Este método es más eficiente que crear un formateador nuevo para cada marca de tiempo. La diferencia de rendimiento se vuelve significativa cuando necesitas mostrar cientos o miles de marcas de tiempo en fuentes de actividad o hilos de comentarios.

Usar tiempos relativos en las interfaces

Puedes utilizar el formato de tiempo relativo en cualquier lugar donde muestres marcas de tiempo a los usuarios. Esto incluye feeds de redes sociales, secciones de comentarios, registros de actividad, sistemas de notificaciones y cualquier interfaz donde mostrar cuánto tiempo ha pasado ayude a entender el contexto.

const rtf = new Intl.RelativeTimeFormat(navigator.language, {
  numeric: 'auto'
});

function updateTimestamp(element, date) {
  const now = new Date();
  const diffInMs = date - now;
  const diffInMinutes = Math.round(diffInMs / (60 * 1000));

  element.textContent = rtf.format(diffInMinutes, 'minute');
}

const commentDate = new Date('2025-10-15T14:30:00');
const timestampElement = document.getElementById('comment-timestamp');

updateTimestamp(timestampElement, commentDate);

Las cadenas formateadas funcionan como cualquier otro valor de cadena. Puedes insertarlas en contenido de texto, atributos o en cualquier otro contexto donde muestres información a los usuarios.