Calendar — este o clasă Foundation care definește sistemul calendaristic și oferă metode pentru calcule calendaristice: extragerea componentelor datei, calcularea diferenței între date, găsirea limitelor perioadelor și deplasarea datelor. Calendarul leagă timpul absolut (Date) de componente lizibile pentru om și ține cont de particularitățile regionale: începutul săptămânii, fusul orar și ora de vară. Conform Apple Developer Documentation (2025), Foundation suportă 17 sisteme calendaristice — de la gregorian la budist și japonez, ceea ce face din Calendar un instrument universal pentru aplicații internaționalizate.
Principalele puncte
Calendar — este o clasă Foundation care implementează calcule calendaristice bazate pe ICU (International Components for Unicode). Calendarul determină modul în care timpul absolut (Date) este mapat la componentele calendaristice: an, lună, zi, oră, minut, secundă. Fără Calendar, nu se poate determina ce an, lună și zi este astăzi — Date în sine nu conține aceste informații.
Calendarul ține cont de trei grupuri de parametri: sistemul calendaristic (gregorian, budist, japonez), fusul orar și locația. Calendar.current le combină pe toate trei din setările de sistem ale utilizatorului. Calendar.autoupdatingCurrent — o versiune specială care se actualizează automat la modificarea setărilor fără repornirea aplicației prin NotificationCenter.
Calendarul este un tip de valoare (value type) în Foundation. Calendar(identifier:) creează o nouă instanță cu parametri fixați. Calendarul poate fi copiat, comparat prin == și folosit ca cheie într-un dicționar. Acest lucru permite crearea de calendare cu setări specifice timeZone și locale pentru testare.
Calendar — versiunea Swift a NSCalendar din Objective-C, cu puntea as Calendar / as NSCalendar. În Swift modern, Calendar este folosit peste tot. NSCalendar rămâne pentru compatibilitatea inversă cu API-ul Objective-C. Calendar are un set complet de metode fără prefixul NS, cu argumente type-safe și opționalitate Swift.
Thread Safety — Calendar este thread-safe pentru citire. Instanța creată poate fi citită în siguranță din mai multe fire. Modificarea proprietăților (timeZone, locale) nu este thread-safe — pentru configurații diferite, creați instanțe separate de Calendar.
Foundation suportă 17 sisteme calendaristice prin enumerarea Calendar.Identifier. Fiecare sistem are propriile reguli pentru ani bisecți, numărul de luni și începutul erei. Alegerea calendarului afectează toate calculele: dateComponents, dateInterval, nextDate.
Sistemele calendaristice principale:
Calendar(identifier: .gregorian) — cel mai frecvent utilizat. Respectă standardul internațional ISO 8601 și este calendarul implicit în majoritatea țărilor. Pentru aplicații cu audiență internațională, utilizați Calendar.current — se potrivește automat cu calendarul de sistem al utilizatorului.
| Identificator | Tip | Regiunea de utilizare |
|---|---|---|
| .gregorian | Solar | Internațional |
| .buddhist | Solar | Thailanda, Cambodgia |
| .japanese | Solar | Japonia |
| .hebrew | Lunar-solar | Israel |
| .islamic | Lunar | Țările islamice |
| .chinese | Lunar-solar | China |
DateComponents și Calendar — o pereche inseparabilă. Calendar.dateComponents(_:from:) extrage componentele din Date ținând cont de fusul orar al calendarului. Calendar.date(from:) construiește Date din DateComponents, completând câmpurile lipsă cu valori implicite: zi = 1, oră = 0, minut = 0, secundă = 0.
Metoda Calendar.component extrage o singură componentă, ceea ce este convenabil pentru verificări rapide. Calendar.dateComponents extrage un set de componente într-un singur apel — este mai eficient, deoarece Calendar efectuează calculele calendaristice o singură dată, nu pentru fiecare componentă separat. Pentru o listă de 3+ componente, utilizați întotdeauna dateComponents.
Calendar.compare compară două Date cu o precizie specificată. Parametrul toGranularity determină până la ce componentă se efectuează compararea: .year compară doar anul, .month — anul și luna, .day — anul, luna, ziua. Este convenabil pentru a verifica dacă două date aparțin aceleiași zile, fără a lua în considerare timpul.
let calendar = Calendar.current
let now = Date()
// Extragerea unei singure componente
let year = calendar.component(.year, from: now)
// Extragerea unui set de componente
let comps = calendar.dateComponents(
[.year, .month, .day], from: now
)
// Comparare cu granularitate pe zi
let isSameDay = calendar.compare(date1, to: date2,
toGranularity: .day) == .orderedSame
// Verifică dacă data este astăzi
let isToday = calendar.isDateInToday(someDate)
Calendar.isDateInToday, isDateInTomorrow, isDateInYesterday — metode pentru verificări relative. Calendar.isDate(_:inSameDayAs:) verifică dacă două date cad în aceeași zi calendaristică, ținând cont de fusul orar al calendarului. Aceste metode folosesc Calendar.compare intern și sunt optimizate pentru apeluri frecvente.
Calendar.dateInterval — una dintre cele mai utile metode pentru analitică și UI. Returnează DateInterval pentru componenta specificată: începutul și sfârșitul zilei, săptămânii, lunii, anului. DateInterval conține start (Date) și end (Date) — limitele perioadei. De exemplu, dateInterval(of: .weekOfYear, for: Date()) returnează începutul lunii și sfârșitul duminicii săptămânii curente.
Calendar.date cu byAdding — metoda pentru deplasarea datei. Calendar.date(byAdding: .day, value: 7, to: Date()) returnează data peste o săptămână. Calendar.date(byAdding: DateComponents) — o versiune mai flexibilă, permițând deplasarea mai multor componente simultan: +1 lună +3 zile. Calendar ține cont automat de lungimea diferită a lunilor și de anii bisecți.
Calendar.nextDate caută următoarea dată care se potrivește cu DateComponents specificate. Parametrul matchingPolicy determină comportamentul în caz de nepotrivire: .nextTime — următoarea potrivire după timp, .nextTimePreservingSmallerComponents — păstrează minutele și secundele din data originală, .strict — necesită o potrivire exactă.
let calendar = Calendar.current
let today = Date()
// Începutul și sfârșitul săptămânii
let weekInterval = calendar.dateInterval(
of: .weekOfYear, for: today
)!
// Deplasare cu 1 lună
let nextMonth = calendar.date(
byAdding: .month, value: 1, to: today
)!
// Deplasare prin DateComponents
var delta = DateComponents()
delta.month = 1
delta.day = 3
let shifted = calendar.date(byAdding: delta, to: today)!
// Următoarea vineri 13
let friday13Components = DateComponents(
weekday: 6, day: 13
)
let nextFriday13 = calendar.nextDate(
after: today, matching: friday13Components,
matchingPolicy: .nextTime
)
EnumerateDates — o metodă puternică pentru iterarea datelor după un model. Calendar.enumerateDates(startingAfter:matching:matchingPolicy:using:) apelează un bloc pentru fiecare potrivire, până când blocul returnează stop = true. Este folosită pentru generarea evenimentelor recurente în calendare și programări. Metoda este mai eficientă decât o buclă manuală cu nextDate, deoarece este optimizată de ICU.
TimeZone — o parte inseparabilă a Calendar. Fusul orar determină cărui timp calendaristic îi corespunde Date-ul absolut. Același Date în UTC și la Moscova dă componente diferite: Date() în UTC poate arăta 10:00, iar în MSK — 13:00. Calendar.timeZone este implicit egal cu TimeZone.current.
Locale influențează prima zi a săptămânii, numărul minim de zile în prima săptămână a anului (minDaysInFirstWeek) și numele lunilor/zilelor săptămânii (la conversia prin DateFormatter). Calendar.locale este implicit egal cu Locale.current. În locala românească, săptămâna începe de luni, în cea americană — de duminică.
Calendar.availableIdentifiers returnează lista tuturor identificatorilor calendaristici suportați. static property Calendar.availableCalendarIdentifiers — un tablou de șiruri cu aceiași identificatori. Folosit pentru construirea UI-ului de selectare a calendarului și pentru verificarea disponibilității unui sistem calendaristic specific pe dispozitiv.
// Calendar cu fus orar specific
var utcCalendar = Calendar(identifier: .gregorian)
utcCalendar.timeZone = TimeZone(identifier: "UTC")!
// Calendar cu locala românească
var russianCalendar = Calendar(identifier: .gregorian)
russianCalendar.locale = Locale(identifier: "ru_RU")
// Prima zi a săptămânii depinde de locală
let firstWeekday = russianCalendar.firstWeekday
// 2 = luni (în ro_RO)
// Lista calendarelor disponibile
for identifier in Calendar.availableIdentifiers {
print(identifier)
}
firstWeekday — proprietatea Calendar care determină ce zi a săptămânii este considerată prima. În locala românească Sunday = 2 (luni prima). În cea americană Sunday = 1. Aceasta afectează funcționarea weekOfMonth și weekOfYear: aceeași dată poate fi atribuită unor numere diferite de săptămână în locații diferite. Pentru aplicații cu date, utilizați Calendar.current sau setați explicit firstWeekday.
Să examinăm scenarii practice care demonstrează capacitățile Calendar. Fiecare exemplu rezolvă o sarcină specifică de dezvoltare iOS și arată modul corect de utilizare a calculelor calendaristice.
Calendar.dateInterval(of: .month, for:) returnează limitele lunii curente. Verificarea apartenenței Date la acest interval — cel mai rapid mod de a determina dacă data aparține lunii curente. Mod alternativ — Calendar.compare cu granularity .month: dacă rezultatul este .orderedSame, atunci luna se potrivește.
func isInCurrentMonth(_ date: Date) -> Bool {
let calendar = Calendar.current
let monthInterval = calendar.dateInterval(
of: .month, for: Date()
)!
return monthInterval.contains(date)
}
// Numărul de zile într-o lună
func daysInMonth(for date: Date) -> Int {
let calendar = Calendar.current
return calendar.range(
of: .day, in: .month, for: date
)?.count ?? 0
}
// Adăugarea lunilor cu finalizare corectă
func addMonths(_ months: Int, to date: Date) -> Date {
let calendar = Calendar.current
return calendar.date(
byAdding: .month, value: months, to: date
)!
}
Calendar.range(of:in:for:) returnează intervalul valorilor permise pentru componenta specificată în contextul altei componente. De exemplu, range(of: .day, in: .month, for: date) returnează 1..<32 pentru lunile cu 31 de zile sau 1..<29 pentru februarie într-un an non-bisect. Aceasta este modalitatea corectă de a afla numărul de zile dintr-o lună, nu utilizarea valorilor hardcodate.
Adăugarea lunilor prin Calendar.date(byAdding:value:to:) gestionează corect datele limită. Dacă la 31 ianuarie se adaugă 1 lună, Calendar returnează 28 februarie (sau 29 într-un an bisect), nu 3 martie, cum s-ar întâmpla la adăugarea simplă a 30 de zile prin TimeInterval. Acesta este un alt motiv pentru a nu folosi TimeInterval pentru calcule calendaristice.
| Metoda Calendar | Destinație | Exemplu |
|---|---|---|
| dateInterval | Limitele perioadei | Începutul și sfârșitul lunii |
| range(of:in:for:) | Intervalul componentei | Zile în luna curentă |
| date(byAdding:) | Deplasarea datei | +1 lună de astăzi |
| isDateInToday | Verificare dacă este astăzi | Data este de astăzi |
| compare(toGranularity:) | Comparare cu precizie | Aceeași zi fără timp |
Întrebări frecvente
Calendar.current returnează calendarul din setările de sistem ale utilizatorului — poate să nu fie gregorian (de exemplu, budist în Thailanda). Calendar(identifier: .gregorian) creează întotdeauna un calendar gregorian indiferent de setări. Pentru afișarea datelor, utilizați Calendar.current, pentru logica de afaceri — identificatorul ales explicit.
Aceasta se datorează lungimii diferite a lunilor. Dacă data curentă este 31 ianuarie, adăugarea unei luni dă 28 februarie, deoarece februarie nu are 31 de zile. Calendar completează automat data până la ultima zi permisă a lunii. Pentru control precis, utilizați DateComponents cu day: 1 pentru a trece la prima zi a lunii.
DateFormatter utilizează Calendar.current — calendarul de sistem al utilizatorului. Dacă aplicația trebuie să afișeze întotdeauna datele în calendarul gregorian indiferent de setări, setați formatter.calendar = Calendar(identifier: .gregorian). Aceasta garantează o afișare uniformă pentru toți utilizatorii.
Calendar.range(of: .day, in: .year, for: date) returnează 365 sau 366 de zile. Mai simplu: Calendar.date(from: DateComponents(year: an, month: 2, day: 29)) != nil — dacă 29 februarie există, anul este bisect. Calendar ține cont singur de regulile pentru sistemul calendaristic specific.
Da, proprietatea firstWeekday este disponibilă pentru scriere. Modificarea afectează weekOfMonth, weekOfYear și toate calculele legate de numerele săptămânilor. La setarea locale = Locale(identifier: "ro_RO"), firstWeekday devine automat 2 (luni). Setarea manuală suprascrie valoarea din locale.
Concluzii
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