API Intl.RelativeTimeFormat
Formatea cadenas de tiempo relativo en JavaScript con soporte completo de internacionalización
Introducción
Mostrar marcas de tiempo relativas como "hace 3 horas" o "en 2 días" es un requisito común en aplicaciones web. Los feeds de redes sociales, secciones de comentarios, sistemas de notificaciones y registros de actividad necesitan mostrar el tiempo de forma legible para humanos que se actualice a medida que el contenido envejece.
Crear esta funcionalidad desde cero presenta desafíos. Necesitas calcular diferencias de tiempo, seleccionar unidades apropiadas, manejar reglas de pluralización en diferentes idiomas y mantener traducciones para cada localización soportada. Esta complejidad explica por qué los desarrolladores tradicionalmente recurrían a bibliotecas como Moment.js, añadiendo un tamaño significativo al paquete para lo que parece un formateo simple.
La API Intl.RelativeTimeFormat proporciona una solución nativa. Formatea cadenas de tiempo relativo con soporte completo de internacionalización, manejando automáticamente reglas de pluralización y convenciones culturales. La API funciona en todos los navegadores principales con una cobertura global del 95%, eliminando la necesidad de dependencias externas mientras produce resultados que suenan naturales en docenas de idiomas.
Uso básico
El constructor Intl.RelativeTimeFormat crea una instancia de formateador que convierte valores numéricos y unidades de tiempo en cadenas localizadas.
const rtf = new Intl.RelativeTimeFormat('en');
console.log(rtf.format(-1, 'day'));
// "hace 1 día"
console.log(rtf.format(2, 'hour'));
// "en 2 horas"
console.log(rtf.format(-3, 'month'));
// "hace 3 meses"
El método format() toma dos parámetros:
value: Un número que indica la cantidad de tiempounit: Una cadena que especifica la unidad de tiempo
Los valores negativos indican tiempos pasados, los valores positivos indican tiempos futuros. La API maneja automáticamente la pluralización, produciendo "hace 1 día" o "hace 2 días" según el valor.
Unidades de tiempo soportadas
La API soporta ocho unidades de tiempo, cada una aceptando formas singulares y plurales:
const rtf = new Intl.RelativeTimeFormat('en');
// Estos producen resultados idénticos
console.log(rtf.format(-5, 'second'));
// "hace 5 segundos"
console.log(rtf.format(-5, 'seconds'));
// "hace 5 segundos"
Unidades disponibles de menor a mayor:
secondosecondsminuteominuteshourohoursdayodaysweekoweeksmonthomonthsquarteroquartersyearoyears
La unidad trimestre resulta útil en aplicaciones empresariales que hacen seguimiento de períodos fiscales, mientras que las otras cubren las necesidades típicas de formateo de tiempo relativo.
Salida en lenguaje natural
La opción numeric controla si el formateador utiliza valores numéricos o alternativas en lenguaje natural.
const rtfNumeric = new Intl.RelativeTimeFormat('en', {
numeric: 'always'
});
console.log(rtfNumeric.format(-1, 'day'));
// "1 day ago"
console.log(rtfNumeric.format(0, 'day'));
// "in 0 days"
console.log(rtfNumeric.format(1, 'day'));
// "in 1 day"
Configurar numeric como auto produce frases más idiomáticas para valores comunes:
const rtfAuto = new Intl.RelativeTimeFormat('en', {
numeric: 'auto'
});
console.log(rtfAuto.format(-1, 'day'));
// "yesterday"
console.log(rtfAuto.format(0, 'day'));
// "today"
console.log(rtfAuto.format(1, 'day'));
// "tomorrow"
Esta salida en lenguaje natural crea interfaces más conversacionales. La opción auto funciona con todas las unidades de tiempo, aunque el efecto es más notable con los días. Otros idiomas tienen sus propias alternativas idiomáticas que la API maneja automáticamente.
Estilos de formateo
La opción style ajusta la verbosidad de la salida para diferentes contextos de interfaz:
const rtfLong = new Intl.RelativeTimeFormat('en', {
style: 'long'
});
console.log(rtfLong.format(-2, 'hour'));
// "2 hours ago"
const rtfShort = new Intl.RelativeTimeFormat('en', {
style: 'short'
});
console.log(rtfShort.format(-2, 'hour'));
// "2 hr. ago"
const rtfNarrow = new Intl.RelativeTimeFormat('en', {
style: 'narrow'
});
console.log(rtfNarrow.format(-2, 'hour'));
// "2h ago"
Utilice el estilo long (el predeterminado) para interfaces estándar donde la legibilidad es lo más importante. Utilice el estilo short para diseños con restricciones de espacio como interfaces móviles o tablas de datos. Utilice el estilo narrow para pantallas extremadamente compactas donde cada carácter cuenta.
Cálculo de diferencias de tiempo
La API Intl.RelativeTimeFormat formatea valores pero no los calcula. Debe calcular las diferencias de tiempo y seleccionar las unidades apropiadas usted mismo. Esta separación de responsabilidades le da control sobre la lógica de cálculo mientras delega la complejidad de formateo a la API.
Cálculo básico de diferencia de tiempo
Para una unidad de tiempo específica, calcula la diferencia entre dos fechas:
const rtf = new Intl.RelativeTimeFormat('en', { 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 nextWeek = new Date();
nextWeek.setDate(nextWeek.getDate() + 7);
console.log(formatDaysAgo(nextWeek));
// "in 7 days"
Este enfoque funciona cuando sabes qué unidad tiene sentido para tu caso de uso. Las marcas de tiempo de comentarios podrían usar siempre horas o días, mientras que la programación de eventos podría centrarse en días o semanas.
Selección automática de unidades
Para el formateo de tiempo relativo de propósito general, selecciona la unidad más apropiada basada en la magnitud de la diferencia de tiempo:
const rtf = new Intl.RelativeTimeFormat('en', { 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"
Esta implementación itera a través de las unidades desde la más grande hasta la más pequeña, seleccionando la primera unidad donde la diferencia de tiempo excede el valor en milisegundos de la unidad. El respaldo a segundos asegura que la función siempre devuelva un resultado.
Los umbrales de unidad utilizan valores aproximados. Los meses se calculan como 1/12 de un año en lugar de tener en cuenta las longitudes variables de los meses. Esta aproximación funciona bien para visualizaciones de tiempo relativo donde la precisión importa menos que la legibilidad.
Soporte de internacionalización
El formateador respeta las convenciones específicas del idioma para la visualización de tiempo relativo. Diferentes idiomas tienen diferentes reglas de pluralización, diferentes órdenes de palabras y diferentes expresiones idiomáticas.
const rtfEnglish = new Intl.RelativeTimeFormat('en', {
numeric: 'auto'
});
console.log(rtfEnglish.format(-1, 'day'));
// "yesterday"
const rtfSpanish = new Intl.RelativeTimeFormat('es', {
numeric: 'auto'
});
console.log(rtfSpanish.format(-1, 'day'));
// "ayer"
const rtfJapanese = new Intl.RelativeTimeFormat('ja', {
numeric: 'auto'
});
console.log(rtfJapanese.format(-1, 'day'));
// "昨日"
Las reglas de pluralización varían significativamente entre idiomas. El inglés distingue entre uno y muchos (1 day vs 2 days). El árabe tiene seis formas plurales dependiendo del conteo. El japonés utiliza la misma forma independientemente de la cantidad. La API maneja estas complejidades automáticamente.
const rtfArabic = new Intl.RelativeTimeFormat('ar');
console.log(rtfArabic.format(-1, 'day'));
// "قبل يوم واحد"
console.log(rtfArabic.format(-2, 'day'));
// "قبل يومين"
console.log(rtfArabic.format(-3, 'day'));
// "قبل 3 أيام"
console.log(rtfArabic.format(-11, 'day'));
// "قبل 11 يومًا"
El formateador también maneja la dirección del texto para idiomas de derecha a izquierda y aplica convenciones de formato culturalmente apropiadas. Esta localización automática elimina la necesidad de mantener archivos de traducción o implementar lógica de pluralización personalizada.
Formateo avanzado con formatToParts
El método formatToParts() devuelve la cadena formateada como un array de objetos, permitiendo la personalización de estilo o manipulación de componentes individuales.
const rtf = new Intl.RelativeTimeFormat('en');
const parts = rtf.formatToParts(-5, 'second');
console.log(parts);
// [
// { type: 'integer', value: '5', unit: 'second' },
// { type: 'literal', value: ' seconds ago' }
// ]
Cada objeto parte contiene:
type: Ya seaintegerpara valores numéricos oliteralpara textovalue: El contenido de texto de esta parteunit: La unidad de tiempo (presente en partes de tipo integer)
Esta estructura permite renderizado personalizado donde podrías querer aplicar estilos diferentes a números y texto, o extraer componentes específicos para su visualización:
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
function formatWithStyledNumber(value, unit) {
const parts = rtf.formatToParts(value, unit);
return parts.map(part => {
if (part.type === 'integer') {
return `<strong>${part.value}</strong>`;
}
return part.value;
}).join('');
}
console.log(formatWithStyledNumber(-5, 'hour'));
// "<strong>5</strong> hours ago"
Cuando se usa numeric: 'auto' con valores que tienen alternativas en lenguaje natural, formatToParts() devuelve una única parte literal:
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
const parts = rtf.formatToParts(-1, 'day');
console.log(parts);
// [
// { type: 'literal', value: 'yesterday' }
// ]
Este comportamiento te permite detectar cuándo se utiliza lenguaje natural versus formateo numérico, permitiéndote aplicar diferentes estilos o comportamientos basados en el tipo de salida.
Optimización de rendimiento
Crear instancias de Intl.RelativeTimeFormat implica cargar datos de localización e inicializar reglas de formato. Esta operación es lo suficientemente costosa como para evitar repetirla innecesariamente.
Almacenar en caché las instancias del formateador
Crea formateadores una vez y reutilízalos:
const formatterCache = new Map();
function getFormatter(locale, options = {}) {
const cacheKey = `${locale}-${JSON.stringify(options)}`;
if (!formatterCache.has(cacheKey)) {
formatterCache.set(
cacheKey,
new Intl.RelativeTimeFormat(locale, options)
);
}
return formatterCache.get(cacheKey);
}
// Reutiliza formateadores en caché
const rtf = getFormatter('en', { numeric: 'auto' });
console.log(rtf.format(-1, 'day'));
// "yesterday"
Esta estrategia de almacenamiento en caché se vuelve importante cuando se formatean muchas marcas de tiempo, como al renderizar feeds de actividad o hilos de comentarios.
Minimizar la sobrecarga de cálculo
Almacena marcas de tiempo en lugar de calcular tiempos relativos repetidamente:
// Almacena la fecha de creación
const comment = {
text: "Great article!",
createdAt: new Date('2025-10-14T10:30:00Z')
};
// Calcula el tiempo relativo solo al renderizar
function renderComment(comment, locale) {
const rtf = getFormatter(locale, { numeric: 'auto' });
const units = {
day: 24 * 60 * 60 * 1000,
hour: 60 * 60 * 1000,
minute: 60 * 1000,
second: 1000
};
const diffInMs = comment.createdAt - new Date();
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);
}
}
}
Este enfoque separa el almacenamiento de datos de la presentación, permitiéndote recalcular tiempos relativos cuando cambia la configuración regional del usuario o cuando se actualizan las renderizaciones sin modificar los datos subyacentes.
Implementación práctica
Combinar la lógica de cálculo con el formateo produce una función de utilidad reutilizable adecuada para aplicaciones en producción:
class RelativeTimeFormatter {
constructor(locale = 'en', options = { numeric: 'auto' }) {
this.formatter = new Intl.RelativeTimeFormat(locale, options);
this.units = [
{ name: 'year', ms: 24 * 60 * 60 * 1000 * 365 },
{ name: 'month', ms: 24 * 60 * 60 * 1000 * 365 / 12 },
{ name: 'week', ms: 24 * 60 * 60 * 1000 * 7 },
{ name: 'day', ms: 24 * 60 * 60 * 1000 },
{ name: 'hour', ms: 60 * 60 * 1000 },
{ name: 'minute', ms: 60 * 1000 },
{ name: 'second', ms: 1000 }
];
}
format(date) {
const now = new Date();
const diffInMs = date - now;
const absDiff = Math.abs(diffInMs);
for (const unit of this.units) {
if (absDiff >= unit.ms || unit.name === 'second') {
const value = Math.round(diffInMs / unit.ms);
return this.formatter.format(value, unit.name);
}
}
}
}
// Uso
const formatter = new RelativeTimeFormatter('en');
const fiveMinutesAgo = new Date(Date.now() - 5 * 60 * 1000);
console.log(formatter.format(fiveMinutesAgo));
// "5 minutes ago"
const tomorrow = new Date(Date.now() + 24 * 60 * 60 * 1000);
console.log(formatter.format(tomorrow));
// "tomorrow"
Esta clase encapsula tanto el formateador como la lógica de selección de unidades, proporcionando una interfaz limpia que acepta objetos Date y devuelve cadenas formateadas.
Integración con frameworks
En aplicaciones React, crea el formateador una vez y pásalo a través del contexto o props:
import { createContext, useContext } from 'react';
const RelativeTimeContext = createContext(null);
export function RelativeTimeProvider({ locale, children }) {
const formatter = new RelativeTimeFormatter(locale);
return (
<RelativeTimeContext.Provider value={formatter}>
{children}
</RelativeTimeContext.Provider>
);
}
export function useRelativeTime() {
const formatter = useContext(RelativeTimeContext);
if (!formatter) {
throw new Error('useRelativeTime must be used within RelativeTimeProvider');
}
return formatter;
}
// Uso en componentes
function CommentTimestamp({ date }) {
const formatter = useRelativeTime();
return <time>{formatter.format(date)}</time>;
}
Este patrón asegura que los formateadores se creen una sola vez por locale y se compartan entre todos los componentes que necesiten formateo de tiempo relativo.
Compatibilidad con navegadores
Intl.RelativeTimeFormat funciona en todos los navegadores modernos con una cobertura global del 95%:
- Chrome 71+
- Firefox 65+
- Safari 14+
- Edge 79+
Internet Explorer no soporta esta API. Para aplicaciones que requieren soporte para IE, hay polyfills disponibles, aunque la implementación nativa proporciona mejor rendimiento y tamaños de paquete más pequeños.
Cuándo usar esta API
Intl.RelativeTimeFormat funciona mejor para:
- Mostrar la antigüedad del contenido en feeds y líneas de tiempo
- Mostrar marcas de tiempo de comentarios o publicaciones
- Formatear la programación de eventos en relación con el tiempo actual
- Construir sistemas de notificaciones con marcas de tiempo relativas
- Crear registros de actividad con tiempos legibles para humanos
La API no es adecuada para:
- Formateo de fecha y hora absoluta (usar
Intl.DateTimeFormat) - Seguimiento preciso del tiempo que requiere exactitud de milisegundos
- Temporizadores de cuenta regresiva que se actualizan cada segundo
- Aritmética de fechas o cálculos de calendario
Para aplicaciones que requieren visualizaciones de tiempo tanto relativas como absolutas, combina Intl.RelativeTimeFormat con Intl.DateTimeFormat. Muestra tiempos relativos para contenido reciente y cambia a fechas absolutas para contenido más antiguo:
function formatTimestamp(date, locale = 'en') {
const now = new Date();
const diffInMs = Math.abs(date - now);
const sevenDaysInMs = 7 * 24 * 60 * 60 * 1000;
if (diffInMs < sevenDaysInMs) {
const rtf = new RelativeTimeFormatter(locale);
return rtf.format(date);
} else {
const dtf = new Intl.DateTimeFormat(locale, {
year: 'numeric',
month: 'short',
day: 'numeric'
});
return dtf.format(date);
}
}
const yesterday = new Date(Date.now() - 24 * 60 * 60 * 1000);
console.log(formatTimestamp(yesterday));
// "ayer"
const lastMonth = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
console.log(formatTimestamp(lastMonth));
// "14 sept. 2025"
Este enfoque híbrido proporciona los beneficios del lenguaje natural de los tiempos relativos para contenido reciente mientras mantiene la claridad para las marcas de tiempo más antiguas.