A RelativeDateTimeFormatter egy Foundation-osztály iOS-ben és macOS-ben, amely az abszolút dátumokat emberi számára olvasható relatív megfogalmazásokká alakítja: „5 perce“, „tegnap“, „3 nap múlva“. A Apple Developer Documentation, 2024 szerint a RelativeDateTimeFormatter automatikusan kiválasztja a megfelelő egységet (másodperc, perc, óra, nap), és az eszköz aktuális lokáljának nyelvén lokalizálja a kimenetet. Ellentétben a Calendar segítségével történő manuális dátumkülönbség-számítással, ez az osztály figyelembe veszi minden nyelv nyelvi sajátosságait: egyes nyelveknél a számnevek ragozódnak, másoknál a „tegnap“ szóhoz speciális alakot használ. Az osztály iOS 13 és macOS 10.15 verziótól érhető el.
A legfontosabb
RelativeDateTimeFormatter a Formatter alosztálya a Foundationben, amely elfogad egy Date-et (vagy másodpercben kifejezett különbséget), és lokalizált karakterláncot ad vissza relatív idővel. Például egy 5 perccel korábbi dátumhoz az ru_RU esetén az „5 perce“, az en_US esetén pedig az „5 minutes ago“ szöveget adja vissza. Az osztály három időbeli kontextust támogat: múlt (past), jövő (future) és jelen (present).
A RelativeDateTimeFormatter belső logikája a Calendar és a Locale segítségével számolja ki a dátumok közötti különbséget, és választja ki a helyes nyelvtani alakot. A magyar nyelvhez az osztály helyesen ragozza a számneveket: „1 perce“, „2 perce“, „5 perce“. Az angolhoz — az „minute ago“ és „minutes ago“ közül választ. Ez a funkció az ICU (International Components for Unicode) adataira épül, és nem igényel további beállítást a fejlesztő részéről.
Az Apple WWDC 2019 szerint a RelativeDateTimeFormatter a lokalizáció egyszerűsítésére vált a keretrendszer részévé — megjelenése előtt a fejlesztőknek manuálisan kellett kiszámítaniuk a dátumkülönbséget, és a String.localizedStringWithFormat segítségével lokalizált karakterláncokat beilleszteni. Ez ragozási hibákhoz (különösen a szláv és arab nyelveknél) és a mértékegységek helytelen kiválasztásához vezetett.
A működés algoritmusa a RelativeDateTimeFormatter három lépésből áll: az átadott dátum és az aktuális pillanat közötti különbség kiszámítása, a megfelelő egység kiválasztása (a legnagyobb, amely nem ad nullát), és a lokalizációt figyelembe vevő formázás. Például a 3720 másodperces (1 óra 2 perc) különbséghez az „óra“ egység kerül kiválasztásra, az eredmény pedig „1 órával ezelőtt“ lesz, nem „62 perccel ezelőtt“.
Az egységek a „legnagyobb nem nulla“ elve alapján kerülnek kiválasztásra: ha a különbség nagyobb, mint 86400 másodperc (1 nap), napokat használunk; ha nagyobb, mint 604800 (1 hét) — heteket, és így tovább. Ez az algoritmus garantálja, hogy az eredmény mindig természetesen olvasható: az „518400 másodperccel ezelőtt“ helyett a felhasználó „6 nappal ezelőtt“-et lát. Az egységek pontos határait az aktuális lokál naptára határozza meg.
| Különbség-tartomány | Egység | Példa ru_RU esetén |
|---|---|---|
| 0–59 másodperc | Seconds | 30 másodperccel ezelőtt |
| 1–59 perc | Minutes | 5 perce |
| 1–23 óra | Hours | 3 órával ezelőtt |
| 1–6 nap | Days | 2 nappal ezelőtt |
| 7–27 nap | Weeks | 1 hete |
| 28 nap–11 hónap | Months | 3 hónapja |
| 12+ hónap | Years | 1 éve |
A formázási kontextus határozza meg a kifejezés végét. A múlthoz: „ezelőtt“ (magyar), „ago“ (angol). A jövőhöz: „3 nap múlva“ (magyar), „in 3 days“ (angol). A jelenhez: „most“ (magyar), „now“ (angol). A kontextus a localizeString(fromTimeInterval:) metódussal vagy közvetlenül a string(from: Date) segítségével állítható be.
RelativeDateTimeFormatter több beállítást kínál a kimenet szabályozására: a unitsStyle tulajdonság határozza meg a formázási stílust (numeric, abbreviated, full, spellOut), a maximumUnitCount pedig korlátozza a megjelenített egységek számát. Például a maximumUnitCount = 1 beállításnál az 1 óra 30 perc különbség „1 órával ezelőtt“ formában jelenik meg az „1 óra 30 perce“ helyett.
Az egységek korlátozása: alapértelmezésben a RelativeDateTimeFormatter csak egy (a legnagyobb) egységet jelenít meg. A maximumUnitCount = 2 beállítás aktiválja a következő egységet a pontosabb leírás érdekében: „1 óra 30 perce“. Ez azonban túl hosszúvá teheti a karakterláncot a rövid üzenetekhez (push, értesítések). A UI-hoz ajánlott megtartani a maximumUnitCount = 1 értéket.
import Foundation
let formatter = RelativeDateTimeFormatter()
// Stílusok beállítása
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1
// Példák különböző dátumokkal
let fiveMinAgo = Date().addingTimeInterval(-300)
print("5 perce: \(formatter.localizedString(for: fiveMinAgo, relativeTo: Date()))")
let twoDaysLater = Date().addingTimeInterval(172800)
print("2 nap múlva: \(formatter.localizedString(for: twoDaysLater, relativeTo: Date()))")
// Rövidített stílus
formatter.unitsStyle = .abbreviated
let oneWeekAgo = Date().addingTimeInterval(-604800)
print("Rövidített: \(formatter.localizedString(for: oneWeekAgo, relativeTo: Date()))")
// Teljes stílus (betűkkel)
formatter.unitsStyle = .full
let threeHours = Date().addingTimeInterval(10800)
print("Teljes: \(formatter.localizedString(for: threeHours, relativeTo: Date()))")
Stílusválasztás különböző kontextusokhoz: a hírfolyamhoz használjon .numeric-ot maximumUnitCount = 1 értékkel — ez a Twitter, Instagram és Facebook szabványa. Az Accessibility (VoiceOver) számára használjon .full-t — a betűkkel írt számok természetesebben olvashatók. A kompakt elemekhez (értesítési sarok, állapotsor) használjon .abbreviated-et a helymegtakarítás érdekében.
Alapvető használat a RelativeDateTimeFormatter egy példány létrehozásából, a tulajdonságok beállításából és az egyik formázási metódus meghívásából áll. A főbb metódusok: localizedString(for:relativeTo:) — dátumok párjához, localizedString(fromTimeInterval:) — a másodpercben kifejezett különbséghez, és string(for:) — Date-hez automatikus kontextussal (múlt/jövő).
import Foundation
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1
// Közösségi hálózat UI példái
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)")
}
// Jövőbeli dátumok
let reminderFormatter = RelativeDateTimeFormatter()
reminderFormatter.unitsStyle = .abbreviated
let inOneHour = Date().addingTimeInterval(3600)
let reminderText = reminderFormatter.localizedString(
for: inOneHour,
relativeTo: Date()
)
print("Emlékeztető: \(reminderText)")
Az „éppen most“ forgatókönyv kezelése — a RelativeDateTimeFormatter nem rendelkezik beépített támogatással az „éppen most“ kifejezéshez nagyon kis intervallumok esetén. Az 5 másodpercnél kisebb különbséghez „0 másodperccel ezelőtt“-et ad vissza, ami nem szép a UI-ban. Ajánlott a formatter hívását feltételes logikába csomagolni: ha a különbség kisebb a beállított küszöbértéknél (például 5 másodperc) — az „éppen most“ szöveget manuálisan jelenítse meg, egyébként adja át a dátumot a formatternek.
import Foundation
func relativeTimeString(from date: Date) -> String {
let interval = Date().timeIntervalSince(date)
// Az „éppen most“ küszöbérték
if interval < 5 {
return "just now"
}
// A „ma“ küszöbérték
if interval < 60 {
return "just now"
}
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1
// Megjelenítés „ezelőtt“ utótag nélkül
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)))
A string(fromTimeInterval:) metódus elfogadja a másodpercben kifejezett különbséget, és automatikusan meghatározza a kontextust (pozitív érték — jövő, negatív — múlt). Ez akkor kényelmes, ha a különbség már ismert (például a szervertől unix timestampként érkezett). Ebben az esetben nem kell Date-et létrehozni — a különbség közvetlenül átadható.
RelativeDateTimeFormatter automatikusan lokalizálja a kimenetet a Locale.current alapján. A formázási nyelv megváltoztatásához állítsa be a locale tulajdonságot — a DateFormatter-rel ellentétben a RelativeDateTimeFormatter esetén a locale nincs rögzítve, és minden híváshoz módosítható. Ez lehetővé teszi a relatív dátumok megjelenítését az interfész nyelvétől eltérő nyelven (például az eredeti nyelvű tartalom esetén).
A relatív dátumok lokalizációjának bonyolultsága a különböző nyelvek nyelvtani sajátosságaiban rejlik. Az orosz nyelv a számnevek különböző alakjait igényli: „1 perc“, „2 perc“, „5 perc“. Az arab — a 3-tól 10-ig terjedő számokhoz többes számú alakot, a 11+ esetén speciális alakokat használ. A kínai — egyáltalán nem ragoz, ami egyszerűsíti a feladatot. A RelativeDateTimeFormatter mindezeket az eseteket az ICU-szabályokon keresztül fedi le, további kód nélkül.
import Foundation
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1
let targetDate = Date().addingTimeInterval(-7200) // 2 órával ezelőtt
// Különböző lokálok
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)")
}
// Ellenőrizze az orosz ragozást
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)) perc: \(formatter.localizedString(for: date, relativeTo: Date()))")
}
Fontos részlet: a RelativeDateTimeFormatter a .numeric beállításoknál figyelmen kívül hagyja a TimeZone-t a különbség kiszámításakor — az abszolút másodpercben kifejezett különbséget használ. Azonban a .full stílushoz (betűkkel írt számokkal) és a speciális esetekhez (tegnap, ma) a TimeZone figyelembe van véve. Az egységesség érdekében mindig állítsa be kifejezetten a TimeZone-t, különösen, ha az alkalmazás UTC-ben használt szerverdátumokkal dolgozik.
A TimeZone figyelmen kívül hagyása a relatív dátumok kiszámításakor — gyakori hiba a szerverdátumokkal való munka során. Ha a szerver UTC-ben küldi a Date-et, és a RelativeDateTimeFormatter a TimeZone.current-et használja, a különbség hibásan számolható ki az aktuális pillanathoz közeli dátumoknál. Ajánlott a szerveradatokhoz mindig beállítani a formatter.timeZone = TimeZone(secondsFromGMT: 0) értéket.
Helytelen egységválasztás rövid intervallumokhoz — a RelativeDateTimeFormatter a legnagyobb egységre kerekíti a különbséget. 25 órához az eredmény „1 nappal ezelőtt“ lesz, ami félrevezetheti a felhasználót. Ha nagy pontosság szükséges (például visszaszámlálóknál), a RelativeDateTimeFormatter helyett a DateComponentsFormatter-t használja — ez lehetővé teszi több egység egyidejű megjelenítését.
A negatív TimeInterval ellenőrzésének hiánya — ha egy jövőbeli dátumot múltként adnak át (negatív érték a string(fromTimeInterval:)-ban), a formatter helytelen karakterláncot adhat vissza. Mindig ellenőrizze az intervallum előjelét, mielőtt átadja a formatternek, különösen a szerveradatokkal végzett munka során, ahol az időzóna torzíthatja a számítást.
A Hacker News (2024) szerint a RelativeDateTimeFormatter egyik legtöbbet vitatott problémája az „tegnap“ és „ma“ beépített támogatásának hiánya az angol nyelvben. Az „tegnap“ helyett a formatter a 90000 másodperces különbséghez „1 nappal ezelőtt“-et ad vissza. Az orosz nyelvnél nincs ilyen probléma — az „1 nappal ezelőtt“ természetesen hangzik, de az angol UI-hoz a „yesterday“ előnyösebb. Ez a funkció nem támogatott, és manuális ellenőrzést igényel a Calendar.isDateInToday/Yesterday segítségével.
Gyakran ismételt kérdések
RelativeDateTimeFormatter — Foundation-osztály a dátumok relatív formátumban történő megjelenítésére: „5 perce“, „2 nap múlva“. iOS 13 és macOS 10.15 verziótól érhető el.
A legnagyobb nem nulla egység elve szerint — másodperc, perc, óra, nap, hét, hónap vagy év. Például a 3720 másodperces (1 óra 2 perc) különbséghez az „óra“ egység kerül kiválasztásra, nem a „perc“.
Állítsa be a locale tulajdonságot a kívánt Locale-példányra. Alapértelmezésben a Locale.current kerül használatra. Példa: formatter.locale = Locale(identifier: "de_DE") a német nyelvhez.
.numeric — teljes forma („3 nappal ezelőtt“), .abbreviated — rövidített („3 nappal ezelőtt“). A választás a kontextustól függ: numeric a fő UI-hoz, abbreviated a kompakt elemekhez.
Adjon hozzá manuális ellenőrzést az 5–10 másodpercnél kisebb intervallumhoz. A RelativeDateTimeFormatter nem támogatja az „éppen most“ szöveget — a kis intervallumokhoz „0 másodperccel ezelőtt“-et ad vissza. Használjon küszöbértékes feltételes logikát.
Összegzés
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is