DateFormatter — o clasă Foundation concepută pentru conversia bidirecțională între obiectele Date și reprezentarea lor sub formă de șir. Clasa ia în considerare setările regionale, fusul orar și calendarul utilizatorului, asigurând afișarea corectă a datelor în orice regiune a lumii. Conform Apple Developer Documentation (2025), DateFormatter acceptă patru stiluri predefinite de dată și oră, precum și formate complet personalizate printr-un șablon. Fără DateFormatter nu este posibilă afișarea corectă a datei utilizatorului într-o aplicație internaționalizată.
Principalele
DateFormatter — o clasă din framework-ul Foundation care implementează conversia bidirecțională între Date și șir. A apărut în OpenStep ca NSDateFormatter și de atunci rămâne principalul instrument de formatare a datelor pe toate platformele Apple. Clasa moștenește de la Formatter și oferă o API convenabilă pentru afișarea localizată a datelor.
Principiul de funcționare al DateFormatter se bazează pe șabloanele Unicode LDML — aceleași care sunt utilizate în ICU (International Components for Unicode). Șablonul este definit prin proprietatea dateFormat, unde simbolurile y, M, d, H, m, s corespund anului, lunii, zilei, orelor, minutelor și secundelor. Repetarea simbolului determină formatul: „y” — an cu două cifre, „yyyy” — cu patru cifre.
Crearea unui DateFormatter este o operație costisitoare, deoarece la inițializare se încarcă datele setărilor regionale și ale calendarului. Apple recomandă crearea formatorului o dată pentru fiecare tip de formatare și reutilizarea lui. În SwiftUI și UIKit, formatorii sunt adesea stocați în cache în proprietăți statice sau creați leneș la prima accesare.
DateFormatter este utilizat în multe componente de sistem iOS. UIDatePicker folosește intern DateFormatter pentru afișarea datelor în modul countDownTimer. TextField cu un formator la intrare poate valida automat data introdusă de utilizator. Core Data acceptă atribute de tip Date, dar afișarea lor ca text se face întotdeauna prin DateFormatter.
Thread Safety — DateFormatter nu este sigur pentru fire de execuție. Modificarea proprietăților formatorului din fire diferite duce la un comportament nedefinit. Pentru utilizare multi-thread, creați instanțe separate ale formatorului pentru fiecare fir sau utilizați sincronizarea prin NSLock sau serial queue.
dateStyle și timeStyle — cele mai simple moduri de a configura afișarea datei. Fiecare stil are patru variante: .short, .medium, .long, .full. Combinarea dateStyle și timeStyle permite configurarea independentă a formatului datei și orei, iar proprietatea .none dezactivează partea corespunzătoare.
Pentru setările regionale americane, stilul .short formatează data ca „7/21/26”, iar pentru cele românești — ca „21.07.2026”. Stilul .long pentru setările regionale românești afișează „21 iulie 2026”, iar .full — „marți, 21 iulie 2026” cu indicarea zilei săptămânii. Toate cele patru stiluri se adaptează automat la standardele regionale, inclusiv ordinea componentelor și separatoarele.
RelativeDateFormatter în iOS 15+ oferă o abordare alternativă prin RelativeDateFormatter și DateIntervalFormatter. RelativeDateFormatter afișează „azi”, „ieri”, „peste 3 zile” pentru contextul curent. DateIntervalFormatter arată intervale de date: “21–25 iulie 2026” — pentru rezervări și planificare.
| Stil | Exemplu (ro_RO) | Exemplu (en_US) |
|---|---|---|
| .short | 21.07.2026 | 7/21/26 |
| .medium | 21 iul. 2026 | Jul 21, 2026 |
| .long | 21 iulie 2026 | July 21, 2026 |
| .full | marți, 21 iulie 2026 | Tuesday, July 21, 2026 |
La combinarea stilurilor, DateFormatter alege automat separatorul: pentru .short.date + .short.time rezultatul poate fi „21.07.2026, 14:30”. Pentru .full.date + .full.time — „marți, 21 iulie 2026, 14:30:00 MSK”. Separatorul este controlat de setările regionale, nu de dezvoltator — aceasta garantează conformitatea cu așteptările regionale ale utilizatorului.
dateFormat permite definirea unui șablon arbitrar de formatare folosind simbolurile specificației Unicode LDML. Acest lucru oferă control total asupra afișării: puteți afișa doar anul și luna, ziua săptămânii fără număr sau ora fără secunde. Formatul personalizat este de neînlocuit pentru cerințele specifice de design.
Simbolurile de bază — yyyy (an: 2026), MM (lună: 07), dd (zi: 21), HH (ore: 14), mm (minute: 30), ss (secunde: 00). Pentru numele complet al lunii utilizați MMMM (iulie), pentru cel prescurtat — MMM (iul.). Ziua săptămânii — EEEE (marți), prescurtat — E (ma).
Când utilizați dateFormat, este important să setați locale formatorului. Dacă locale nu este setat, formatorul folosește setările regionale ale sistemului, ceea ce poate fi nedorit pentru un format fix în API. Apple recomandă setarea locale = Locale(identifier: “en_US_POSIX”) pentru un format inter-regional fix, în special la parsarea datelor din răspunsurile serverului.
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ru_RU")
formatter.dateFormat = "d MMMM yyyy"
let customString = formatter.string(from: Date())
// "21 July 2026"
// Parsarea unui șir personalizat
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let date = formatter.date(from: "2026-07-21 14:30:00")!
Eroarea în dateFormat — una dintre cauzele frecvente ale crashes-ului aplicației. Dacă formatul nu se potrivește cu șirul, metoda date(from:) returnează nil. Utilizați guard let sau ?? pentru extragerea sigură a valorii opționale. Pentru validarea formatului, testați pe toate limbile acceptate — unele simboluri LDML funcționează diferit în diferite setări regionale.
Locale determină cum sunt afișate numele lunilor, zilelor săptămânii și ce separatoare sunt utilizate. DateFormatter utilizează implicit Locale.current, dar în unele scenarii este necesară indicarea unor setări regionale specifice: pentru formatul fix în jurnal utilizați en_US_POSIX, pentru datele serverului — setări regionale identice cu cele ale serverului.
Proprietatea TimeZone determină fusul orar pentru afișare. Implicit se utilizează fusul orar al sistemului, dar pentru aplicațiile cu audiență internațională este adesea necesară afișarea datelor în fusul orar al utilizatorului sau în UTC. Modificarea timeZone afectează doar afișarea — valoarea Date rămâne neschimbată.
O caracteristică importantă: dacă DateFormatter este utilizat pentru parsarea unui șir, iar șirul conține o indicație a fusului orar (de exemplu, “2026-07-21T14:30:00Z” cu Z pentru UTC), proprietatea timeZone este ignorată — formatorul folosește fusul orar din șir. Dacă fusul orar lipsește din șir, se aplică timeZone formatorului.
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ru_RU")
formatter.timeZone = TimeZone(identifier: "Europe/Moscow")
formatter.dateStyle = .long
formatter.timeStyle = .short
let moscowTime = formatter.string(from: Date())
// "21 July 2026, 14:30"
// Parsare fără fus orar în șir
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd HH:mm"
let utcDate = formatter.date(from: "2026-07-21 10:30")!
AutoupdatingCurrentLocale — un tip special de setări regionale care se actualizează automat la modificarea setărilor de sistem ale utilizatorului. DateFormatter îl acceptă implicit. Dacă aplicația rulează în fundal și utilizatorul schimbă limba sistemului, formatorul creat înainte de modificare va continua să utilizeze setările regionale vechi — pentru actualizare este necesară crearea unei noi instanțe.
ISO8601DateFormatter — un formator specializat pentru lucrul cu datele în format ISO 8601. Acest format este standardul de facto pentru REST API, JSON și schimbul de date. ISO8601DateFormatter funcționează considerabil mai rapid decât DateFormatter, deoarece nu depinde de setările regionale și utilizează o gramatică fixă de parsare.
Opțiunile principale ale formatorului — .withInternetDateTime (2026-07-21T14:30:00Z), .withFractionalSeconds (adaugă milisecunde), .withTimeZone (include deplasarea fusului orar). Combinând opțiunile, puteți obține orice variantă ISO 8601: cu milisecunde, cu fus orar, cu dată fără oră.
JSONEncoder.DateEncodingStrategy vă permite să configurați global codificarea datelor pentru toate modelele Codable. Variante — .iso8601 (folosește ISO8601DateFormatter), .formatted(DateFormatter), .millisecondsSince1970, .secondsSince1970. Alegerea strategiei afectează întregul ciclu de viață al serializării și trebuie să fie uniformă pentru toate endpoint-urile API.
// ISO8601DateFormatter
let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let isoString = isoFormatter.string(from: Date())
// "2026-07-21T14:30:00.000Z"
// JSONEncoder cu ISO8601
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
// Alternativă: JSONEncoder cu formator personalizat
let customEncoder = JSONEncoder()
customEncoder.dateEncodingStrategy = .formatted(myFormatter)
DateFormatter vs ISO8601DateFormatter — alegeți ISO8601DateFormatter pentru serializarea și parsarea datelor în API, deoarece funcționează de 5-10 ori mai rapid decât DateFormatter și nu este supus erorilor de localizare. Lăsați DateFormatter pentru interfața utilizator, unde este necesară afișarea localizată cu numele lunilor și zilelor săptămânii în limba maternă a utilizatorului.
Să analizăm scenarii reale de utilizare a DateFormatter într-o aplicație iOS: afișarea într-o listă de știri, introducerea datei de naștere și exportul unui raport cu date în diferite fusuri orare.
RelativeDateFormatter este optim pentru fluxurile de știri. Acesta afișează „acum”, „acum 5 minute”, „ieri” pentru știri recente și comută la data completă pentru cele vechi. Pragul de comutare se configurează prin calendar: pentru știri utilizați un prag de 24 de ore, pentru mesagerie — o săptămână.
func formatRelativeDate(_ date: Date) -> String {
let relative = RelativeDateFormatter()
relative.unitsStyle = .full
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
let daysDiff = Calendar.current.dateComponents(
[.day], from: date, to: Date()
).day ?? 0
return daysDiff < 1
? relative.localizedString(for: date, relativeTo: Date())
: formatter.string(from: date)
}
Introducerea datei de naștere — un alt scenariu frecvent. DateFormatter se configurează cu un dateFormat specific „dd.MM.yyyy” și setări regionale „ro_RO”. La parsarea șirului introdus, este important să tratați eventualele erori: formatorul returnează nil pentru un șir incorect. După parsarea reușită, data se verifică pentru încadrarea în intervalul permis — nu mai devreme de 1900, nu mai târziu de astăzi.
Exportul raportului cu date necesită un format fix, independent de setările regionale ale utilizatorului. Utilizați dateFormat „yyyy-MM-dd HH:mm:ss” cu setări regionale en_US_POSIX și fus orar UTC. Această abordare garantează că fișierul se va deschide corect în orice țară, indiferent de setările regionale ale sistemului.
| Scenariu | Formator | Setare cheie |
|---|---|---|
| Flux de știri | RelativeDateFormatter | unitsStyle = .full |
| Introducere dată | DateFormatter | dateFormat + fallback |
| Serializare API | ISO8601DateFormatter | withInternetDateTime |
| Export raport | DateFormatter | en_US_POSIX + UTC |
Întrebări frecvente
Cea mai frecventă cauză — nepotrivirea dateFormat cu formatul șirului. De exemplu, formatul „dd.MM.yyyy” nu va parsa șirul „2026-07-21”. A doua cauză — nepotrivirea setărilor regionale: șirul „July 21, 2026” nu se va parsa cu setările regionale ro_RO. A treia — greșeli în simbolurile LDML: utilizați yyyy, nu YYYY (sens diferit).
Nu. DateFormatter este un obiect greu, inițializarea sa include încărcarea datelor setărilor regionale. Creați o singură instanță pe tip de formatare și reutilizați-o. Într-un mediu multi-thread, utilizați Thread-local storage sau un pool de formatori cu o serial queue pentru sincronizare.
DateFormatter afișează data absolută (21 iulie 2026), iar RelativeDateFormatter — data relativă (azi, ieri, peste 3 zile). RelativeDateFormatter a apărut în iOS 15+ și utilizează același șablon LDML, dar alege automat afișarea relativă.
Setați timeZone formatorului la UTC înainte de parsare. Dacă serverul returnează data în ora locală fără indicarea fusului, verificați specificația API — cel mai probabil se subînțelege UTC. Pentru ISO 8601 cu Z la sfârșit, timeZone nu este necesar — formatorul parsează deplasarea din șir.
Nu utilizați o singură instanță din fire diferite fără sincronizare. Creați o nouă instanță în fiecare fir sau utilizați Thread.current.threadDictionary pentru stocare. Alternativă — NSLock cu blocare pe durata string(from:) și date(from:).
Rezumat
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.
Citiți și