RelativeDateTimeFormatter: суштина, релативни датуми и Swift

Аутор: IT Sectr Објављено: 2026-07-13 Време читања: 10 мин

RelativeDateTimeFormatter је класа Foundation у iOS и macOS која претвара апсолутне датуме у читљиве релативне формулације: „пре 5 минута“, „јуче“, „за 3 дана“. Према Епл Developer Documentation, 2024, RelativeDateTimeFormatter аутоматски бира одговарајућу јединицу (секунде, минуте, сате, дане) и локализује резултат на језику тренутне локале уређаја. За разлику од ручног рачунања разлике између датума преко Calendar-а, ова класа узима у обзир лингвистичке карактеристике сваког језика: за неке језике бројеви се мењају по падежима, за друге — користи се посебан облик за реч „јуче“. Класа је доступна од iOS 13 и macOS 10.15.

Главно

  • RelativeDateTimeFormatter — класа за приказ релативних датума у iOS и macOS (iOS 13+)
  • Локализован резултат — аутоматски бира формулације на језику тренутне локале
  • Три типа контекста — past (уназад), future (за), present (сада) са различитим формулацијама
  • Аутоматски избор јединице — секунде, минуте, сати, дани, недеље, месеци, године
  • Подешавање стила — numeric (за 3 дана) или abbreviated (за 3 дана)

Шта је RelativeDateTimeFormatter?

RelativeDateTimeFormatter је поткласа Formatter-а у Foundation-у која прима Date (или разлику у секундама) и враћа локализовани стринг са релативним временом. На пример, за датум 5 минута раније од тренутног вратиће „пре 5 минута“ за ru_RU или „5 minutes ago“ за en_US. Класа подржава три временска контекста: прошлост (past), будућност (future) и садашњост (present).

Унутрашња логика RelativeDateTimeFormatter-а користи Calendar и Locale за рачунање разлике између датума и избор исправне граматичке форме. За српски језик класа исправно мења бројеве: „пре 1 минут“, „пре 2 минута“, „пре 5 минута“. За енглески — бира између „minute ago“ и „minutes ago“. Ова функционалност се заснива на подацима ICU (International Components for Unicode) и не захтева додатна подешавања од стране програмера.

Према Apple WWDC 2019, RelativeDateTimeFormatter је постао део фрејмворка ради поједностављења локализације — пре његовог појављивања програмери су били приморани да ручно рачунају разлику датума и убацују локализоване стрингове преко String.localizedStringWithFormat. То је доводило до грешака у промени облика речи (посебно за словенске и арапске језике) и нетачног избора јединица мере.

Како RelativeDateTimeFormatter приказује „пре 5 минута“?

Алгоритам рада RelativeDateTimeFormatter-а се састоји из три корака: рачунање разлике између прослеђеног датума и тренутног тренутка, избор одговарајуће јединице (највеће која не даје нулу) и форматирање у складу са локалом. На пример, за разлику од 3720 секунди (1 сат 2 минута) биће изабрана јединица „сат“, а резултат ће бити „пре 1 сат“, а не „пре 62 минута“.

Јединице се бирају по принципу „највеће не-нулте“: ако је разлика већа од 86400 секунди (1 дан), користе се дани; ако је већа од 604800 (1 недеља) — недеље, и тако даље. Овај алгоритам гарантује да се резултат увек чита природно: уместо „пре 518400 секунди“ корисник види „пре 6 дана“. Тачне границе јединица одређује календар тренутне локале.

Опсег разликеЈединицаПример за ru_RU
0–59 секундиSecondsпре 30 секунди
1–59 минутаMinutesпре 5 минута
1–23 сатаHoursпре 3 сата
1–6 данаDaysпре 2 дана
7–27 данаWeeksпре 1 недеље
28 дана–11 месециMonthsпре 3 месеца
12+ месециYearsпре 1 годину

Контекст форматирања одређује завршетак фразе. За прошлост: „уназад“ (српски), „ago“ (енглески). За будућност: „за 3 дана“ (српски), „in 3 days“ (енглески). За садашњост: „сада“ (српски), „now“ (енглески). Контекст се подешава преко методе localizeString(fromTimeInterval:) или директно преко string(from: Date).

Подешавања јединица и стилова

RelativeDateTimeFormatter пружа неколико подешавања за контролу резултата: својство unitsStyle одређује стил форматирања (numeric, abbreviated, full, spellOut), а maximumUnitCount ограничава број приказаних јединица. На пример, са maximumUnitCount = 1 разлика од 1 сат 30 минута биће приказана као „пре 1 сат“ уместо „пре 1 сат 30 минута“.

Стилови форматирања

  • .numeric — пуна нумеричка вредност: „пре 3 дана“, „за 2 недеље“. Препоручује се за UI подразумевано
  • .abbreviated — скраћена форма: „пре 3 дана“, „за 2 нед.“. За компактан приказ у табелама и листама
  • .full — словна форма без цифара: „пре три дана“. За Accessibility и гласовне интерфејсе
  • .spellOut — словна форма са алтернативним писањем: „three days ago“. Користи се ретко, углавном за посебне намене

Ограничење јединица: подразумевано RelativeDateTimeFormatter приказује само једну (највећу) јединицу. Подешавање maximumUnitCount = 2 укључује следећу јединицу ради прецизнијег описа: „пре 1 сат 30 минута“. Међутим, ово може учинити стринг превише дугачким за кратке поруке (пуш, обавештења). За UI се препоручује да maximumUnitCount остане 1.

swift
import Foundation

let formatter = RelativeDateTimeFormatter()

// Подесите стилове
formatter.unitsStyle = .numeric
formatter.maximumUnitCount = 1

// Примери са различитим датумима
let fiveMinAgo = Date().addingTimeInterval(-300)
print("пре 5 мин: \(formatter.localizedString(for: fiveMinAgo, relativeTo: Date()))")

let twoDaysLater = Date().addingTimeInterval(172800)
print("за 2 дана: \(formatter.localizedString(for: twoDaysLater, relativeTo: Date()))")

// Скраћени стил
formatter.unitsStyle = .abbreviated
let oneWeekAgo = Date().addingTimeInterval(-604800)
print("Скраћено: \(formatter.localizedString(for: oneWeekAgo, relativeTo: Date()))")

// Пуни стил (речима)
formatter.unitsStyle = .full
let threeHours = Date().addingTimeInterval(10800)
print("Пуно: \(formatter.localizedString(for: threeHours, relativeTo: Date()))")

Избор стила за различите контексте: за вести користите .numeric са maximumUnitCount = 1 — то је стандард за Twitter, Instagram и Facebook. За Accessibility (VoiceOver) користите .full — бројеви речима се читају природније. За компактне елементе (угао обавештења, статусни ред) користите .abbreviated ради уштеде простора.

RelativeDateTimeFormatter у Swift-у: примери

Основна употреба RelativeDateTimeFormatter-а своди се на креирање инстанце, подешавање својстава и позивање једне од метода форматирања. Основне методе: localizedString(for:relativeTo:) — за пар датума, localizedString(fromTimeInterval:) — за разлику у секундама, и string(for:) — за Date са аутоматским контекстом (прошлост/будућност).

swift
import Foundation

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

// Примери UI за друштвене мреже
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)")
}

// Будући датуми
let reminderFormatter = RelativeDateTimeFormatter()
reminderFormatter.unitsStyle = .abbreviated
let inOneHour = Date().addingTimeInterval(3600)
let reminderText = reminderFormatter.localizedString(
    for: inOneHour,
    relativeTo: Date()
)
print("Подсетник: \(reminderText)")

Обрада сценарија „управо сада“ — RelativeDateTimeFormatter нема уграђену подршку за фразу „управо сада“ за веома мале интервале. За разлику мању од 5 секунди вратиће „пре 0 секунди“, што не изгледа лепо у UI-ју. Препоручује се да се позив formatter-а обави у условној логици: ако је разлика мања од утврђеног прага (на пример, 5 секунди) — прикажите „управо сада“ ручно, у супротном проследите датум форматтеру.

swift
import Foundation

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

    // Праг „управо сада“
    if interval < 5 {
        return "just now"
    }

    // Праг „данас“
    if interval < 60 {
        return "just now"
    }

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

    // Приказ без суфикса „уназад“
    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)))

Метод string(fromTimeInterval:) прима разлику у секундама и аутоматски одређује контекст (позитивна вредност — будућност, негативна — прошлост). Ово је згодно када је разлика већ позната (на пример, примљена од сервера као unix timestamp). У том случају није потребно креирати Date — разлика се прослеђује директно.

Локализација релативних датума

RelativeDateTimeFormatter аутоматски локализује резултат на основу Locale.current. Да бисте променили језик форматирања, подесите својство locale — за разлику од DateFormatter-а, за RelativeDateTimeFormatter locale није фиксна и може се мењати за сваки позив. Ово омогућава приказ релативних датума на језику различитом од језика интерфејса (на пример, садржај на оригиналном језику).

Сложеност локализације релативних датума лежи у граматичким карактеристикама различитих језика. Руски језик захтева различите облике бројева: „1 минут“, „2 минуте“, „5 минут“. Арапски — користи облик множине за бројеве од 3 до 10 и посебне облике за 11+. Кинески — нема промену облика уопште, што поједностављује задатак. RelativeDateTimeFormatter покрива све ове случајеве кроз ICU правила, без потребе за додатним кодом.

swift
import Foundation

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

let targetDate = Date().addingTimeInterval(-7200) // пре 2 сата

// Различите локале
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)")
}

// Проверите руске промене облика
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)) мин: \(formatter.localizedString(for: date, relativeTo: Date()))")
}

Важан детаљ: RelativeDateTimeFormatter игнорише TimeZone при рачунању разлике за подешавања .numeric — користи апсолутну разлику у секундама. Међутим, за стил .full (са бројевима речима) и посебне случајеве (јуче, данас) TimeZone се узима у обзир. Увек експлицитно подешавајте TimeZone ради доследности, посебно ако апликација ради са серверским датумима у UTC-у.

Типичне грешке при форматирању

Игнорисање TimeZone-а при рачунању релативних датума — честа грешка при раду са серверским датумима. Ако сервер шаље Date у UTC-у, а RelativeDateTimeFormatter користи TimeZone.current, разлика се може нетачно израчунати за датуме блиске тренутном тренутку. Препоручује се да увек подешавате formatter.timeZone = TimeZone(secondsFromGMT: 0) за серверске податке.

Нетачан избор јединице за кратке интервале — RelativeDateTimeFormatter заокружује разлику на највећу јединицу. За 25 сати резултат ће бити „пре 1 дан“, што може довести корисника у заблуду. Ако је потребна висока прецизност (на пример, за тајмере одбројавања), користите DateComponentsFormatter уместо RelativeDateTimeFormatter-а — он омогућава приказ више јединица истовремено.

Непроверавање негативног TimeInterval-а — ако се датум из будућности проследи као прошли (негативна вредност у string(fromTimeInterval:)), форматтер може вратити нетачан стринг. Увек проверавајте знак интервала пре прослеђивања форматтеру, посебно при раду са серверским подацима, где временска зона може искривити рачуницу.

Према Hacker News (2024), један од најдискутованијих проблема RelativeDateTimeFormatter-а је одсуство уграђене подршке за „јуче“ и „данас“ у енглеском језику. Уместо „јуче“ форматтер за разлику од 90000 секунди вратиће „пре 1 дан“. За руски језик такав проблем не постоји — „пре 1 дан“ звучи природно, али за енглески UI „yesterday“ је пожељније. Ова функционалност није подржана и захтева ручну проверу преко Calendar.isDateInToday/Yesterday.

Често постављана питања

Шта је RelativeDateTimeFormatter?

RelativeDateTimeFormatter — класа Foundation за приказ датума у релативном формату: „пре 5 минута“, „за 2 дана“. Доступна од iOS 13 и macOS 10.15.

Како RelativeDateTimeFormatter бира јединице?

По принципу највеће не-нулте јединице — секунде, минуте, сати, дани, недеље, месеци или године. На пример, за разлику од 3720 секунди (1 сат 2 минута) биће изабрана јединица „сат“, а не „минуте“.

Како променити језик резултата?

Подесите својство locale на жељену инстанцу Locale-а. Подразумевано се користи Locale.current. Пример: formatter.locale = Locale(identifier: "de_DE") за немачки језик.

Која је разлика између .numeric и .abbreviated?

.numeric — пуна форма („пре 3 дана“), .abbreviated — скраћена („пре 3 дана“). Избор зависи од контекста: numeric за главни UI, abbreviated за компактне елементе.

Како приказати „управо сада“ уместо „пре 0 секунди“?

Додајте ручну проверу за интервал мањи од 5–10 секунди. RelativeDateTimeFormatter не подржава „управо сада“ — за мале интервале враћа „пре 0 секунди“. Користите условну логику са прагом.

Закључци

  • RelativeDateTimeFormatter — згодна класа за приказ релативних датума у iOS 13+
  • Аутоматска локализација — исправна промена облика за све подржане језике преко ICU-а
  • Три стила — .numeric (стандард), .abbreviated (компактан), .full (речима)
  • Избор јединице — аутоматски по принципу највеће не-нулте вредности
  • Подешавање TimeZone-а — обавезно ради доследности при раду са серверским датумима
  • Праг „управо сада“ — није уграђен; захтева ручну проверу интервала
  • Нема подршке за „јуче“ — за енглески језик форматтер не користи форму yesterday

Развићемо мобилну апликацију под кључ

IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.

Разговарајте о пројекту

Прочитајте такође