RelativeDateTimeFormatter est une classe Foundation dans iOS et macOS qui convertit les dates absolues en phrases relatives lisibles par l'homme : « il y a 5 minutes », « hier », « dans 3 jours ». Selon Apple Developer Documentation, 2024, RelativeDateTimeFormatter sélectionne automatiquement l'unité appropriée (secondes, minutes, heures, jours) et localise la sortie dans la langue des paramètres régionaux actuels de l'appareil. Contrairement au calcul manuel de la différence entre les dates via Calendar, cette classe prend en compte les particularités linguistiques de chaque langue : pour certaines langues, les nombres se déclinent, pour d'autres, une forme spéciale est utilisée pour le mot « hier ». La classe est disponible à partir d'iOS 13 et macOS 10.15.
Points clés
RelativeDateTimeFormatter est une sous-classe de Formatter dans Foundation qui prend une Date (ou une différence en secondes) et retourne une chaîne localisée avec un temps relatif. Par exemple, pour une date 5 minutes avant la date actuelle, il retourne « il y a 5 minutes ». La classe prend en charge trois contextes temporels : passé, futur et présent.
La logique interne de RelativeDateTimeFormatter utilise Calendar et Locale pour calculer la différence entre les dates et sélectionner la forme grammaticale correcte. Pour le français, il choisit entre « il y a 1 minute » et « il y a 5 minutes ». Cette fonctionnalité est basée sur les données ICU (International Components for Unicode) et ne nécessite aucune configuration supplémentaire de la part du développeur.
Selon Apple WWDC 2019, RelativeDateTimeFormatter est devenu partie intégrante du framework pour simplifier la localisation — avant son introduction, les développeurs devaient calculer manuellement la différence de dates et remplacer les chaînes localisées via String.localizedStringWithFormat. Cela entraînait des erreurs de déclinaison (notamment pour les langues slaves et arabes) et une sélection incorrecte des unités de mesure.
L'algorithme de RelativeDateTimeFormatter comprend trois étapes : calculer la différence entre la date transmise et le moment actuel, sélectionner l'unité appropriée (la plus grande qui ne donne pas zéro) et formater selon les paramètres régionaux. Par exemple, pour une différence de 3720 secondes (1 heure 2 minutes), l'unité « heure » est sélectionnée et le résultat est « il y a 1 heure », et non « il y a 62 minutes ».
Les unités sont sélectionnées selon le principe de la « plus grande non nulle » : si la différence est supérieure à 86400 secondes (1 jour), les jours sont utilisés ; si supérieure à 604800 (1 semaine) — les semaines, et ainsi de suite. Cet algorithme garantit que le résultat se lit toujours naturellement : au lieu de « il y a 518400 secondes », l'utilisateur voit « il y a 6 jours ». Les limites exactes des unités sont déterminées par le calendrier des paramètres régionaux actuels.
| Plage de différence | Unité | Exemple pour fr_FR |
|---|---|---|
| 0–59 secondes | Seconds | il y a 30 secondes |
| 1–59 minutes | Minutes | il y a 5 minutes |
| 1–23 heures | Hours | il y a 3 heures |
| 1–6 jours | Days | il y a 2 jours |
| 7–27 jours | Weeks | il y a 1 semaine |
| 28 jours–11 mois | Months | il y a 3 mois |
| 12+ mois | Years | il y a 1 an |
Le contexte de formatage détermine la terminaison de la phrase. Pour le passé : « il y a » (français). Pour le futur : « dans 3 jours » (français). Pour le présent : « maintenant » (français). Le contexte est défini via la méthode localizeString(fromTimeInterval:) ou directement via string(from: Date).
RelativeDateTimeFormatter fournit plusieurs paramètres pour contrôler la sortie : la propriété unitsStyle détermine le style de formatage (numeric, abbreviated, full, spellOut), et maximumUnitCount limite le nombre d'unités affichées. Par exemple, avec maximumUnitCount = 1, une différence de 1 heure 30 minutes est affichée comme « il y a 1 heure » au lieu de « il y a 1 heure 30 minutes ».
Limitation des unités : par défaut, RelativeDateTimeFormatter n'affiche qu'une seule unité (la plus grande). Définir maximumUnitCount = 2 inclut l'unité suivante pour une description plus précise : « il y a 1 heure 30 minutes ». Cependant, cela peut rendre la chaîne excessivement longue pour les messages courts (notifications push, alertes). Pour l'interface utilisateur, il est recommandé de conserver maximumUnitCount = 1.
import Foundation
let formatter = RelativeDateTimeFormatter()
// Configure styles
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1
// Examples with different dates
let fiveMinAgo = Date().addingTimeInterval(-300)
print("5 min ago: \(formatter.localizedString(for: fiveMinAgo, relativeTo: Date()))")
let twoDaysLater = Date().addingTimeInterval(172800)
print("2 days later: \(formatter.localizedString(for: twoDaysLater, relativeTo: Date()))")
// Abbreviated style
formatter.unitsStyle = .abbreviated
let oneWeekAgo = Date().addingTimeInterval(-604800)
print("Abbreviated: \(formatter.localizedString(for: oneWeekAgo, relativeTo: Date()))")
// Full style (spelled out)
formatter.unitsStyle = .full
let threeHours = Date().addingTimeInterval(10800)
print("Full: \(formatter.localizedString(for: threeHours, relativeTo: Date()))")
Choix du style pour différents contextes : pour un fil d'actualités, utilisez .numeric avec maximumUnitCount = 1 — c'est la norme pour Twitter, Instagram et Facebook. Pour l'accessibilité (VoiceOver), utilisez .full — les nombres en toutes lettres sont lus plus naturellement. Pour les éléments compacts (badge de notification, barre d'état), utilisez .abbreviated pour économiser de l'espace.
Utilisation de base de RelativeDateTimeFormatter se résume à créer une instance, configurer les propriétés et appeler l'une des méthodes de formatage. Les principales méthodes sont : localizedString(for:relativeTo:) — pour une paire de dates, localizedString(fromTimeInterval:) — pour une différence en secondes, et string(for:) — pour Date avec contexte automatique (passé/futur).
import Foundation
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1
// Social network UI examples
let postDates: [(title: String, date: Date)] = [
("Just now", Date().addingTimeInterval(-30)),
("5 min ago", Date().addingTimeInterval(-300)),
("Yesterday", Date().addingTimeInterval(-90000)),
("Last week", Date().addingTimeInterval(-700000)),
("Last year", Date().addingTimeInterval(-32000000))
]
for (title, postDate) in postDates {
let relative = formatter.localizedString(
for: postDate,
relativeTo: Date()
)
print("\(title): \(relative)")
}
// Future dates
let reminderFormatter = RelativeDateTimeFormatter()
reminderFormatter.unitsStyle = .abbreviated
let inOneHour = Date().addingTimeInterval(3600)
let reminderText = reminderFormatter.localizedString(
for: inOneHour,
relativeTo: Date()
)
print("Reminder: \(reminderText)")
Gestion du scénario « tout à l'heure » — RelativeDateTimeFormatter n'a pas de prise en charge intégrée pour l'expression « tout à l'heure » pour les très petits intervalles. Pour une différence de moins de 5 secondes, il retourne « il y a 0 seconde », ce qui n'est pas esthétique dans l'interface utilisateur. Il est recommandé d'envelopper l'appel du formateur dans une logique conditionnelle : si la différence est inférieure à un seuil défini (par exemple, 5 secondes) — afficher « tout à l'heure » manuellement, sinon passer la date au formateur.
import Foundation
func relativeTimeString(from date: Date) -> String {
let interval = Date().timeIntervalSince(date)
// "Just now" threshold
if interval < 5 {
return "just now"
}
// "Today" threshold
if interval < 60 {
return "just now"
}
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1
// Display without "ago" suffix
return formatter.localizedString(
for: date,
relativeTo: Date()
)
}
print(relativeTimeString(from: Date().addingTimeInterval(-3)))
print(relativeTimeString(from: Date().addingTimeInterval(-120)))
print(relativeTimeString(from: Date().addingTimeInterval(-3600)))
La méthode string(fromTimeInterval:) accepte une différence en secondes et détermine automatiquement le contexte (valeur positive — futur, négative — passé). C'est pratique lorsque la différence est déjà connue (par exemple, reçue du serveur sous forme de timestamp unix). Dans ce cas, il n'est pas nécessaire de créer une Date — la différence est transmise directement.
RelativeDateTimeFormatter localise automatiquement la sortie en fonction de Locale.current. Pour changer la langue de formatage, définissez la propriété locale — contrairement à DateFormatter, pour RelativeDateTimeFormatter, les paramètres régionaux ne sont pas fixes et peuvent être modifiés pour chaque appel. Cela permet d'afficher des dates relatives dans une langue différente de celle de l'interface (par exemple, contenu dans la langue d'origine).
La complexité de la localisation des dates relatives réside dans les particularités grammaticales des différentes langues. Le français nécessite différentes formes de nombres : « 1 minute », « 2 minutes ». L'arabe utilise la forme plurielle pour les nombres de 3 à 10 et des formes spéciales pour 11+. Le chinois n'a aucune déclinaison, ce qui simplifie la tâche. RelativeDateTimeFormatter couvre tous ces cas via les règles ICU, sans nécessiter de code supplémentaire.
import Foundation
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1
let targetDate = Date().addingTimeInterval(-7200) // 2 hours ago
// Different locales
let locales: [String] = ["ru_RU", "en_US", "de_DE", "fr_FR", "ja_JP", "ar_SA"]
for identifier in locales {
formatter.locale = Locale(identifier: identifier)
let result = formatter.localizedString(
for: targetDate,
relativeTo: Date()
)
print("\(identifier): \(result)")
}
// Check French pluralization
formatter.locale = Locale(identifier: "ru_RU")
let intervals: [TimeInterval] = [-60, -120, -180, -300]
for interval in intervals {
let date = Date().addingTimeInterval(interval)
print("\(-Int(interval / 60)) min: \(formatter.localizedString(for: date, relativeTo: Date()))")
}
Une nuance importante : RelativeDateTimeFormatter ignore TimeZone lors du calcul de la différence pour les paramètres .numeric — il utilise la différence absolue en secondes. Cependant, pour le style .full (avec des nombres en toutes lettres) et les cas spéciaux (hier, aujourd'hui), TimeZone est pris en compte. Définissez toujours TimeZone explicitement pour la cohérence, surtout si l'application travaille avec des dates serveur en UTC.
Ignorer TimeZone lors du calcul des dates relatives — une erreur courante lors du travail avec des dates serveur. Si le serveur envoie une Date en UTC et que RelativeDateTimeFormatter utilise TimeZone.current, la différence peut être calculée incorrectement pour les dates proches du moment actuel. Il est recommandé de toujours définir formatter.timeZone = TimeZone(secondsFromGMT: 0) pour les données serveur.
Sélection incorrecte de l'unité pour les intervalles courts — RelativeDateTimeFormatter arrondit la différence à l'unité la plus grande. Pour 25 heures, le résultat sera « il y a 1 jour », ce qui peut induire l'utilisateur en erreur. Si une haute précision est nécessaire (par exemple, pour les minuteurs de compte à rebours), utilisez DateComponentsFormatter au lieu de RelativeDateTimeFormatter — il permet d'afficher plusieurs unités simultanément.
Absence de vérification du TimeInterval négatif — si une date future est transmise comme passée (valeur négative dans string(fromTimeInterval:)), le formateur peut retourner une chaîne incorrecte. Vérifiez toujours le signe de l'intervalle avant de le passer au formateur, surtout lors du travail avec des données serveur où le fuseau horaire peut fausser le calcul.
Selon Hacker News (2024), l'un des problèmes les plus discutés de RelativeDateTimeFormatter est l'absence de prise en charge intégrée de « hier » et « aujourd'hui » pour la langue anglaise. Au lieu de « hier », le formateur pour une différence de 90000 secondes retourne « il y a 1 jour ». Pour le français, ce problème n'existe pas — « il y a 1 jour » sonne naturellement, mais pour l'interface utilisateur anglaise, « yesterday » est préférable. Cette fonctionnalité n'est pas prise en charge et nécessite une vérification manuelle via Calendar.isDateInToday/Yesterday.
Foire aux questions
RelativeDateTimeFormatter est une classe Foundation pour afficher les dates au format relatif : « il y a 5 minutes », « dans 2 jours ». Disponible depuis iOS 13 et macOS 10.15.
Par le principe de la plus grande unité non nulle — secondes, minutes, heures, jours, semaines, mois ou années. Par exemple, pour une différence de 3720 secondes (1 heure 2 minutes), l'unité « heure » est sélectionnée, pas « minutes ».
Définissez la propriété locale sur l'instance Locale souhaitée. Par défaut, Locale.current est utilisé. Exemple : formatter.locale = Locale(identifier: "de_DE") pour l'allemand.
.numeric — forme complète (« il y a 3 jours »), .abbreviated — forme abrégée (« il y a 3 j. »). Le choix dépend du contexte : numeric pour l'interface utilisateur principale, abbreviated pour les éléments compacts.
Ajoutez une vérification manuelle pour un intervalle de moins de 5 à 10 secondes. RelativeDateTimeFormatter ne prend pas en charge « tout à l'heure » — pour les petits intervalles, il retourne « il y a 0 seconde ». Utilisez une logique conditionnelle avec un seuil.
Résumé
Nous développerons une application mobile clé en main
IT Sectr crée des applications iOS et Android pour les startups et les entreprises depuis 2017. Nous vous conseillerons et vous proposerons la meilleure solution.
Lisez aussi