RelativeDateTimeFormatter ist eine Foundation-Klasse in iOS und macOS, die absolute Daten in menschenlesbare relative Formulierungen umwandelt: „5 Minuten zuvor“, „gestern“, „in 3 Tagen“. Laut Apple Developer Documentation, 2024 wählt RelativeDateTimeFormatter automatisch die passende Einheit (Sekunden, Minuten, Stunden, Tage) aus und lokalisiert die Ausgabe in der Sprache des aktuellen Gebietsschemas des Geräts. Im Gegensatz zur manuellen Berechnung der Differenz zwischen Daten über Calendar berücksichtigt diese Klasse die sprachlichen Besonderheiten jeder Sprache: Für manche Sprachen werden Zahlwörter gebeugt, für andere wird eine spezielle Form für das Wort „gestern“ verwendet. Die Klasse ist ab iOS 13 und macOS 10.15 verfügbar.
Wichtige Punkte
RelativeDateTimeFormatter ist eine Unterklasse von Formatter in Foundation, die ein Date (oder eine Differenz in Sekunden) nimmt und eine lokalisierte Zeichenfolge mit relativer Zeit zurückgibt. Für ein Datum 5 Minuten vor dem aktuellen gibt es beispielsweise „5 Minuten zuvor“ zurück. Die Klasse unterstützt drei zeitliche Kontexte: Vergangenheit, Zukunft und Gegenwart.
Die interne Logik von RelativeDateTimeFormatter verwendet Calendar und Locale, um die Differenz zwischen Daten zu berechnen und die korrekte grammatikalische Form auszuwählen. Für Deutsch wählt es zwischen „1 Minute zuvor“ und „5 Minuten zuvor“. Diese Funktionalität basiert auf ICU-Daten (International Components for Unicode) und erfordert keine zusätzliche Konfiguration durch den Entwickler.
Laut Apple WWDC 2019 wurde RelativeDateTimeFormatter Teil des Frameworks, um die Lokalisierung zu vereinfachen — vor seiner Einführung mussten Entwickler die Datumsdifferenz manuell berechnen und lokalisierte Zeichenfolgen über String.localizedStringWithFormat ersetzen. Dies führte zu Beugungsfehlern (insbesondere bei slawischen und arabischen Sprachen) und einer falschen Auswahl von Maßeinheiten.
Der Algorithmus von RelativeDateTimeFormatter besteht aus drei Schritten: Berechnen der Differenz zwischen dem übergebenen Datum und dem aktuellen Zeitpunkt, Auswählen der geeigneten Einheit (der größten, die nicht Null ergibt) und Formatieren entsprechend dem Gebietsschema. Bei einer Differenz von 3720 Sekunden (1 Stunde 2 Minuten) wird beispielsweise die Einheit „Stunde“ ausgewählt, und das Ergebnis ist „1 Stunde zuvor“, nicht „62 Minuten zuvor“.
Einheiten werden nach dem Prinzip der „größten Nicht-Null“ ausgewählt: Ist die Differenz größer als 86400 Sekunden (1 Tag), werden Tage verwendet; ist sie größer als 604800 (1 Woche) — Wochen, und so weiter. Dieser Algorithmus stellt sicher, dass das Ergebnis immer natürlich lesbar ist: Statt „518400 Sekunden zuvor“ sieht der Benutzer „6 Tage zuvor“. Die genauen Grenzen der Einheiten werden durch den Kalender des aktuellen Gebietsschemas bestimmt.
| Differenzbereich | Einheit | Beispiel für de_DE |
|---|---|---|
| 0–59 Sekunden | Seconds | 30 Sekunden zuvor |
| 1–59 Minuten | Minutes | 5 Minuten zuvor |
| 1–23 Stunden | Hours | 3 Stunden zuvor |
| 1–6 Tage | Days | 2 Tage zuvor |
| 7–27 Tage | Weeks | 1 Woche zuvor |
| 28 Tage–11 Monate | Months | 3 Monate zuvor |
| 12+ Monate | Years | 1 Jahr zuvor |
Der Formatierungskontext bestimmt die Endung des Satzes. Für die Vergangenheit: „zuvor“ (Deutsch). Für die Zukunft: „in 3 Tagen“ (Deutsch). Für die Gegenwart: „jetzt“ (Deutsch). Der Kontext wird über die Methode localizeString(fromTimeInterval:) oder direkt über string(from: Date) festgelegt.
RelativeDateTimeFormatter bietet mehrere Einstellungen zur Steuerung der Ausgabe: Die Eigenschaft unitsStyle bestimmt den Formatierungsstil (numeric, abbreviated, full, spellOut), und maximumUnitCount begrenzt die Anzahl der angezeigten Einheiten. Mit maximumUnitCount = 1 wird eine Differenz von 1 Stunde 30 Minuten beispielsweise als „1 Stunde zuvor“ statt „1 Stunde 30 Minuten zuvor“ angezeigt.
Einheitenbegrenzung: Standardmäßig zeigt RelativeDateTimeFormatter nur eine (die größte) Einheit an. Das Setzen von maximumUnitCount = 2 schließt die nächste Einheit für eine genauere Beschreibung ein: „1 Stunde 30 Minuten zuvor“. Dies kann jedoch die Zeichenfolge für kurze Nachrichten (Push-Benachrichtigungen, Alarme) übermäßig lang machen. Für die UI wird empfohlen, maximumUnitCount = 1 beizubehalten.
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()))")
Stilauswahl für verschiedene Kontexte: Für einen Nachrichtenfeed verwenden Sie .numeric mit maximumUnitCount = 1 — dies ist der Standard für Twitter, Instagram und Facebook. Für Barrierefreiheit (VoiceOver) verwenden Sie .full — ausgeschriebene Zahlen werden natürlicher vorgelesen. Für kompakte Elemente (Benachrichtigungsabzeichen, Statusleiste) verwenden Sie .abbreviated, um Platz zu sparen.
Grundlegende Verwendung von RelativeDateTimeFormatter beschränkt sich auf das Erstellen einer Instanz, das Konfigurieren von Eigenschaften und das Aufrufen einer der Formatierungsmethoden. Die wichtigsten Methoden sind: localizedString(for:relativeTo:) — für ein Paar von Daten, localizedString(fromTimeInterval:) — für eine Differenz in Sekunden, und string(for:) — für Date mit automatischem Kontext (Vergangenheit/Zukunft).
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)")
Behandlung des „gerade eben“-Szenarios — RelativeDateTimeFormatter hat keine integrierte Unterstützung für den Ausdruck „gerade eben“ für sehr kleine Intervalle. Bei einer Differenz von weniger als 5 Sekunden gibt es „0 Sekunden zuvor“ zurück, was in der UI unschön aussieht. Es wird empfohlen, den Formatiereraufruf in eine bedingte Logik zu verpacken: Wenn die Differenz kleiner als ein festgelegter Schwellenwert (z.B. 5 Sekunden) ist — zeigen Sie manuell „gerade eben“ an, andernfalls übergeben Sie das Datum an den Formatierer.
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)))
Die Methode string(fromTimeInterval:) akzeptiert eine Differenz in Sekunden und bestimmt automatisch den Kontext (positiver Wert — Zukunft, negativer — Vergangenheit). Dies ist praktisch, wenn die Differenz bereits bekannt ist (z.B. vom Server als Unix-Zeitstempel empfangen). In diesem Fall muss kein Date erstellt werden — die Differenz wird direkt übergeben.
RelativeDateTimeFormatter lokalisiert die Ausgabe automatisch basierend auf Locale.current. Um die Formatierungssprache zu ändern, setzen Sie die Eigenschaft locale — im Gegensatz zu DateFormatter ist bei RelativeDateTimeFormatter das Gebietsschema nicht festgelegt und kann für jeden Aufruf geändert werden. Dies ermöglicht die Anzeige relativer Daten in einer anderen Sprache als der Oberflächensprache (z.B. Inhalte in der Originalsprache).
Die Komplexität der Lokalisierung relativer Daten liegt in den grammatikalischen Besonderheiten verschiedener Sprachen. Deutsch erfordert unterschiedliche Formen von Zahlwörtern: „1 Minute“, „2 Minuten“. Arabisch verwendet die Pluralform für Zahlen von 3 bis 10 und spezielle Formen für 11+. Chinesisch hat überhaupt keine Beugung, was die Aufgabe vereinfacht. RelativeDateTimeFormatter deckt all diese Fälle durch ICU-Regeln ab, ohne dass zusätzlicher Code erforderlich ist.
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 German 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()))")
}
Eine wichtige Nuance: RelativeDateTimeFormatter ignoriert TimeZone bei der Berechnung der Differenz für .numeric-Einstellungen — es verwendet die absolute Differenz in Sekunden. Für den .full-Stil (mit ausgeschriebenen Zahlen) und Sonderfälle (gestern, heute) wird TimeZone jedoch berücksichtigt. Setzen Sie TimeZone aus Gründen der Konsistenz immer explizit, insbesondere wenn die Anwendung mit Serverdaten in UTC arbeitet.
Ignorieren von TimeZone bei der Berechnung relativer Daten — ein häufiger Fehler bei der Arbeit mit Serverdaten. Wenn der Server ein Date in UTC sendet und RelativeDateTimeFormatter TimeZone.current verwendet, kann die Differenz für Daten nahe dem aktuellen Zeitpunkt falsch berechnet werden. Es wird empfohlen, für Serverdaten immer formatter.timeZone = TimeZone(secondsFromGMT: 0) zu setzen.
Falsche Einheitenauswahl für kurze Intervalle — RelativeDateTimeFormatter rundet die Differenz auf die größte Einheit. Bei 25 Stunden ist das Ergebnis „1 Tag zuvor“, was den Benutzer in die Irre führen kann. Wenn eine hohe Genauigkeit erforderlich ist (z.B. für Countdown-Timer), verwenden Sie DateComponentsFormatter anstelle von RelativeDateTimeFormatter — dieser ermöglicht die gleichzeitige Anzeige mehrerer Einheiten.
Fehlende Prüfung auf negatives TimeInterval — Wenn ein zukünftiges Datum als vergangen übergeben wird (negativer Wert in string(fromTimeInterval:)), kann der Formatierer eine falsche Zeichenfolge zurückgeben. Überprüfen Sie immer das Vorzeichen des Intervalls, bevor Sie es an den Formatierer übergeben, insbesondere bei der Arbeit mit Serverdaten, bei denen die Zeitzone die Berechnung verzerren kann.
Laut Hacker News (2024) ist eines der am meisten diskutierten Probleme von RelativeDateTimeFormatter das Fehlen einer integrierten Unterstützung für „gestern“ und „heute“ für die englische Sprache. Statt „gestern“ gibt der Formatierer bei einer Differenz von 90000 Sekunden „1 Tag zuvor“ zurück. Für Deutsch gibt es dieses Problem nicht — „1 Tag zuvor“ klingt natürlich, aber für die englische UI ist „yesterday“ vorzuziehen. Diese Funktionalität wird nicht unterstützt und erfordert eine manuelle Überprüfung über Calendar.isDateInToday/Yesterday.
Häufig gestellte Fragen
RelativeDateTimeFormatter ist eine Foundation-Klasse zur Anzeige von Daten in relativem Format: „5 Minuten zuvor“, „in 2 Tagen“. Verfügbar ab iOS 13 und macOS 10.15.
Nach dem Prinzip der größten Nicht-Null-Einheit — Sekunden, Minuten, Stunden, Tage, Wochen, Monate oder Jahre. Bei einer Differenz von 3720 Sekunden (1 Stunde 2 Minuten) wird beispielsweise die Einheit „Stunde“ ausgewählt, nicht „Minuten“.
Setzen Sie die Eigenschaft locale auf die gewünschte Locale-Instanz. Standardmäßig wird Locale.current verwendet. Beispiel: formatter.locale = Locale(identifier: "de_DE") für Deutsch.
.numeric — vollständige Form („3 Tage zuvor“), .abbreviated — Kurzform („3 Tg. zuvor“). Die Wahl hängt vom Kontext ab: numeric für die Haupt-UI, abbreviated für kompakte Elemente.
Fügen Sie eine manuelle Prüfung für ein Intervall von weniger als 5–10 Sekunden hinzu. RelativeDateTimeFormatter unterstützt „gerade eben“ nicht — bei kleinen Intervallen gibt es „0 Sekunden zuvor“ zurück. Verwenden Sie bedingte Logik mit einem Schwellenwert.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch