RelativeDateTimeFormatter је класа Foundation у iOS и macOS која претвара апсолутне датуме у читљиве релативне формулације: „пре 5 минута“, „јуче“, „за 3 дана“. Према Епл Developer Documentation, 2024, RelativeDateTimeFormatter аутоматски бира одговарајућу јединицу (секунде, минуте, сате, дане) и локализује резултат на језику тренутне локале уређаја. За разлику од ручног рачунања разлике између датума преко Calendar-а, ова класа узима у обзир лингвистичке карактеристике сваког језика: за неке језике бројеви се мењају по падежима, за друге — користи се посебан облик за реч „јуче“. Класа је доступна од iOS 13 и macOS 10.15.
Главно
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-а се састоји из три корака: рачунање разлике између прослеђеног датума и тренутног тренутка, избор одговарајуће јединице (највеће која не даје нулу) и форматирање у складу са локалом. На пример, за разлику од 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 минута“.
Ограничење јединица: подразумевано RelativeDateTimeFormatter приказује само једну (највећу) јединицу. Подешавање maximumUnitCount = 2 укључује следећу јединицу ради прецизнијег описа: „пре 1 сат 30 минута“. Међутим, ово може учинити стринг превише дугачким за кратке поруке (пуш, обавештења). За UI се препоручује да maximumUnitCount остане 1.
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-а своди се на креирање инстанце, подешавање својстава и позивање једне од метода форматирања. Основне методе: localizedString(for:relativeTo:) — за пар датума, localizedString(fromTimeInterval:) — за разлику у секундама, и string(for:) — за Date са аутоматским контекстом (прошлост/будућност).
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 секунди) — прикажите „управо сада“ ручно, у супротном проследите датум форматтеру.
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 правила, без потребе за додатним кодом.
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 — класа Foundation за приказ датума у релативном формату: „пре 5 минута“, „за 2 дана“. Доступна од iOS 13 и macOS 10.15.
По принципу највеће не-нулте јединице — секунде, минуте, сати, дани, недеље, месеци или године. На пример, за разлику од 3720 секунди (1 сат 2 минута) биће изабрана јединица „сат“, а не „минуте“.
Подесите својство locale на жељену инстанцу Locale-а. Подразумевано се користи Locale.current. Пример: formatter.locale = Locale(identifier: "de_DE") за немачки језик.
.numeric — пуна форма („пре 3 дана“), .abbreviated — скраћена („пре 3 дана“). Избор зависи од контекста: numeric за главни UI, abbreviated за компактне елементе.
Додајте ручну проверу за интервал мањи од 5–10 секунди. RelativeDateTimeFormatter не подржава „управо сада“ — за мале интервале враћа „пре 0 секунди“. Користите условну логику са прагом.
Закључци
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође