¿Cómo formatear 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, secciones de comentarios y 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 comprender rápidamente cuándo ocurrió algo sin necesidad de analizar una fecha absoluta.
Cuando codificas estas cadenas directamente en inglés, asumes que todos los usuarios hablan inglés y siguen las reglas gramaticales del inglés. Diferentes idiomas tienen distintas formas de expresar el tiempo relativo. El español dice "hace 3 días" en lugar de "3 days ago". El japonés usa "3日前" con una estructura completamente diferente. Cada idioma también tiene reglas de pluralización únicas que determinan cuándo usar formas singulares versus plurales.
JavaScript proporciona la API Intl.RelativeTimeFormat para manejar el formato de tiempo relativo automáticamente. Esta lección explica cómo formatear tiempos relativos correctamente para cualquier idioma utilizando esta API integrada.
Por qué el formato de tiempo relativo necesita internacionalización
Diferentes idiomas expresan el tiempo relativo de distintas maneras. El inglés coloca la unidad de tiempo antes de "ago" para tiempos pasados y después de "in" para tiempos futuros. Otros idiomas utilizan diferentes órdenes de palabras, 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 produce un resultado que suena natural y sigue sus propias convenciones. No necesitas conocer estas convenciones ni mantener archivos de traducción. La API maneja automáticamente todos los detalles de formato.
Las reglas de pluralización también varían significativamente entre idiomas. El inglés distingue entre "1 day" y "2 days". El árabe tiene seis formas plurales diferentes dependiendo del conteo. El japonés usa la misma forma independientemente de la cantidad. La API Intl.RelativeTimeFormat aplica las reglas de pluralización correctas para cada idioma.
La API Intl.RelativeTimeFormat
El constructor Intl.RelativeTimeFormat crea un formateador que convierte valores numéricos y unidades de tiempo en cadenas de texto localizadas. Se pasa un identificador de localización como primer argumento, luego se llama 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() toma 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 números positivos indican tiempos futuros. Esta convención hace que la API sea intuitiva de usar una vez que se comprende la convención de signos.
Formateo de 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 valores positivos producen tiempos 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 en todas las unidades de tiempo y en todos los idiomas. La API selecciona automáticamente la estructura gramatical correcta 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 las necesidades de formateo de tiempo relativo. Puedes usar formas singulares o plurales, y ambas funcionan de manera idéntica.
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 como formas plurales como days. Ambas producen resultados idénticos. La unidad trimestre (quarter) es útil para aplicaciones empresariales que trabajan con períodos fiscales.
Uso del lenguaje natural con numeric auto
La opción numeric controla si el formateador utiliza números o alternativas en 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"
Configurar numeric como auto produce frases más naturales 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 se sientan más conversacionales. Los usuarios ven "ayer" en lugar de "hace 1 día", lo que se lee de forma más natural. La opción auto funciona en todas las unidades de tiempo y en todos los idiomas, proporcionando cada idioma sus propias alternativas idiomáticas.
Elección de un estilo de formato
La opción style controla la verbosidad 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 para la mayoría de las interfaces. El estilo short ahorra espacio en diseños móviles o tablas. El estilo narrow produce la salida más compacta para diseños extremadamente limitados en espacio.
Cálculo de diferencias de tiempo
La API Intl.RelativeTimeFormat formatea valores pero no los calcula. Debes calcular la diferencia de tiempo por tu cuenta y luego pasar el resultado al formateador.
Para calcular una diferencia de tiempo, resta la fecha objetivo de la fecha actual, 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. El cálculo divide los milisegundos por el número de milisegundos en un día, y luego 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 esperada por el método format().
Construyendo una función de utilidad completa
Para un formateador de tiempo relativo de uso general, necesitas seleccionar la unidad de tiempo más apropiada basada en 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 itera a través de unidades de tiempo desde la más grande hasta la más pequeña, seleccionando la primera unidad donde la diferencia absoluta excede el valor en milisegundos de la unidad. El respaldo a segundos asegura que la función siempre devuelva un resultado.
Las definiciones de unidad utilizan 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 visualizaciones de tiempo relativo donde los valores aproximados son más útiles que la precisión exacta.
Formato para la configuración regional del usuario
En lugar de codificar una configuración regional específica, puedes utilizar el idioma preferido del 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'));
// La salida varía según la configuración regional del usuario
// Para en-US: "yesterday"
// Para es-ES: "ayer"
// Para fr-FR: "hier"
// Para de-DE: "gestern"
Este enfoque muestra tiempos relativos de acuerdo con las preferencias de idioma de cada usuario sin requerir selección manual de configuración regional. El navegador proporciona la preferencia de idioma, y la API aplica las convenciones de formato apropiadas.
Viendo el mismo tiempo en diferentes idiomas
El mismo valor de tiempo relativo produce diferentes salidas para diferentes configuraciones regionales. Cada idioma sigue sus propias convenciones para el orden de las 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 produce una salida que suena natural y que los hablantes nativos usarían en una conversación. La API maneja toda la complejidad de las diferentes estructuras gramaticales, diferentes sistemas de escritura y diferentes direcciones de texto.
Reutilización de formateadores para mejorar el rendimiento
Crear una instancia de Intl.RelativeTimeFormat implica cargar datos de configuración regional y procesar opciones. Cuando se formatean múltiples marcas de tiempo, es mejor crear el formateador una vez y reutilizarlo.
const rtf = new Intl.RelativeTimeFormat('en-US', { numeric: 'auto' });
const timestamps = [
new Date(Date.now() - 5 * 60 * 1000), // hace 5 minutos
new Date(Date.now() - 2 * 60 * 60 * 1000), // hace 2 horas
new Date(Date.now() - 24 * 60 * 60 * 1000) // hace 1 día
];
timestamps.forEach(date => {
const diffInMs = date - new Date();
const diffInMinutes = Math.round(diffInMs / (60 * 1000));
console.log(rtf.format(diffInMinutes, 'minute'));
});
Este enfoque es más eficiente que crear un nuevo formateador para cada marca de tiempo. La diferencia de rendimiento se vuelve significativa cuando se formatean cientos o miles de marcas de tiempo en feeds de actividad o hilos de comentarios.
Uso de tiempos relativos en 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 desde que algo ocurrió ayude a los usuarios 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 cualquier otro contexto donde muestres información a los usuarios.