Calendar — to klasa Foundation, która definiuje system kalendarzowy i dostarcza metod do obliczeń kalendarzowych: wyodrębnianie składników daty, obliczanie różnicy między datami, znajdowanie granic okresów i przesuwanie dat. Kalendarz łączy czas absolutny (Date) z czytelnymi dla człowieka składnikami i uwzględnia regionalne różnice: początek tygodnia, strefę czasową i czas letni. Według Apple Developer Documentation (2025), Foundation obsługuje 17 systemów kalendarzowych — od gregoriańskiego po buddyjski i japoński, co czyni Calendar uniwersalnym narzędziem dla zinternacjonalizowanych aplikacji.
Najważniejsze
Calendar — to klasa Foundation implementująca obliczenia kalendarzowe w oparciu o ICU (International Components for Unicode). Kalendarz określa, jak czas absolutny (Date) jest mapowany na składniki kalendarzowe: rok, miesiąc, dzień, godzinę, minutę, sekundę. Bez Calendar nie można określić, jaki jest dzisiaj rok, miesiąc i dzień — Date sam w sobie nie zawiera tych informacji.
Kalendarz uwzględnia trzy grupy parametrów: system kalendarzowy (gregoriański, buddyjski, japoński), strefę czasową i lokalizację. Calendar.current łączy wszystkie trzy z ustawień systemowych użytkownika. Calendar.autoupdatingCurrent — specjalna wersja automatycznie aktualizowana przy zmianie ustawień bez restartu aplikacji przez NotificationCenter.
Kalendarz jest typem wartościowym (value type) w Foundation. Calendar(identifier:) tworzy nową instancję ze stałymi parametrami. Calendar można kopiować, porównywać przez == i używać jako klucza w słowniku. Pozwala to tworzyć kalendarze z konkretnymi ustawieniami timeZone i locale do testowania.
Calendar — to Swiftowa wersja Objective-C NSCalendar, z mostem as Calendar / as NSCalendar. We współczesnym Swifcie wszędzie używa się Calendar. NSCalendar pozostaje dla wstecznej kompatybilności z Objective-C API. Calendar ma pełny zestaw metod bez prefiksu NS, z type-safe argumentami i Swiftową opcjonalnością.
Thread Safety — Calendar jest bezpieczny wątkowo do odczytu. Utworzoną instancję można bezpiecznie czytać z wielu wątków. Modyfikacja właściwości (timeZone, locale) nie jest bezpieczna wątkowo — dla różnych konfiguracji twórz różne instancje Calendar.
Foundation obsługuje 17 systemów kalendarzowych przez wyliczenie Calendar.Identifier. Każdy system ma własne zasady lat przestępnych, liczbę miesięcy i początek ery. Wybór kalendarza wpływa na wszystkie obliczenia: dateComponents, dateInterval, nextDate.
Podstawowe systemy kalendarzowe:
Calendar(identifier: .gregorian) — najczęściej używany. Odpowiada międzynarodowemu standardowi ISO 8601 i jest domyślnym kalendarzem w większości krajów. Dla aplikacji z międzynarodową publicznością używaj Calendar.current — automatycznie odpowiada systemowemu kalendarzowi użytkownika.
| Identyfikator | Typ | Region użycia |
|---|---|---|
| .gregorian | Słoneczny | Międzynarodowy |
| .buddhist | Słoneczny | Tajlandia, Kambodża |
| .japanese | Słoneczny | Japonia |
| .hebrew | Księżycowo-słoneczny | Izrael |
| .islamic | Księżycowy | Kraje islamskie |
| .chinese | Księżycowo-słoneczny | Chiny |
DateComponents i Calendar — nierozłączna para. Calendar.dateComponents(_:from:) wyodrębnia składniki z Date z uwzględnieniem strefy czasowej kalendarza. Calendar.date(from:) składa Date z DateComponents, wypełniając brakujące pola wartościami domyślnymi: dzień = 1, godzina = 0, minuta = 0, sekunda = 0.
Metoda Calendar.component wyodrębnia jeden składnik, co jest wygodne do szybkiego sprawdzenia. Calendar.dateComponents wyodrębnia zestaw składników w jednym wywołaniu — jest to bardziej wydajne, ponieważ Calendar wykonuje obliczenia kalendarzowe raz, a nie dla każdego składnika osobno. Dla listy 3+ składników zawsze używaj dateComponents.
Calendar.compare porównuje dwie Date z zadaną dokładnością. Parametr toGranularity określa, do którego składnika wykonać porównanie: .year porównuje tylko rok, .month — rok i miesiąc, .day — rok, miesiąc, dzień. Jest to wygodne do sprawdzenia, czy dwie daty należą do tego samego dnia, bez uwzględniania czasu.
let calendar = Calendar.current
let now = Date()
// Wyodrębnianie pojedynczego komponentu
let year = calendar.component(.year, from: now)
// Wyodrębnianie zestawu komponentów
let comps = calendar.dateComponents(
[.year, .month, .day], from: now
)
// Porównanie z dokładnością do dnia
let isSameDay = calendar.compare(date1, to: date2,
toGranularity: .day) == .orderedSame
// Sprawdź, czy data jest dzisiaj
let isToday = calendar.isDateInToday(someDate)
Calendar.isDateInToday, isDateInTomorrow, isDateInYesterday — metody do sprawdzeń względnych. Calendar.isDate(_:inSameDayAs:) sprawdza, czy dwie daty przypadają na ten sam dzień kalendarzowy z uwzględnieniem strefy czasowej kalendarza. Te metody używają Calendar.compare wewnętrznie i są zoptymalizowane do częstego wywoływania.
Calendar.dateInterval — jedna z najbardziej przydatnych metod do analityki i UI. Zwraca DateInterval dla określonego składnika: początek i koniec dnia, tygodnia, miesiąca, roku. DateInterval zawiera start (Date) i end (Date) — granice okresu. Na przykład dateInterval(of: .weekOfYear, for: Date()) zwraca początek poniedziałku i koniec niedzieli bieżącego tygodnia.
Calendar.date z byAdding — metoda do przesuwania daty. Calendar.date(byAdding: .day, value: 7, to: Date()) zwraca datę za tydzień. Calendar.date(byAdding: DateComponents) — bardziej elastyczna wersja, pozwalająca przesunąć kilka składników naraz: +1 miesiąc +3 dni. Calendar automatycznie uwzględnia różną długość miesięcy i lata przestępne.
Calendar.nextDate szuka następnej daty pasującej do zadanych DateComponents. Parametr matchingPolicy określa zachowanie przy braku dopasowania: .nextTime — następne dopasowanie czasu, .nextTimePreservingSmallerComponents — zachowuje minuty i sekundy z oryginalnej daty, .strict — wymaga dokładnego dopasowania.
let calendar = Calendar.current
let today = Date()
// Początek i koniec tygodnia
let weekInterval = calendar.dateInterval(
of: .weekOfYear, for: today
)!
// Przesunięcie o 1 miesiąc
let nextMonth = calendar.date(
byAdding: .month, value: 1, to: today
)!
// Przesunięcie przez DateComponents
var delta = DateComponents()
delta.month = 1
delta.day = 3
let shifted = calendar.date(byAdding: delta, to: today)!
// Następny piątek 13-tego
let friday13Components = DateComponents(
weekday: 6, day: 13
)
let nextFriday13 = calendar.nextDate(
after: today, matching: friday13Components,
matchingPolicy: .nextTime
)
EnumerateDates — potężna metoda do iteracji dat według wzorca. Calendar.enumerateDates(startingAfter:matching:matchingPolicy:using:) wywołuje blok dla każdego dopasowania, dopóki blok nie zwróci stop = true. Używana do generowania powtarzających się wydarzeń w kalendarzach i harmonogramach. Metoda jest bardziej wydajna niż ręczna pętla z nextDate, ponieważ jest zoptymalizowana przez ICU.
TimeZone — nieodłączna część Calendar. Strefa czasowa określa, któremu czasowi kalendarzowemu odpowiada absolutny Date. Ten sam Date w UTC i w Moskwie daje różne składniki: Date() w UTC może pokazywać 10:00, a w MSK — 13:00. Calendar.timeZone domyślnie równa się TimeZone.current.
Locale wpływa na pierwszy dzień tygodnia, minimalną liczbę dni w pierwszym tygodniu roku (minDaysInFirstWeek) i nazwy miesięcy/dni tygodnia (przy konwersji przez DateFormatter). Calendar.locale domyślnie równa się Locale.current. W polskiej lokalizacji tydzień zaczyna się od poniedziałku, w amerykańskiej — od niedzieli.
Calendar.availableIdentifiers zwraca listę wszystkich obsługiwanych identyfikatorów kalendarzowych. static property Calendar.availableCalendarIdentifiers — tablica ciągów z tymi samymi identyfikatorami. Używane do budowania UI wyboru kalendarza i do sprawdzania dostępności konkretnego systemu kalendarzowego na urządzeniu.
// Kalendarz z konkretną strefą czasową
var utcCalendar = Calendar(identifier: .gregorian)
utcCalendar.timeZone = TimeZone(identifier: "UTC")!
// Kalendarz z rosyjską lokalizacją
var russianCalendar = Calendar(identifier: .gregorian)
russianCalendar.locale = Locale(identifier: "ru_RU")
// Pierwszy dzień tygodnia zależy od lokalizacji
let firstWeekday = russianCalendar.firstWeekday
// 2 = poniedziałek (w pl_PL)
// Lista dostępnych kalendarzy
for identifier in Calendar.availableIdentifiers {
print(identifier)
}
firstWeekday — właściwość Calendar określająca, który dzień tygodnia jest uważany za pierwszy. W polskiej lokalizacji Sunday = 2 (poniedziałek pierwszy). W amerykańskiej Sunday = 1. Wpływa to na działanie weekOfMonth i weekOfYear: ta sama data może być przypisana do różnych numerów tygodnia w różnych lokalizacjach. Dla aplikacji z datami używaj Calendar.current lub jawnie ustawiaj firstWeekday.
Rozważmy praktyczne scenariusze demonstrujące możliwości Calendar. Każdy przykład rozwiązuje konkretne zadanie iOS developmentu i pokazuje prawidłowy sposób użycia obliczeń kalendarzowych.
Calendar.dateInterval(of: .month, for:) zwraca granice bieżącego miesiąca. Sprawdzenie Date pod kątem przynależności do tego przedziału — najszybszy sposób określenia, czy data należy do bieżącego miesiąca. Alternatywny sposób — Calendar.compare z granularity .month: jeśli wynik .orderedSame, to miesiąc się zgadza.
func isInCurrentMonth(_ date: Date) -> Bool {
let calendar = Calendar.current
let monthInterval = calendar.dateInterval(
of: .month, for: Date()
)!
return monthInterval.contains(date)
}
// Liczba dni w miesiącu
func daysInMonth(for date: Date) -> Int {
let calendar = Calendar.current
return calendar.range(
of: .day, in: .month, for: date
)?.count ?? 0
}
// Dodawanie miesięcy z poprawnym zakończeniem
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:) zwraca zakres dozwolonych wartości dla określonego składnika w kontekście innego składnika. Na przykład range(of: .day, in: .month, for: date) zwraca 1..<32 dla miesięcy z 31 dniami lub 1..<29 dla lutego roku nieprzestępnego. To prawidłowy sposób na poznanie liczby dni w miesiącu, a nie używanie hardkodowanych wartości.
Dodawanie miesięcy przez Calendar.date(byAdding:value:to:) poprawnie obsługuje daty graniczne. Jeśli 31 stycznia dodać 1 miesiąc, Calendar zwróci 28 lutego (lub 29 w roku przestępnym), a nie 3 marca, jak by się stało przy prostym dodaniu 30 dni przez TimeInterval. To kolejny powód, by nie używać TimeInterval do obliczeń kalendarzowych.
| Metoda Calendar | Przeznaczenie | Przykład |
|---|---|---|
| dateInterval | Granice okresu | Początek i koniec miesiąca |
| range(of:in:for:) | Zakres składnika | Dni w bieżącym miesiącu |
| date(byAdding:) | Przesunięcie daty | +1 miesiąc od dzisiaj |
| isDateInToday | Sprawdzenie czy dzisiaj | Czy data jest dzisiejsza |
| compare(toGranularity:) | Porównanie z dokładnością | Czy ten sam dzień bez czasu |
Często zadawane pytania
Calendar.current zwraca kalendarz z ustawień systemowych użytkownika — może nie być gregoriański (na przykład buddyjski w Tajlandii). Calendar(identifier: .gregorian) zawsze tworzy kalendarz gregoriański niezależnie od ustawień. Do wyświetlania dat używaj Calendar.current, do logiki biznesowej — jawnie wybranego identyfikatora.
To wynika z różnej długości miesięcy. Jeśli bieżąca data to 31 stycznia, dodanie 1 miesiąca daje 28 lutego, ponieważ w lutym nie ma 31 dni. Calendar automatycznie kończy datę na ostatnim dozwolonym dniu miesiąca. Do precyzyjnej kontroli używaj DateComponents z day: 1 do przejścia na pierwszy dzień miesiąca.
DateFormatter używa Calendar.current — systemowego kalendarza użytkownika. Jeśli aplikacja zawsze ma pokazywać daty w kalendarzu gregoriańskim niezależnie od ustawień, ustaw formatter.calendar = Calendar(identifier: .gregorian). Gwarantuje to jednolite wyświetlanie dla wszystkich użytkowników.
Calendar.range(of: .day, in: .year, for: date) zwraca 365 lub 366 dni. Prościej: Calendar.date(from: DateComponents(year: rok, month: 2, day: 29)) != nil — jeśli 29 lutego istnieje, rok jest przestępny. Calendar sam uwzględnia zasady dla konkretnego systemu kalendarzowego.
Tak, właściwość firstWeekday jest dostępna do zapisu. Zmiana wpływa na weekOfMonth, weekOfYear i wszystkie obliczenia związane z numerami tygodni. Przy ustawieniu locale = Locale(identifier: "pl_PL") firstWeekday automatycznie staje się 2 (poniedziałek). Ręczne ustawienie nadpisuje wartość z locale.
Podsumowanie
Opracujemy aplikację mobilną pod klucz
IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.
Przeczytaj również