API Intl.RelativeTimeFormat
Formatez les chaînes de temps relatif en JavaScript avec une prise en charge complète de l'internationalisation
Introduction
L'affichage d'horodatages relatifs comme « il y a 3 heures » ou « dans 2 jours » est une exigence courante dans les applications web. Les fils d'actualité des réseaux sociaux, les sections de commentaires, les systèmes de notification et les journaux d'activité nécessitent tous des affichages temporels lisibles par l'humain qui se mettent à jour au fur et à mesure que le contenu vieillit.
Construire cette fonctionnalité à partir de zéro présente des défis. Vous devez calculer les différences de temps, sélectionner les unités appropriées, gérer les règles de pluralisation dans toutes les langues et maintenir les traductions pour chaque locale prise en charge. Cette complexité explique pourquoi les développeurs se tournaient traditionnellement vers des bibliothèques comme Moment.js, ajoutant une taille de bundle significative pour ce qui semble être un formatage simple.
L'API Intl.RelativeTimeFormat fournit une solution native. Elle formate les chaînes de temps relatif avec une prise en charge complète de l'internationalisation, gérant automatiquement les règles de pluralisation et les conventions culturelles. L'API fonctionne sur tous les principaux navigateurs avec une couverture mondiale de 95 %, éliminant le besoin de dépendances externes tout en produisant des résultats naturels dans des dizaines de langues.
Utilisation de base
Le constructeur Intl.RelativeTimeFormat crée une instance de formateur qui convertit les valeurs numériques et les unités de temps en chaînes localisées.
const rtf = new Intl.RelativeTimeFormat('en');
console.log(rtf.format(-1, 'day'));
// "1 day ago"
console.log(rtf.format(2, 'hour'));
// "in 2 hours"
console.log(rtf.format(-3, 'month'));
// "3 months ago"
La méthode format() prend deux paramètres :
value: un nombre indiquant la quantité de tempsunit: une chaîne spécifiant l'unité de temps
Les valeurs négatives indiquent des temps passés, les valeurs positives indiquent des temps futurs. L'API gère automatiquement la pluralisation, produisant « il y a 1 jour » ou « il y a 2 jours » en fonction de la valeur.
Unités de temps prises en charge
L'API prend en charge huit unités de temps, chacune acceptant les formes singulières et plurielles :
const rtf = new Intl.RelativeTimeFormat('en');
// These produce identical output
console.log(rtf.format(-5, 'second'));
// "5 seconds ago"
console.log(rtf.format(-5, 'seconds'));
// "5 seconds ago"
Unités disponibles de la plus petite à la plus grande :
secondousecondsminuteouminuteshourouhoursdayoudaysweekouweeksmonthoumonthsquarterouquartersyearouyears
L'unité trimestre s'avère utile dans les applications métier qui suivent les périodes fiscales, tandis que les autres couvrent les besoins typiques de formatage de temps relatif.
Sortie en langage naturel
L'option numeric contrôle si le formateur utilise des valeurs numériques ou des alternatives en langage naturel.
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"
Définir numeric sur auto produit des formulations plus idiomatiques pour les valeurs courantes :
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"
Cette sortie en langage naturel crée des interfaces plus conversationnelles. L'option auto fonctionne avec toutes les unités de temps, bien que l'effet soit plus perceptible avec les jours. D'autres langues ont leurs propres alternatives idiomatiques que l'API gère automatiquement.
Styles de formatage
L'option style ajuste la verbosité de la sortie pour différents contextes d'interface :
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"
Utilisez le style long (par défaut) pour les interfaces standard où la lisibilité est primordiale. Utilisez le style short pour les mises en page à espace restreint comme les interfaces mobiles ou les tableaux de données. Utilisez le style narrow pour les affichages extrêmement compacts où chaque caractère compte.
Calcul des différences de temps
L'API Intl.RelativeTimeFormat formate les valeurs mais ne les calcule pas. Vous devez calculer les différences de temps et sélectionner les unités appropriées vous-même. Cette séparation des préoccupations vous donne le contrôle sur la logique de calcul tout en déléguant la complexité du formatage à l'API.
Calcul de base de la différence de temps
Pour une unité de temps spécifique, calculez la différence entre deux dates :
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"
Cette approche fonctionne lorsque vous savez quelle unité est pertinente pour votre cas d'usage. Les horodatages de commentaires peuvent toujours utiliser des heures ou des jours, tandis que la planification d'événements peut se concentrer sur des jours ou des semaines.
Sélection automatique de l'unité
Pour un formatage de temps relatif à usage général, sélectionnez l'unité la plus appropriée en fonction de l'amplitude de la différence de temps :
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"
Cette implémentation itère à travers les unités de la plus grande à la plus petite, en sélectionnant la première unité où la différence de temps dépasse la valeur en millisecondes de l'unité. Le repli sur les secondes garantit que la fonction retourne toujours un résultat.
Les seuils d'unité utilisent des valeurs approximatives. Les mois sont calculés comme 1/12 d'une année plutôt que de tenir compte des longueurs variables des mois. Cette approximation fonctionne bien pour les affichages de temps relatif où la précision importe moins que la lisibilité.
Support de l'internationalisation
Le formateur respecte les conventions spécifiques aux paramètres régionaux pour l'affichage du temps relatif. Différentes langues ont différentes règles de pluralisation, différents ordres de mots et différentes expressions idiomatiques.
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'));
// "昨日"
Les règles de pluralisation varient considérablement d'une langue à l'autre. L'anglais distingue entre un et plusieurs (1 day vs 2 days). L'arabe a six formes plurielles selon le nombre. Le japonais utilise la même forme quel que soit la quantité. L'API gère ces complexités automatiquement.
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 يومًا"
Le formateur gère également la direction du texte pour les langues de droite à gauche et applique des conventions de formatage culturellement appropriées. Cette localisation automatique élimine le besoin de maintenir des fichiers de traduction ou d'implémenter une logique de pluralisation personnalisée.
Formatage avancé avec formatToParts
La méthode formatToParts() retourne la chaîne formatée sous forme d'un tableau d'objets, permettant un style personnalisé ou une manipulation de composants individuels.
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' }
// ]
Chaque objet part contient :
type: soitintegerpour les valeurs numériques, soitliteralpour le textevalue: le contenu textuel de cette partieunit: l'unité de temps (présente sur les parties entières)
Cette structure permet un rendu personnalisé où vous pourriez vouloir styliser les nombres différemment du texte, ou extraire des composants spécifiques pour l'affichage :
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"
Lors de l'utilisation de numeric: 'auto' avec des valeurs qui ont des alternatives en langage naturel, formatToParts() renvoie une seule partie littérale :
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
const parts = rtf.formatToParts(-1, 'day');
console.log(parts);
// [
// { type: 'literal', value: 'yesterday' }
// ]
Ce comportement vous permet de détecter quand le langage naturel est utilisé par rapport au formatage numérique, vous permettant d'appliquer un style ou un comportement différent en fonction du type de sortie.
Optimisation des performances
La création d'instances Intl.RelativeTimeFormat implique le chargement des données de locale et l'initialisation des règles de formatage. Cette opération est suffisamment coûteuse pour éviter de la répéter inutilement.
Mettre en cache les instances de formateur
Créez les formateurs une fois et réutilisez-les :
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);
}
// Reuse cached formatters
const rtf = getFormatter('en', { numeric: 'auto' });
console.log(rtf.format(-1, 'day'));
// "yesterday"
Cette stratégie de mise en cache devient importante lors du formatage de nombreux horodatages, comme le rendu de fils d'actualité ou de fils de commentaires.
Minimiser la surcharge de calcul
Stockez les horodatages plutôt que de calculer les temps relatifs de manière répétée :
// Store the creation date
const comment = {
text: "Great article!",
createdAt: new Date('2025-10-14T10:30:00Z')
};
// Calculate relative time only when rendering
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);
}
}
}
Cette approche sépare le stockage des données de la présentation, vous permettant de recalculer les temps relatifs lorsque la locale de l'utilisateur change ou lorsque le rendu se met à jour sans modifier les données sous-jacentes.
Implémentation pratique
La combinaison de la logique de calcul avec le formatage produit une fonction utilitaire réutilisable adaptée aux applications de production :
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);
}
}
}
}
// Usage
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"
Cette classe encapsule à la fois le formateur et la logique de sélection d'unité, fournissant une interface claire qui accepte des objets Date et renvoie des chaînes formatées.
Intégration avec les frameworks
Dans les applications React, créez le formateur une seule fois et transmettez-le via le contexte ou les 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;
}
// Component usage
function CommentTimestamp({ date }) {
const formatter = useRelativeTime();
return <time>{formatter.format(date)}</time>;
}
Ce modèle garantit que les formateurs sont créés une seule fois par locale et partagés entre tous les composants nécessitant un formatage de temps relatif.
Compatibilité des navigateurs
Intl.RelativeTimeFormat fonctionne sur tous les navigateurs modernes avec une couverture mondiale de 95 % :
- Chrome 71+
- Firefox 65+
- Safari 14+
- Edge 79+
Internet Explorer ne prend pas en charge cette API. Pour les applications nécessitant la prise en charge d'IE, des polyfills sont disponibles, bien que l'implémentation native offre de meilleures performances et des tailles de bundle plus petites.
Quand utiliser cette API
Intl.RelativeTimeFormat fonctionne mieux pour :
- Afficher l'âge du contenu dans les fils d'actualité et les chronologies
- Afficher les horodatages des commentaires ou des publications
- Formater la planification d'événements par rapport à l'heure actuelle
- Créer des systèmes de notification avec des horodatages relatifs
- Créer des journaux d'activité avec des heures lisibles par l'humain
L'API n'est pas adaptée pour :
- Le formatage de date et d'heure absolues (utilisez
Intl.DateTimeFormat) - Le suivi précis du temps nécessitant une précision exacte à la milliseconde
- Les minuteurs de compte à rebours qui se mettent à jour chaque seconde
- Les calculs de date ou de calendrier
Pour les applications nécessitant à la fois des affichages de temps relatifs et absolus, combinez Intl.RelativeTimeFormat avec Intl.DateTimeFormat. Affichez les temps relatifs pour le contenu récent et basculez vers les dates absolues pour le contenu plus ancien :
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));
// "yesterday"
const lastMonth = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
console.log(formatTimestamp(lastMonth));
// "Sep 14, 2025"
Cette approche hybride offre les avantages du langage naturel des temps relatifs pour le contenu récent tout en maintenant la clarté pour les horodatages plus anciens.