RelativeDateTimeFormatter je třída Foundation v iOS a macOS, která převádí absolutní data na čitelné relativní formulace: „před 5 minutami“, „včera“, „za 3 dny“. Podle Apple Developer Documentation, 2024 třída RelativeDateTimeFormatter automaticky vybírá vhodnou jednotku (sekundy, minuty, hodiny, dny) a lokalizuje výstup v jazyce aktuální lokality zařízení. Na rozdíl od ručního výpočtu rozdílu mezi daty přes Calendar tato třída zohledňuje jazykové zvláštnosti každého jazyka: u některých jazyků se číslovky skloňují, u jiných se používá zvláštní tvar pro slovo „včera“. Třída je dostupná od iOS 13 a macOS 10.15.
Hlavní
RelativeDateTimeFormatter je podtřídou Formatter ve Foundation, která přijímá Date (nebo rozdíl v sekundách) a vrací lokalizovaný řetězec s relativním časem. Například pro datum o 5 minut dříve než aktuální vrátí „před 5 minutami“ pro ru_RU nebo „5 minutes ago“ pro en_US. Třída podporuje tři časové kontexty: minulost (past), budoucnost (future) a přítomnost (present).
Vnitřní logika RelativeDateTimeFormatter používá Calendar a Locale pro výpočet rozdílu mezi daty a volbu správného gramatického tvaru. Pro češtinu třída správně skloňuje číslovky: „před 1 minutou“, „před 2 minutami“, „před 5 minutami“. Pro angličtinu — volí mezi „minute ago“ a „minutes ago“. Tato funkce je založena na datech ICU (International Components for Unicode) a nevyžaduje další konfiguraci ze strany vývojáře.
Podle Apple WWDC 2019 se RelativeDateTimeFormatter stal součástí frameworku pro zjednodušení lokalizace — před jeho vznikem byli vývojáři nuceni ručně vypočítávat rozdíl dat a vkládat lokalizované řetězce přes String.localizedStringWithFormat. To vedlo k chybám ve skloňování (zejména u slovanských a arabských jazyků) a k nesprávnému výběru měrných jednotek.
Algoritmus činnosti RelativeDateTimeFormatter se skládá ze tří kroků: výpočet rozdílu mezi předaným datem a aktuálním okamžikem, výběr vhodné jednotky (největší, která nedává nulu) a formátování s ohledem na lokalitu. Například pro rozdíl 3720 sekund (1 hodina 2 minuty) bude vybrána jednotka „hodina“ a výsledek bude „před 1 hodinou“, nikoli „před 62 minutami“.
Jednotky se vybírají podle principu „největší nenulové“: pokud je rozdíl větší než 86400 sekund (1 den), používají se dny; pokud větší než 604800 (1 týden) — týdny a tak dále. Tento algoritmus zaručuje, že výsledek vždy zní přirozeně: místo „před 518400 sekundami“ uživatel vidí „před 6 dny“. Přesné hranice jednotek určuje kalendář aktuální lokality.
| Rozsah rozdílu | Jednotka | Příklad pro ru_RU |
|---|---|---|
| 0–59 sekund | Seconds | před 30 sekundami |
| 1–59 minut | Minutes | před 5 minutami |
| 1–23 hodin | Hours | před 3 hodinami |
| 1–6 dní | Days | před 2 dny |
| 7–27 dní | Weeks | před 1 týdnem |
| 28 dní–11 měsíců | Months | před 3 měsíci |
| 12+ měsíců | Years | před 1 rokem |
Kontext formátování určuje zakončení fráze. Pro minulost: „zpět“ (čeština), „ago“ (angličtina). Pro budoucnost: „za 3 dny“ (čeština), „in 3 days“ (angličtina). Pro přítomnost: „nyní“ (čeština), „now“ (angličtina). Kontext se nastavuje metodou localizeString(fromTimeInterval:) nebo přímo přes string(from: Date).
RelativeDateTimeFormatter poskytuje několik nastavení pro kontrolu výstupu: vlastnost unitsStyle určuje styl formátování (numeric, abbreviated, full, spellOut) a maximumUnitCount omezuje počet zobrazených jednotek. Například s maximumUnitCount = 1 bude rozdíl 1 hodina 30 minut zobrazen jako „před 1 hodinou“ místo „před 1 hodinou 30 minutami“.
Omezení jednotek: ve výchozím nastavení RelativeDateTimeFormatter zobrazuje pouze jednu (největší) jednotku. Nastavení maximumUnitCount = 2 aktivuje další jednotku pro přesnější popis: „před 1 hodinou 30 minutami“. To však může řetězec nadměrně prodloužit pro krátké zprávy (push, oznámení). Pro UI se doporučuje ponechat maximumUnitCount = 1.
import Foundation
let formatter = RelativeDateTimeFormatter()
// Nastavte styly
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1
// Příklady s různými daty
let fiveMinAgo = Date().addingTimeInterval(-300)
print("před 5 min: \(formatter.localizedString(for: fiveMinAgo, relativeTo: Date()))")
let twoDaysLater = Date().addingTimeInterval(172800)
print("za 2 dny: \(formatter.localizedString(for: twoDaysLater, relativeTo: Date()))")
// Zkrácený styl
formatter.unitsStyle = .abbreviated
let oneWeekAgo = Date().addingTimeInterval(-604800)
print("Zkrácený: \(formatter.localizedString(for: oneWeekAgo, relativeTo: Date()))")
// Plný styl (slovy)
formatter.unitsStyle = .full
let threeHours = Date().addingTimeInterval(10800)
print("Plný: \(formatter.localizedString(for: threeHours, relativeTo: Date()))")
Výběr stylu pro různé kontexty: pro zpravodajský kanál použijte .numeric s maximumUnitCount = 1 — to je standard pro Twitter, Instagram a Facebook. Pro Accessibility (VoiceOver) použijte .full — čísla slovy se čtou přirozeněji. Pro kompaktní prvky (roh oznámení, stavový řádek) použijte .abbreviated pro úsporu místa.
Základní použití RelativeDateTimeFormatter spočívá ve vytvoření instance, nastavení vlastností a volání jedné z metod formátování. Hlavní metody: localizedString(for:relativeTo:) — pro dvojici dat, localizedString(fromTimeInterval:) — pro rozdíl v sekundách a string(for:) — pro Date s automatickým kontextem (minulost/budoucnost).
import Foundation
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1
// Příklady UI sociálních sítí
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)")
}
// Budoucí data
let reminderFormatter = RelativeDateTimeFormatter()
reminderFormatter.unitsStyle = .abbreviated
let inOneHour = Date().addingTimeInterval(3600)
let reminderText = reminderFormatter.localizedString(
for: inOneHour,
relativeTo: Date()
)
print("Připomenutí: \(reminderText)")
Zpracování scénáře „právě teď“ — RelativeDateTimeFormatter nemá vestavěnou podporu fráze „právě teď“ pro velmi malé intervaly. Pro rozdíl menší než 5 sekund vrátí „před 0 sekundami“, což v UI nevypadá dobře. Doporučuje se zabalit volání formatteru do podmínkové logiky: pokud je rozdíl menší než stanovený práh (např. 5 sekund) — zobrazte „právě teď“ ručně, jinak předejte datum formatteru.
import Foundation
func relativeTimeString(from date: Date) -> String {
let interval = Date().timeIntervalSince(date)
// Práh „právě teď“
if interval < 5 {
return "just now"
}
// Práh „dnes“
if interval < 60 {
return "just now"
}
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1
// Zobrazení bez přípony „zpět“
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)))
Metoda string(fromTimeInterval:) přijímá rozdíl v sekundách a automaticky určuje kontext (kladná hodnota — budoucnost, záporná — minulost). To je výhodné, když je rozdíl již znám (např. přijat ze serveru jako unix timestamp). V tomto případě není třeba vytvářet Date — rozdíl se předává přímo.
RelativeDateTimeFormatter automaticky lokalizuje výstup na základě Locale.current. Pro změnu jazyka formátování nastavte vlastnost locale — na rozdíl od DateFormatter není locale pro RelativeDateTimeFormatter pevně dané a lze jej měnit pro každé volání. To umožňuje zobrazovat relativní data v jazyce odlišném od jazyka rozhraní (např. obsah v původním jazyce).
Složitost lokalizace relativních dat spočívá v gramatických zvláštnostech různých jazyků. Ruština vyžaduje různé tvary číslovek: „1 minuta“, „2 minuty“, „5 minut“. Arabština — používá tvar množného čísla pro čísla od 3 do 10 a zvláštní tvary pro 11+. Čínština — nemá skloňování vůbec, což úkol zjednodušuje. RelativeDateTimeFormatter pokrývá všechny tyto případy prostřednictvím pravidel ICU, bez potřeby dalšího kódu.
import Foundation
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1
let targetDate = Date().addingTimeInterval(-7200) // před 2 hodinami
// Různé lokality
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)")
}
// Zkontrolujte ruské skloňování
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()))")
}
Důležitá nuance: RelativeDateTimeFormatter ignoruje TimeZone při výpočtu rozdílu pro nastavení .numeric — používá absolutní rozdíl v sekundách. Pro styl .full (s čísly slovy) a zvláštní případy (včera, dnes) se však TimeZone zohledňuje. Vždy nastavujte TimeZone explicitně pro konzistenci, zejména pokud aplikace pracuje se serverovými daty v UTC.
Ignorování TimeZone při výpočtu relativních dat — častá chyba při práci se serverovými daty. Pokud server odesílá Date v UTC a RelativeDateTimeFormatter používá TimeZone.current, může být rozdíl vypočten nesprávně pro data blízká aktuálnímu okamžiku. Doporučuje se vždy nastavovat formatter.timeZone = TimeZone(secondsFromGMT: 0) pro serverová data.
Nesprávný výběr jednotky pro krátké intervaly — RelativeDateTimeFormatter zaokrouhluje rozdíl na největší jednotku. Pro 25 hodin bude výsledek „před 1 dnem“, což může uživatele uvést v omyl. Pokud je nutná vysoká přesnost (např. pro odpočítávací časovače), použijte místo RelativeDateTimeFormatter DateComponentsFormatter — umožňuje zobrazit více jednotek současně.
Chybějící kontrola záporného TimeInterval — pokud je budoucí datum předáno jako minulé (záporná hodnota v string(fromTimeInterval:)), formatter může vrátit nesprávný řetězec. Vždy kontrolujte znaménko intervalu před předáním formatteru, zejména při práci se serverovými daty, kde může časové pásmo zkreslit výpočet.
Podle Hacker News (2024) je jedním z nejdiskutovanějších problémů RelativeDateTimeFormatter nedostatek vestavěné podpory „včera“ a „dnes“ pro angličtinu. Místo „včera“ vrátí formatter pro rozdíl 90000 sekund „před 1 dnem“. Pro ruštinu takový problém neexistuje — „před 1 dnem“ zní přirozeně, ale pro anglické UI je preferováno „yesterday“. Tato funkce není podporována a vyžaduje ruční kontrolu přes Calendar.isDateInToday/Yesterday.
Často kladené dotazy
RelativeDateTimeFormatter — třída Foundation pro zobrazení dat v relativním formátu: „před 5 minutami“, „za 2 dny“. Dostupná od iOS 13 a macOS 10.15.
Podle principu největší nenulové jednotky — sekundy, minuty, hodiny, dny, týdny, měsíce nebo roky. Například pro rozdíl 3720 sekund (1 hodina 2 minuty) bude vybrána jednotka „hodina“, nikoli „minuty“.
Nastavte vlastnost locale na požadovanou instanci Locale. Ve výchozím nastavení se používá Locale.current. Příklad: formatter.locale = Locale(identifier: "de_DE") pro němčinu.
.numeric — plná forma („před 3 dny“), .abbreviated — zkrácená („před 3 dny“). Volba závisí na kontextu: numeric pro hlavní UI, abbreviated pro kompaktní prvky.
Přidejte ruční kontrolu pro interval menší než 5–10 sekund. RelativeDateTimeFormatter nepodporuje „právě teď“ — pro malé intervaly vrací „před 0 sekundami“. Použijte podmínkovou logiku s prahem.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také