RelativeDateTimeFormatter är en Foundation-klass i iOS och macOS som omvandlar absoluta datum till lättlästa relativa formuleringar: “5 minuter sedan”, “igår”, “om 3 dagar”. Enligt Apple Developer Documentation, 2024 väljer RelativeDateTimeFormatter automatiskt lämplig enhet (sekunder, minuter, timmar, dagar) och lokaliserar utdata på språket för enhetens aktuella locale. Till skillnad från manuell beräkning av skillnaden mellan datum via Calendar tar denna klass hänsyn till varje språks språkliga egenskaper: för vissa språk böjs räkneord, för andra används en särskild form för ordet “igår”. Klassen är tillgänglig från och med iOS 13 och macOS 10.15.
Det viktigaste
RelativeDateTimeFormatter är en underklass till Formatter i Foundation som tar emot ett Date (eller skillnaden i sekunder) och returnerar en lokaliserad sträng med relativ tid. Till exempel, för ett datum 5 minuter tidigare än det aktuella returnerar det “5 minuter sedan” för ru_RU eller “5 minutes ago” för en_US. Klassen stödjer tre tidskontexter: dåtid (past), framtid (future) och nutid (present).
Den interna logiken i RelativeDateTimeFormatter använder Calendar och Locale för att beräkna skillnaden mellan datum och välja rätt grammatisk form. För svenska böjer klassen räkneord korrekt: “1 minut sedan”, “2 minuter sedan”, “5 minuter sedan”. För engelska — väljer mellan “minute ago” och “minutes ago”. Denna funktionalitet bygger på ICU-data (International Components for Unicode) och kräver ingen extra konfiguration från utvecklaren.
Enligt Apple WWDC 2019 blev RelativeDateTimeFormatter en del av ramverket för att förenkla lokalisering — före dess uppkomst var utvecklare tvungna att manuellt beräkna skillnaden mellan datum och infoga lokaliserade strängar via String.localizedStringWithFormat. Detta ledde till böjningsfel (särskilt för slaviska och arabiska språk) och felaktigt val av måttenheter.
Arbetsalgoritmen för RelativeDateTimeFormatter består av tre steg: beräkna skillnaden mellan det angivna datumet och det aktuella ögonblicket, välja lämplig enhet (den största som inte ger noll) och formatera med hänsyn till locale. Till exempel, för en skillnad på 3720 sekunder (1 timme 2 minuter) väljs enheten “timme” och resultatet blir “för 1 timme sedan”, inte “för 62 minuter sedan”.
Enheter väljs enligt principen “största icke-noll”: om skillnaden är större än 86400 sekunder (1 dag) används dagar; om större än 604800 (1 vecka) — veckor och så vidare. Denna algoritm garanterar att resultatet alltid läses naturligt: istället för “518400 sekunder sedan” ser användaren “6 dagar sedan”. De exakta gränserna för enheterna bestäms av kalendern för den aktuella locale.
| Skillnadens intervall | Enhet | Exempel för ru_RU |
|---|---|---|
| 0–59 sekunder | Seconds | för 30 sekunder sedan |
| 1–59 minuter | Minutes | för 5 minuter sedan |
| 1–23 timmar | Hours | för 3 timmar sedan |
| 1–6 dagar | Days | för 2 dagar sedan |
| 7–27 dagar | Weeks | för 1 vecka sedan |
| 28 dagar–11 månader | Months | för 3 månader sedan |
| 12+ månader | Years | för 1 år sedan |
Formateringskontexten bestämmer slutet av frasen. För dåtid: “sedan” (svenska), “ago” (engelska). För framtid: “om 3 dagar” (svenska), “in 3 days” (engelska). För nutid: “nu” (svenska), “now” (engelska). Kontexten ställs in via metoden localizeString(fromTimeInterval:) eller direkt via string(from: Date).
RelativeDateTimeFormatter erbjuder flera inställningar för att kontrollera utdata: egenskapen unitsStyle bestämmer formateringsstilen (numeric, abbreviated, full, spellOut) och maximumUnitCount begränsar antalet visade enheter. Till exempel, med maximumUnitCount = 1 visas en skillnad på 1 timme 30 minuter som “för 1 timme sedan” istället för “för 1 timme 30 minuter sedan”.
Begränsning av enheter: som standard visar RelativeDateTimeFormatter endast en (den största) enhet. Genom att ställa in maximumUnitCount = 2 aktiveras nästa enhet för en mer exakt beskrivning: “för 1 timme 30 minuter sedan”. Detta kan dock göra strängen alltför lång för korta meddelanden (push, aviseringar). För UI rekommenderas att behålla maximumUnitCount = 1.
import Foundation
let formatter = RelativeDateTimeFormatter()
// Konfigurera stilar
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1
// Exempel med olika datum
let fiveMinAgo = Date().addingTimeInterval(-300)
print("för 5 min sedan: \(formatter.localizedString(for: fiveMinAgo, relativeTo: Date()))")
let twoDaysLater = Date().addingTimeInterval(172800)
print("om 2 dagar: \(formatter.localizedString(for: twoDaysLater, relativeTo: Date()))")
// Förkortad stil
formatter.unitsStyle = .abbreviated
let oneWeekAgo = Date().addingTimeInterval(-604800)
print("Förkortad: \(formatter.localizedString(for: oneWeekAgo, relativeTo: Date()))")
// Fullständig stil (med bokstäver)
formatter.unitsStyle = .full
let threeHours = Date().addingTimeInterval(10800)
print("Fullständig: \(formatter.localizedString(for: threeHours, relativeTo: Date()))")
Val av stil för olika kontexter: för nyhetsflödet använd .numeric med maximumUnitCount = 1 — detta är standarden för Twitter, Instagram och Facebook. För Accessibility (VoiceOver) använd .full — siffror skrivna med bokstäver läses mer naturligt. För kompakta element (aviseringens hörn, statusrad) använd .abbreviated för att spara utrymme.
Grundläggande användning av RelativeDateTimeFormatter består i att skapa en instans, konfigurera egenskaper och anropa en av formateringsmetoderna. De viktigaste metoderna: localizedString(for:relativeTo:) — för ett par datum, localizedString(fromTimeInterval:) — för skillnaden i sekunder, och string(for:) — för Date med automatisk kontext (dåtid/framtid).
import Foundation
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1
// UI-exempel för sociala nätverk
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)")
}
// Framtida datum
let reminderFormatter = RelativeDateTimeFormatter()
reminderFormatter.unitsStyle = .abbreviated
let inOneHour = Date().addingTimeInterval(3600)
let reminderText = reminderFormatter.localizedString(
for: inOneHour,
relativeTo: Date()
)
print("Påminnelse: \(reminderText)")
Hantera scenariot “just nu” — RelativeDateTimeFormatter har inget inbyggt stöd för uttrycket “just nu” för mycket små intervall. För en skillnad mindre än 5 sekunder returnerar det “0 sekunder sedan”, vilket inte ser bra ut i UI. Det rekommenderas att slå in formatter-anropet i villkorlig logik: om skillnaden är mindre än den fastställda tröskeln (t.ex. 5 sekunder) — visa “just nu” manuellt, annars skicka datumet till formatter.
import Foundation
func relativeTimeString(from date: Date) -> String {
let interval = Date().timeIntervalSince(date)
// Tröskel “just nu”
if interval < 5 {
return "just now"
}
// Tröskel “idag”
if interval < 60 {
return "just now"
}
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1
// Visa utan suffixet “sedan”
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)))
Metoden string(fromTimeInterval:) tar emot skillnaden i sekunder och bestämmer automatiskt kontexten (positivt värde — framtid, negativt — dåtid). Detta är bekvämt när skillnaden redan är känd (t.ex. mottagen från servern som unix timestamp). I detta fall behöver du inte skapa ett Date — skillnaden skickas direkt.
RelativeDateTimeFormatter lokaliserar automatiskt utdata baserat på Locale.current. För att ändra formateringsspråket, ställ in egenskapen locale — till skillnad från DateFormatter är locale för RelativeDateTimeFormatter inte fast och kan ändras för varje anrop. Detta gör det möjligt att visa relativa datum på ett annat språk än gränssnittsspråket (t.ex. innehåll på originalspråket).
Komplexiteten med att lokalisera relativa datum ligger i de grammatiska egenskaperna hos olika språk. Ryska kräver olika former av räkneord: “1 minut”, “2 minuter”, “5 minuter”. Arabiska — använder pluralform för siffror från 3 till 10 och speciella former för 11+. Kinesiska — har ingen böjning alls, vilket förenklar uppgiften. RelativeDateTimeFormatter täcker alla dessa fall genom ICU-regler, utan behov av extra kod.
import Foundation
let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1
let targetDate = Date().addingTimeInterval(-7200) // för 2 timmar sedan
// Olika 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)")
}
// Kontrollera rysk böjning
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()))")
}
Viktig nyans: RelativeDateTimeFormatter ignorerar TimeZone när det beräknar skillnaden för .numeric-inställningar — det använder den absoluta skillnaden i sekunder. För .full-stilen (med siffror skrivna med bokstäver) och speciella fall (igår, idag) beaktas dock TimeZone. Ställ alltid in TimeZone explicit för konsekvens, särskilt om appen arbetar med serverdatum i UTC.
Att ignorera TimeZone vid beräkning av relativa datum — ett vanligt fel vid arbete med serverdatum. Om servern skickar Date i UTC och RelativeDateTimeFormatter använder TimeZone.current, kan skillnaden beräknas felaktigt för datum nära det aktuella ögonblicket. Det rekommenderas att alltid ställa in formatter.timeZone = TimeZone(secondsFromGMT: 0) för serverdata.
Felaktigt val av enhet för korta intervall — RelativeDateTimeFormatter avrundar skillnaden till den största enheten. För 25 timmar blir resultatet “för 1 dag sedan”, vilket kan vilseleda användaren. Om hög precision krävs (t.ex. för nedräkningstimer), använd DateComponentsFormatter istället för RelativeDateTimeFormatter — den gör det möjligt att visa flera enheter samtidigt.
Avsaknad av kontroll av negativ TimeInterval — om ett datum i framtiden skickas som dåtid (negativt värde i string(fromTimeInterval:)), kan formatter returnera en felaktig sträng. Kontrollera alltid intervallets tecken innan du skickar till formatter, särskilt vid arbete med serverdata där tidszonen kan förvränga beräkningen.
Enligt Hacker News (2024) är en av de mest diskuterade problemen med RelativeDateTimeFormatter avsaknaden av inbyggt stöd för “igår” och “idag” på engelska. Istället för “igår” returnerar formatter för en skillnad på 90000 sekunder “för 1 dag sedan”. För ryska finns inget sådant problem — “för 1 dag sedan” låter naturligt, men för ett engelskt UI är “yesterday” att föredra. Denna funktionalitet stöds inte och kräver manuell kontroll via Calendar.isDateInToday/Yesterday.
Vanliga frågor
RelativeDateTimeFormatter — Foundation-klass för att visa datum i relativt format: “5 minuter sedan”, “om 2 dagar”. Tillgänglig från iOS 13 och macOS 10.15.
Enligt principen om största icke-noll enhet — sekunder, minuter, timmar, dagar, veckor, månader eller år. Till exempel, för skillnaden på 3720 sekunder (1 timme 2 minuter) väljs enheten “timme”, inte “minuter”.
Ställ in egenskapen locale på önskad Locale-instans. Som standard används Locale.current. Exempel: formatter.locale = Locale(identifier: "de_DE") för tyska.
.numeric — fullständig form (“för 3 dagar sedan”), .abbreviated — förkortad (“för 3 dagar sedan”). Valet beror på kontexten: numeric för huvud-UI, abbreviated för kompakta element.
Lägg till manuell kontroll för ett intervall mindre än 5–10 sekunder. RelativeDateTimeFormatter stödjer inte “just nu” — för små intervall returnerar det “0 sekunder sedan”. Använd villkorlig logik med en tröskel.
Sammanfattning
Vi utvecklar en mobil applikation nyckelfärdigt
IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.
Läs också