Calendar — je třída Foundation, která definuje kalendářní systém a poskytuje metody pro kalendářní výpočty: extrahování součástí data, výpočet rozdílu mezi daty, hledání hranic období a posouvání dat. Kalendář propojuje absolutní čas (Date) s lidsky čitelnými součástmi a zohledňuje regionální specifika: začátek týdne, časové pásmo a letní čas. Podle Apple Developer Documentation (2025) Foundation podporuje 17 kalendářních systémů — od gregoriánského po buddhistický a japonský, což činí Calendar univerzálním nástrojem pro internacionalizované aplikace.
Hlavní body
Calendar — je třída Foundation implementující kalendářní výpočty založené na ICU (International Components for Unicode). Kalendář určuje, jak je absolutní čas (Date) mapován na kalendářní součásti: rok, měsíc, den, hodina, minuta, sekunda. Bez Calendar nelze zjistit, jaký je dnes rok, měsíc a den — Date sám o sobě tyto informace neobsahuje.
Kalendář zohledňuje tři skupiny parametrů: kalendářní systém (gregoriánský, buddhistický, japonský), časové pásmo a locale. Calendar.current kombinuje všechny tři ze systémových nastavení uživatele. Calendar.autoupdatingCurrent — speciální verze, která se automaticky aktualizuje při změně nastavení bez restartu aplikace prostřednictvím NotificationCenter.
Kalendář je hodnotový typ (value type) ve Foundation. Calendar(identifier:) vytváří novou instanci s pevnými parametry. Kalendář lze kopírovat, porovnávat pomocí == a používat jako klíč ve slovníku. To umožňuje vytvářet kalendáře s konkrétním nastavením timeZone a locale pro testování.
Calendar — Swift verze Objective-C NSCalendar, s můstkem as Calendar / as NSCalendar. V moderním Swiftu se všude používá Calendar. NSCalendar zůstává pro zpětnou kompatibilitu s Objective-C API. Calendar má plnou sadu metod bez předpony NS, s type-safe argumenty a Swift opcionalitou.
Thread Safety — Calendar je thread-safe pro čtení. Vytvořenou instanci lze bezpečně číst z více vláken. Úprava vlastností (timeZone, locale) není thread-safe — pro různé konfigurace vytvářejte samostatné instance Calendar.
Foundation podporuje 17 kalendářních systémů prostřednictvím výčtu Calendar.Identifier. Každý systém má svá vlastní pravidla pro přestupné roky, počet měsíců a začátek éry. Volba kalendáře ovlivňuje všechny výpočty: dateComponents, dateInterval, nextDate.
Hlavní kalendářní systémy:
Calendar(identifier: .gregorian) — nejčastěji používaný. Odpovídá mezinárodnímu standardu ISO 8601 a je výchozím kalendářem ve většině zemí. Pro aplikace s mezinárodním publikem používejte Calendar.current — automaticky odpovídá systémovému kalendáři uživatele.
| Identifikátor | Typ | Oblast použití |
|---|---|---|
| .gregorian | Sluneční | Mezinárodní |
| .buddhist | Sluneční | Thajsko, Kambodža |
| .japanese | Sluneční | Japonsko |
| .hebrew | Lunárně-sluneční | Izrael |
| .islamic | Lunární | Islámské země |
| .chinese | Lunárně-sluneční | Čína |
DateComponents a Calendar — nerozlučná dvojice. Calendar.dateComponents(_:from:) extrahuje součásti z Date s ohledem na časové pásmo kalendáře. Calendar.date(from:) sestavuje Date z DateComponents a vyplňuje chybějící pole výchozími hodnotami: den = 1, hodina = 0, minuta = 0, sekunda = 0.
Metoda Calendar.component extrahuje jednu součást, což je vhodné pro rychlou kontrolu. Calendar.dateComponents extrahuje sadu součástí v jednom volání — je to efektivnější, protože Calendar provádí kalendářní výpočty jednou, ne pro každou součást zvlášť. Pro seznam 3+ součástí vždy používejte dateComponents.
Calendar.compare porovnává dvě Date se zadanou přesností. Parametr toGranularity určuje, do které součásti se porovnání provádí: .year porovnává pouze rok, .month — rok a měsíc, .day — rok, měsíc, den. To je vhodné pro kontrolu, zda dvě data patří ke stejnému dni, bez ohledu na čas.
let calendar = Calendar.current
let now = Date()
// Extrahování jedné součásti
let year = calendar.component(.year, from: now)
// Extrahování sady součástí
let comps = calendar.dateComponents(
[.year, .month, .day], from: now
)
// Porovnání s přesností na den
let isSameDay = calendar.compare(date1, to: date2,
toGranularity: .day) == .orderedSame
// Zkontrolujte, zda je datum dnes
let isToday = calendar.isDateInToday(someDate)
Calendar.isDateInToday, isDateInTomorrow, isDateInYesterday — metody pro relativní kontroly. Calendar.isDate(_:inSameDayAs:) kontroluje, zda dvě data připadají na stejný kalendářní den s ohledem na časové pásmo kalendáře. Tyto metody interně používají Calendar.compare a jsou optimalizovány pro časté volání.
Calendar.dateInterval — jedna z nejužitečnějších metod pro analytiku a UI. Vrací DateInterval pro zadanou součást: začátek a konec dne, týdne, měsíce, roku. DateInterval obsahuje start (Date) a end (Date) — hranice období. Například dateInterval(of: .weekOfYear, for: Date()) vrací začátek pondělí a konec neděle aktuálního týdne.
Calendar.date s byAdding — metoda pro posouvání data. Calendar.date(byAdding: .day, value: 7, to: Date()) vrací datum za týden. Calendar.date(byAdding: DateComponents) — flexibilnější verze umožňující posunout několik součástí najednou: +1 měsíc +3 dny. Calendar automaticky zohledňuje různou délku měsíců a přestupné roky.
Calendar.nextDate hledá další datum odpovídající zadaným DateComponents. Parametr matchingPolicy určuje chování při neshodě: .nextTime — další shoda v čase, .nextTimePreservingSmallerComponents — zachovává minuty a sekundy z původního data, .strict — vyžaduje přesnou shodu.
let calendar = Calendar.current
let today = Date()
// Začátek a konec týdne
let weekInterval = calendar.dateInterval(
of: .weekOfYear, for: today
)!
// Posun o 1 měsíc
let nextMonth = calendar.date(
byAdding: .month, value: 1, to: today
)!
// Posun přes DateComponents
var delta = DateComponents()
delta.month = 1
delta.day = 3
let shifted = calendar.date(byAdding: delta, to: today)!
// Příští pátek 13.
let friday13Components = DateComponents(
weekday: 6, day: 13
)
let nextFriday13 = calendar.nextDate(
after: today, matching: friday13Components,
matchingPolicy: .nextTime
)
EnumerateDates — výkonná metoda pro iteraci dat podle vzoru. Calendar.enumerateDates(startingAfter:matching:matchingPolicy:using:) volá blok pro každou shodu, dokud blok nevrátí stop = true. Používá se pro generování opakujících se událostí v kalendářích a rozvrzích. Metoda je efektivnější než ruční cyklus s nextDate, protože je optimalizována ICU.
TimeZone — neoddělitelná součást Calendar. Časové pásmo určuje, kterému kalendářnímu času odpovídá absolutní Date. Stejný Date v UTC a v Moskvě dává různé součásti: Date() v UTC může ukazovat 10:00, a v MSK — 13:00. Calendar.timeZone je ve výchozím nastavení roven TimeZone.current.
Locale ovlivňuje první den týdne, minimální počet dní v prvním týdnu roku (minDaysInFirstWeek) a názvy měsíců/dnů v týdnu (při převodu přes DateFormatter). Calendar.locale je ve výchozím nastavení roven Locale.current. V české locale začíná týden pondělím, v americké — nedělí.
Calendar.availableIdentifiers vrací seznam všech podporovaných kalendářních identifikátorů. static property Calendar.availableCalendarIdentifiers — pole řetězců se stejnými identifikátory. Používá se pro vytvoření UI výběru kalendáře a pro kontrolu dostupnosti konkrétního kalendářního systému na zařízení.
// Kalendář s konkrétním časovým pásmem
var utcCalendar = Calendar(identifier: .gregorian)
utcCalendar.timeZone = TimeZone(identifier: "UTC")!
// Kalendář s ruskou locale
var russianCalendar = Calendar(identifier: .gregorian)
russianCalendar.locale = Locale(identifier: "ru_RU")
// První den týdne závisí na locale
let firstWeekday = russianCalendar.firstWeekday
// 2 = pondělí (v cs_CZ)
// Seznam dostupných kalendářů
for identifier in Calendar.availableIdentifiers {
print(identifier)
}
firstWeekday — vlastnost Calendar určující, který den v týdnu je považován za první. V české locale je Sunday = 2 (pondělí první). V americké je Sunday = 1. To ovlivňuje fungování weekOfMonth a weekOfYear: stejné datum může být v různých locale přiřazeno k různým číslům týdne. Pro aplikace s daty používejte Calendar.current nebo explicitně nastavte firstWeekday.
Podívejme se na praktické scénáře demonstrující možnosti Calendar. Každý příklad řeší konkrétní úlohu iOS vývoje a ukazuje správný způsob použití kalendářních výpočtů.
Calendar.dateInterval(of: .month, for:) vrací hranice aktuálního měsíce. Kontrola, zda Date spadá do tohoto intervalu — nejrychlejší způsob, jak zjistit, zda datum patří do aktuálního měsíce. Alternativní způsob — Calendar.compare s granularity .month: pokud je výsledek .orderedSame, měsíc souhlasí.
func isInCurrentMonth(_ date: Date) -> Bool {
let calendar = Calendar.current
let monthInterval = calendar.dateInterval(
of: .month, for: Date()
)!
return monthInterval.contains(date)
}
// Počet dní v měsíci
func daysInMonth(for date: Date) -> Int {
let calendar = Calendar.current
return calendar.range(
of: .day, in: .month, for: date
)?.count ?? 0
}
// Přidávání měsíců se správným ukončením
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:) vrací rozsah povolených hodnot pro zadanou součást v kontextu jiné součásti. Například range(of: .day, in: .month, for: date) vrací 1..<32 pro měsíce s 31 dny nebo 1..<29 pro únor nepřestupného roku. Toto je správný způsob, jak zjistit počet dní v měsíci, ne pomocí pevně zadaných hodnot.
Přidávání měsíců přes Calendar.date(byAdding:value:to:) správně zpracovává hraniční data. Pokud k 31. lednu přidáme 1 měsíc, Calendar vrátí 28. února (nebo 29. v přestupném roce), ne 3. března, jak by se stalo při jednoduchém přidání 30 dnů přes TimeInterval. To je další důvod nepoužívat TimeInterval pro kalendářní výpočty.
| Metoda Calendar | Účel | Příklad |
|---|---|---|
| dateInterval | Hranice období | Začátek a konec měsíce |
| range(of:in:for:) | Rozsah součásti | Dny v aktuálním měsíci |
| date(byAdding:) | Posun data | +1 měsíc od dneška |
| isDateInToday | Kontrola, zda je dnes | Je datum dnešní? |
| compare(toGranularity:) | Porovnání s přesností | Stejný den bez času |
Často kladené otázky
Calendar.current vrací kalendář ze systémových nastavení uživatele — může být negregoriánský (například buddhistický v Thajsku). Calendar(identifier: .gregorian) vždy vytváří gregoriánský kalendář bez ohledu na nastavení. Pro zobrazení dat používejte Calendar.current, pro obchodní logiku — explicitně zvolený identifikátor.
To souvisí s různou délkou měsíců. Pokud je aktuální datum 31. ledna, přidání 1 měsíce dává 28. února, protože únor nemá 31 dní. Kalendář automaticky dokončí datum do posledního povoleného dne měsíce. Pro přesnou kontrolu použijte DateComponents s day: 1 pro přechod na první den měsíce.
DateFormatter používá Calendar.current — systémový kalendář uživatele. Pokud má aplikace vždy zobrazovat data v gregoriánském kalendáři bez ohledu na nastavení, nastavte formatter.calendar = Calendar(identifier: .gregorian). To zaručuje jednotné zobrazení pro všechny uživatele.
Calendar.range(of: .day, in: .year, for: date) vrací 365 nebo 366 dní. Jednodušší: Calendar.date(from: DateComponents(year: rok, month: 2, day: 29)) != nil — pokud 29. únor existuje, rok je přestupný. Calendar sám zohledňuje pravidla pro konkrétní kalendářní systém.
Ano, vlastnost firstWeekday je zapisovatelná. Změna ovlivňuje weekOfMonth, weekOfYear a všechny výpočty související s čísly týdnů. Při nastavení locale = Locale(identifier: "cs_CZ") se firstWeekday automaticky stane 2 (pondělí). Ruční nastavení přepíše hodnotu z locale.
Shrnutí
Vyvineme mobilní aplikaci na klíč
IT Sectr vytváří aplikace pro iOS a Android pro startupy a podniky od roku 2017. Poradíme vám a navrhneme nejlepší řešení.
Přečtěte si také