RelativeDateTimeFormatter: esența, datele relative și Swift

Autor: IT Sectr Publicat: 2026-07-13 Timp de citire: 10 min

RelativeDateTimeFormatter este o clasă Foundation în iOS și macOS care transformă datele absolute în formulări relative lizibile pentru om: „acum 5 minute“, „ieri“, „peste 3 zile“. Conform Documentației pentru dezvoltatori Apple, 2024, RelativeDateTimeFormatter selectează automat unitatea potrivită (secunde, minute, ore, zile) și localizează rezultatul în limba localei curente a dispozitivului. Spre deosebire de calcularea manuală a diferenței dintre date prin Calendar, această clasă ține cont de particularitățile lingvistice ale fiecărei limbi: pentru unele limbi numeralele se declină, pentru altele — se folosește o formă specială pentru cuvântul „ieri“. Clasa este disponibilă începând cu iOS 13 și macOS 10.15.

Esențialul

  • RelativeDateTimeFormatter — clasă pentru afișarea datelor relative în iOS și macOS (iOS 13+)
  • Rezultat localizat — selectează automat formulările în limba localei curente
  • Trei tipuri de context — past (în urmă), future (peste), present (acum) cu formulări diferite
  • Selectarea automată a unității — secunde, minute, ore, zile, săptămâni, luni, ani
  • Configurarea stilului — numeric (peste 3 zile) sau abbreviated (peste 3 zile)

Ce este RelativeDateTimeFormatter?

RelativeDateTimeFormatter este o subclasă a Formatter în Foundation care primește Date (sau diferența în secunde) și returnează un șir localizat cu timp relativ. De exemplu, pentru o dată cu 5 minute mai devreme decât cea curentă va returna „acum 5 minute“ pentru ru_RU sau „5 minutes ago“ pentru en_US. Clasa suportă trei contexte temporale: trecut (past), viitor (future) și prezent (present).

Logica internă a RelativeDateTimeFormatter folosește Calendar și Locale pentru a calcula diferența dintre date și a alege forma gramaticală corectă. Pentru limba română clasa declină corect numeralele: „acum 1 minut“, „acum 2 minute“, „acum 5 minute“. Pentru engleză — alege între „minute ago“ și „minutes ago“. Această funcționalitate se bazează pe datele ICU (International Components for Unicode) și nu necesită configurare suplimentară din partea dezvoltatorului.

Conform Apple WWDC 2019, RelativeDateTimeFormatter a devenit parte a framework-ului pentru simplificarea localizării — înainte de apariția sa dezvoltatorii erau nevoiți să calculeze manual diferența datelor și să insereze șiruri localizate prin String.localizedStringWithFormat. Aceasta ducea la erori de declinare (mai ales pentru limbile slave și arabe) și la selectarea incorectă a unităților de măsură.

Cum afișează RelativeDateTimeFormatter „acum 5 minute“?

Algoritmul de funcționare RelativeDateTimeFormatter constă din trei pași: calcularea diferenței dintre data transmisă și momentul curent, selectarea unității potrivite (cea mai mare care nu dă zero) și formatarea cu luarea în considerare a localei. De exemplu, pentru o diferență de 3720 de secunde (1 oră 2 minute) va fi selectată unitatea „oră“, iar rezultatul va fi „acum 1 oră“, nu „acum 62 de minute“.

Unitățile sunt selectate după principiul „celei mai mari nenule“: dacă diferența este mai mare de 86400 de secunde (1 zi), se folosesc zilele; dacă mai mare de 604800 (1 săptămână) — săptămânile și așa mai departe. Acest algoritm garantează că rezultatul este mereu citit natural: în loc de „acum 518400 de secunde“ utilizatorul vede „acum 6 zile“. Limitele exacte ale unităților sunt determinate de calendarul localei curente.

Intervalul diferențeiUnitateaExemplu pentru ru_RU
0–59 de secundeSecondsacum 30 de secunde
1–59 de minuteMinutesacum 5 minute
1–23 de oreHoursacum 3 ore
1–6 zileDaysacum 2 zile
7–27 de zileWeeksacum 1 săptămână
28 de zile–11 luniMonthsacum 3 luni
12+ luniYearsacum 1 an

Contextul de formatare determină finalul frazei. Pentru trecut: „în urmă“ (română), „ago“ (engleză). Pentru viitor: „peste 3 zile“ (română), „in 3 days“ (engleză). Pentru prezent: „acum“ (română), „now“ (engleză). Contextul este setat prin metoda localizeString(fromTimeInterval:) sau direct prin string(from: Date).

Setările unităților și stilurilor

RelativeDateTimeFormatter oferă mai multe setări pentru controlul rezultatului: proprietatea unitsStyle determină stilul de formatare (numeric, abbreviated, full, spellOut), iar maximumUnitCount limitează numărul de unități afișate. De exemplu, cu maximumUnitCount = 1 o diferență de 1 oră 30 de minute va fi afișată ca „acum 1 oră“ în loc de „acum 1 oră 30 de minute“.

Stilurile de formatare

  • .numeric — valoarea numerică completă: „acum 3 zile“, „peste 2 săptămâni“. Recomandat pentru UI în mod implicit
  • .abbreviated — forma prescurtată: „acum 3 zile“, „peste 2 săpt.“. Pentru afișarea compactă în tabele și liste
  • .full — forma verbală fără cifre: „acum trei zile“. Pentru Accessibility și interfețe vocale
  • .spellOut — forma literală cu scriere alternativă: „three days ago“. Folosit rar, mai ales pentru utilizări speciale

Limitarea unităților: în mod implicit RelativeDateTimeFormatter afișează doar o singură (cea mai mare) unitate. Setarea maximumUnitCount = 2 activează următoarea unitate pentru o descriere mai precisă: „acum 1 oră 30 de minute“. Totuși, aceasta poate face șirul excesiv de lung pentru mesaje scurte (push, notificări). Pentru UI se recomandă păstrarea maximumUnitCount = 1.

swift
import Foundation

let formatter = RelativeDateTimeFormatter()

// Configurați stilurile
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1

// Exemple cu date diferite
let fiveMinAgo = Date().addingTimeInterval(-300)
print("acum 5 min: \(formatter.localizedString(for: fiveMinAgo, relativeTo: Date()))")

let twoDaysLater = Date().addingTimeInterval(172800)
print("peste 2 zile: \(formatter.localizedString(for: twoDaysLater, relativeTo: Date()))")

// Stil prescurtat
formatter.unitsStyle = .abbreviated
let oneWeekAgo = Date().addingTimeInterval(-604800)
print("Prescurtat: \(formatter.localizedString(for: oneWeekAgo, relativeTo: Date()))")

// Stil complet (în litere)
formatter.unitsStyle = .full
let threeHours = Date().addingTimeInterval(10800)
print("Complet: \(formatter.localizedString(for: threeHours, relativeTo: Date()))")

Selectarea stilului pentru diferite contexte: pentru fluxul de știri folosiți .numeric cu maximumUnitCount = 1 — este standardul pentru Twitter, Instagram și Facebook. Pentru Accessibility (VoiceOver) folosiți .full — numerele în litere se citesc mai natural. Pentru elementele compacte (colț de notificare, bara de status) folosiți .abbreviated pentru a economisi spațiu.

RelativeDateTimeFormatter în Swift: exemple

Utilizarea de bază a RelativeDateTimeFormatter se reduce la crearea unei instanțe, configurarea proprietăților și apelarea uneia dintre metodele de formatare. Metodele principale: localizedString(for:relativeTo:) — pentru o pereche de date, localizedString(fromTimeInterval:) — pentru diferența în secunde, și string(for:) — pentru Date cu context automat (trecut/viitor).

swift
import Foundation

let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1

// Exemple de UI pentru rețele sociale
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)")
}

// Date viitoare
let reminderFormatter = RelativeDateTimeFormatter()
reminderFormatter.unitsStyle = .abbreviated
let inOneHour = Date().addingTimeInterval(3600)
let reminderText = reminderFormatter.localizedString(
    for: inOneHour,
    relativeTo: Date()
)
print("Memento: \(reminderText)")

Tratarea scenariului „chiar acum“ — RelativeDateTimeFormatter nu are suport integrat pentru expresia „chiar acum“ pentru intervale foarte mici. Pentru o diferență mai mică de 5 secunde va returna „acum 0 secunde“, ceea ce arată urât în UI. Se recomandă împachetarea apelului formatter-ului în logică condițională: dacă diferența este mai mică de pragul stabilit (de exemplu, 5 secunde) — afișați „chiar acum“ manual, altfel transmiteți data către formatter.

swift
import Foundation

func relativeTimeString(from date: Date) -> String {
    let interval = Date().timeIntervalSince(date)

    // Pragul „chiar acum“
    if interval < 5 {
        return "just now"
    }

    // Pragul „azi“
    if interval < 60 {
        return "just now"
    }

    let formatter = RelativeDateTimeFormatter()
    formatter.unitsStyle = .numeric
    formatter.maximumUnitCount = 1

    // Afișare fără sufixul „în urmă“
    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:) primește diferența în secunde și determină automat contextul (valoare pozitivă — viitor, negativă — trecut). Este convenabil atunci când diferența este deja cunoscută (de exemplu, primită de la server ca unix timestamp). În acest caz nu este nevoie să creați Date — diferența este transmisă direct.

Localizarea datelor relative

RelativeDateTimeFormatter localizează automat rezultatul pe baza Locale.current. Pentru a schimba limba de formatare setați proprietatea locale — spre deosebire de DateFormatter, pentru RelativeDateTimeFormatter locale nu este fixată și poate fi schimbată pentru fiecare apel. Acest lucru permite afișarea datelor relative într-o limbă diferită de limba interfeței (de exemplu, conținut în limba originală).

Complexitatea localizării datelor relative constă în particularitățile gramaticale ale diferitelor limbi. Limba rusă necesită forme diferite ale numeralelor: „1 minut“, „2 minute“, „5 minute“. Araba — folosește forma de plural pentru numerele de la 3 la 10 și forme speciale pentru 11+. Chineza — nu are deloc declinare, ceea ce simplifică sarcina. RelativeDateTimeFormatter acoperă toate aceste cazuri prin regulile ICU, fără necesitatea unui cod suplimentar.

swift
import Foundation

let formatter = RelativeDateTimeFormatter()
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1

let targetDate = Date().addingTimeInterval(-7200) // acum 2 ore

// Locali diferite
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)")
}

// Verificați declinarea rusă
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()))")
}

Un detaliu important: RelativeDateTimeFormatter ignoră TimeZone la calcularea diferenței pentru setările .numeric — folosește diferența absolută în secunde. Totuși, pentru stilul .full (cu numere în litere) și cazurile speciale (ieri, azi) TimeZone este luat în considerare. Setați întotdeauna TimeZone explicit pentru consistență, mai ales dacă aplicația lucrează cu date de server în UTC.

Erori tipice la formatare

Ignorarea TimeZone la calcularea datelor relative — o eroare frecventă la lucrul cu datele de server. Dacă serverul trimite Date în UTC, iar RelativeDateTimeFormatter folosește TimeZone.current, diferența poate fi calculată incorect pentru datele apropiate de momentul curent. Se recomandă să setați întotdeauna formatter.timeZone = TimeZone(secondsFromGMT: 0) pentru datele de server.

Selectarea incorectă a unității pentru intervale scurte — RelativeDateTimeFormatter rotunjește diferența la cea mai mare unitate. Pentru 25 de ore rezultatul va fi „acum 1 zi“, ceea ce poate induce utilizatorul în eroare. Dacă este necesară o precizie ridicată (de exemplu, pentru cronometre de numărătoare inversă), folosiți DateComponentsFormatter în loc de RelativeDateTimeFormatter — acesta permite afișarea mai multor unități simultan.

Lipsa verificării TimeInterval negativ — dacă o dată din viitor este transmisă ca trecută (valoare negativă în string(fromTimeInterval:)), formatter-ul poate returna un șir incorect. Verificați întotdeauna semnul intervalului înainte de transmiterea către formatter, mai ales la lucrul cu datele de server, unde fusul orar poate distorsiona calculul.

Conform Hacker News (2024), una dintre cele mai discutate probleme ale RelativeDateTimeFormatter este lipsa suportului integrat pentru „ieri“ și „azi“ în limba engleză. În loc de „ieri“, formatter-ul pentru o diferență de 90000 de secunde va returna „acum 1 zi“. Pentru limba rusă nu există o astfel de problemă — „acum 1 zi“ sună natural, dar pentru UI-ul în engleză „yesterday“ este preferabil. Această funcționalitate nu este suportată și necesită verificare manuală prin Calendar.isDateInToday/Yesterday.

Întrebări frecvente

Ce este RelativeDateTimeFormatter?

RelativeDateTimeFormatter — clasă Foundation pentru afișarea datelor în format relativ: „acum 5 minute“, „peste 2 zile“. Disponibil din iOS 13 și macOS 10.15.

Cum selectează RelativeDateTimeFormatter unitățile?

După principiul celei mai mari unități nenule — secunde, minute, ore, zile, săptămâni, luni sau ani. De exemplu, pentru diferența de 3720 de secunde (1 oră 2 minute) va fi selectată unitatea „oră“, nu „minute“.

Cum schimb limba rezultatului?

Setați proprietatea locale pe instanța Locale dorită. În mod implicit este folosit Locale.current. Exemplu: formatter.locale = Locale(identifier: "de_DE") pentru limba germană.

Care este diferența dintre .numeric și .abbreviated?

.numeric — forma completă („acum 3 zile“), .abbreviated — prescurtată („acum 3 zile“). Alegerea depinde de context: numeric pentru UI-ul principal, abbreviated pentru elementele compacte.

Cum afișez „chiar acum“ în loc de „acum 0 secunde“?

Adăugați o verificare manuală pentru un interval mai mic de 5–10 secunde. RelativeDateTimeFormatter nu suportă „chiar acum“ — pentru intervale mici returnează „acum 0 secunde“. Folosiți logică condițională cu prag.

Rezumat

  • RelativeDateTimeFormatter — clasă comodă pentru afișarea datelor relative în iOS 13+
  • Localizare automată — declinare corectă pentru toate limbile suportate prin ICU
  • Trei stiluri — .numeric (standard), .abbreviated (compact), .full (în litere)
  • Selectarea unității — automată după principiul celei mai mari valori nenule
  • Configurarea TimeZone — obligatorie pentru consistență la lucrul cu datele de server
  • Pragul „chiar acum“ — nu este suportat integrat; necesită verificare manuală a intervalului
  • Fără suport pentru „ieri“ — pentru limba engleză formatter-ul nu folosește forma yesterday

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și