DateComponents je struktura Foundation, která ukládá komponenty kalendářního data jako samostatná pole: rok, měsíc, den, hodina, minuta, sekunda a další. Na rozdíl od Date, které představuje absolutní okamžik v čase, DateComponents obsahuje člověkem čitelné hodnoty závislé na kalendáři a časovém pásmu. Podle Apple Developer Documentation (2025) se DateComponents používá jako mezičlánek mezi Date a Calendar – prostřednictvím něj se extrahují a konstruují kalendářní data, provádějí se výpočty a posuny dat bez ruční aritmetiky.
Hlavní body
DateComponents je hodnotový typ Foundation určený pro ukládání kalendářních komponent času. Každá komponenta je reprezentována jako volitelné pole Int: year, month, day, hour, minute, second, nanosecond, weekday, weekOfMonth, weekOfYear, quarter, yearForWeekOfYear a další.
Hlavní rozdíl oproti Date je vazba na kalendář. Date ukládá absolutní čas (počet sekund od referenčního data), zatímco DateComponents je člověkem čitelná reprezentace, která má smysl pouze v kontextu konkrétního Calendar. Stejné Date může být reprezentováno různými DateComponents v různých kalendářích a časových pásmech.
DateComponents není samostatný typ času, ale kontejner dat. Pro interpretaci DateComponents jako data je vyžadován Calendar, který rozumí tomu, jak komponenty souvisejí s kalendářním systémem. Calendar.dateComponents(from: Date) provádí extrakci komponent, Calendar.date(from: DateComponents) – zpětné sestavení.
Každé pole DateComponents je volitelné (Int?), což je zásadní pro práci s neúplnými daty. Pokud zadáte pouze rok a měsíc, Calendar doplní chybějící pole výchozími hodnotami: den = 1, hodina = 0, minuta = 0. To je vhodné pro vytváření dat začátku období – stačí zadat pouze relevantní komponenty.
Při porovnávání DateComponents operátorem == se porovnávají pouze zadaná (ne nil) pole. Dvě struktury DateComponents s rokem 2026, ale různými měsíci, jsou považovány za různé. isEqual z NSObjectProtocol se pro DateComponents nepoužívá – DateComponents nedědí z NSObject.
Základní pole DateComponents zahrnují year, month, day, hour, minute, second, nanosecond. Každé pole ukládá číselnou hodnotu v odpovídající jednotce: rok – 2026, měsíc – 1..12, den – 1..31, hodina – 0..23, minuta – 0..59, sekunda – 0..59. Nanosekundy mohou nabývat hodnot 0..999999999.
Pole týdne – weekday (1..7, kde 1 = neděle v gregoriánském kalendáři), weekOfMonth, weekOfYear. Tato pole závisí na Calendar a nemají smysl mimo jeho kontext. weekday závisí na nastavení firstWeekday kalendáře: v české lokalizaci týden začíná pondělím (weekday = 2 v gregoriánském systému), v americké – nedělí (weekday = 1).
Specializovaná pole – quarter (1..4), yearForWeekOfYear (rok, ke kterému týden patří), isLeapMonth (logický příznak pro přestupné měsíce v hebrejském nebo čínském kalendáři). Pole calendar a timeZone ukládají odkazy na odpovídající objekty, se kterými byla struktura vytvořena.
| Kategorie | Pole | Rozsah |
|---|---|---|
| Kalendářní | year, month, day | 1..∞, 1..12, 1..31 |
| Časové | hour, minute, second, nanosecond | 0..23, 0..59, 0..59, 0..999999999 |
| Týdenní | weekday, weekOfMonth, weekOfYear | 1..7, 1..5, 1..53 |
| Speciální | quarter, yearForWeekOfYear | 1..4, závislé |
Při extrakci komponent prostřednictvím Calendar.dateComponents je důležité žádat pouze potřebná pole kvůli výkonu. Calendar extrahuje všechna požadovaná pole v jednom průchodu – to je výrazně rychlejší než volat Calendar.component pro každé pole zvlášť.
Inicializace DateComponents – nejjednodušší způsob: vytvoříte prázdnou strukturu a vyplníte potřebná pole. Všechna neuvedená pole automaticky obdrží nil. Datum vytvořené z částečných komponent není validováno ve fázi inicializace – chyba může nastat pouze při převodu na Date přes Calendar.
Inicializátor DateComponents(calendar:timeZone:era:year:month:day:hour:minute:second:nanosecond:weekday:…) umožňuje nastavit všechna pole v jednom volání. Tento inicializátor je vhodný pro vytvoření úplného data z hotových hodnot, ale zřídka se používá s více než 5-6 argumenty kvůli čitelnosti.
Calendar.dateComponents(_:from:) – hlavní způsob získání DateComponents z existujícího Date. Druhým argumentem je sada komponent, které mají být extrahovány. Calendar provádí kalendářní výpočty s ohledem na časové pásmo a vrací strukturu pouze s požadovanými poli, ostatní pole zůstávají nil.
import Foundation
// Vytvoření přes inicializátor polí
var components = DateComponents()
components.year = 2026
components.month = 7
components.day = 21
// Extrakce z Date
let now = Date()
let extracted = Calendar.current.dateComponents(
[.year, .month, .day],
from: now
)
print("Today: \(extracted.day!).\(extracted.month!).\(extracted.year!)")
// Vytvoření přes rozšířený inicializátor
let birthday = DateComponents(
calendar: Calendar.current,
year: 1990, month: 5, day: 15
)
Při vytváření DateComponents ručním nastavením polí vždy před převodem na Date zkontrolujte Calendar. Calendar při převodu date(from:) může vrátit nil, pokud komponenty tvoří neexistující datum – například 31. února nebo 30. února v nepřestupném roce. Validace data je odpovědností Calendar, nikoli DateComponents.
Calendar.date(from:) – hlavní metoda převodu DateComponents na Date. Calendar interpretuje komponenty podle svého kalendáře a časového pásma. Pokud některá pole nejsou nastavena (nil), Calendar použije výchozí hodnoty: den = 1, hodina = 0, minuta = 0, sekunda = 0.
Metoda vrací volitelný Date – nil nastává, když si komponenty odporují nebo tvoří neplatné datum. Typické příčiny nil: neexistující datum (32. ledna, 29. února 2023), protichůdná pole (weekday=1, day=5 v jedné sadě), nemožný rok pro daný kalendář (rok 0 v gregoriánském kalendáři).
DateComponents s timeZone – pokud DateComponents obsahuje timeZone, Calendar jej použije při převodu. Pokud timeZone není uvedena, Calendar použije svou vlastní aktuální timeZone. Pokud Calendar.timeZone neodpovídá očekávanému časovému pásmu data, výsledek se může lišit o několik hodin – ujistěte se, že je timeZone explicitně nastavena v jednom z objektů.
let calendar = Calendar(identifier: .gregorian)
// Vytvoření Date z DateComponents
var comps = DateComponents()
comps.year = 2026
comps.month = 12
comps.day = 25
comps.hour = 10
if let date = calendar.date(from: comps) {
print("Christmas: \(date)")
}
// Vytvoření s uvedením timeZone
calendar.timeZone = TimeZone(identifier: "UTC")!
let utcComps = DateComponents(
calendar: calendar, year: 2026, month: 7, day: 21,
hour: 12
)
let utcDate = calendar.date(from: utcComps)!
Calendar.dateComponents pro rozdíl dat – další scénář použití DateComponents. Calendar.dateComponents([.year, .month, .day], from: Date(), to: futureDate) vrací rozdíl v letech, měsících a dnech mezi dvěma daty. Toto je správný způsob výpočtu věku namísto dělení TimeInterval počtem sekund v roce, protože Calendar zohledňuje přestupné roky.
Calendar – centrální třída, která pracuje s DateComponents. Všechny operace extrakce, sestavení a porovnávání dat procházejí přes Calendar. Bez Calendar je DateComponents pouze sbírkou čísel bez časového významu. Calendar dává komponentám interpretaci: určuje, že měsíc 2 je únor a weekday 2 je pondělí.
Calendar.nextDate a Calendar.enumerateDates – dvě metody založené na DateComponents. nextDate(after: Date(), matching: DateComponents) najde následující datum odpovídající zadaným komponentám – například příští pondělí po dnešku. enumerateDates(startingAfter:matching:matchingPolicy:using:) prochází všechna data odpovídající vzoru až do zadaného limitu.
Calendar.dateInterval – metoda vracející DateInterval pro zadanou komponentu. dateInterval(of: .month, for: Date()) vrací začátek a konec aktuálního měsíce. Interně tato metoda používá DateComponents k nalezení hranic období: vytvoří DateComponents s prvním a posledním dnem měsíce, převede je na Date přes Calendar.
let calendar = Calendar.current
// Příští pondělí
let nextMonday = calendar.nextDate(
after: Date(),
matching: DateComponents(weekday: 2),
matchingPolicy: .nextTime
)!
// Rozdíl mezi daty ve dnech
let diff = calendar.dateComponents(
[.day], from: Date(), to: nextMonday
)
// Rozsah měsíce
let monthInterval = calendar.dateInterval(
of: .month, for: Date()
)!
let startOfMonth = monthInterval.start
let endOfMonth = monthInterval.end
MatchingPolicy – důležitý parametr metod Calendar při práci s DateComponents. strictPolicy vyžaduje přesnou shodu všech komponent, nextTimePolicy vybírá další časovou shodu, nextTimePreservingSmallerComponents zachovává menší komponenty (minuty, sekundy) z původního data. Volba politiky ovlivňuje výsledek vyhledávání dat, zejména při posunu přes změnu letního/zimního času.
Prozkoumáme praktické scénáře použití DateComponents v aplikaci. Každý příklad demonstruje typický úkol, se kterým se iOS vývojář setkává při práci s kalendářními daty.
Calendar.nextDate s DateComponents(day: 1) najde první den následujícího měsíce. Calendar automaticky určí počet dní v aktuálním měsíci a přejde k dalšímu měsíci. Pro opakovaná upozornění použijte enumerateDates nebo Combine.Timer s klíčem Calendar.
func firstDayOfNextMonth(from date: Date) -> Date {
let calendar = Calendar.current
let comps = DateComponents(day: 1)
return calendar.nextDate(
after: date,
matching: comps,
matchingPolicy: .nextTime
)!
}
// Výpočet věku v letech
func ageInYears(from birthDate: Date) -> Int {
let calendar = Calendar.current
let ageComponents = calendar.dateComponents(
[.year], from: birthDate, to: Date()
)
return ageComponents.year ?? 0
}
// Seskupování událostí podle roku a měsíce
func groupEventsByMonth(_ events: [Event]) -> [String: [Event]] {
let calendar = Calendar.current
return Dictionary(grouping: events) { event in
let comps = calendar.dateComponents(
[.year, .month], from: event.date
)
return "\(comps.year!)-\(comps.month!)"
}
}
Výpočet věku prostřednictvím Calendar.dateComponents([.year], from:to:) – jediný správný způsob, který zohledňuje přestupné roky. Výpočet založený na TimeInterval (sekundy / 31536000) dává chybu pro osoby narozené 29. února. Calendar správně určuje, zda byly narozeniny v aktuálním roce, a vrací přesný věk.
Seskupování podle roku a měsíce – běžný úkol pro obrazovky s historií nebo kalendářem. DateComponents slouží jako klíč seskupení: extrahujete rok a měsíc z data události, vytvoříte řetězcový klíč a seskupíte přes Dictionary(grouping:). Pro zobrazení použijte DateFormatter se šablonou „LLLL yyyy‟ pro lokalizovaný název měsíce.
| Úkol | Metoda Calendar | Role DateComponents |
|---|---|---|
| První den měsíce | nextDate(after:matching:) | day: 1 |
| Výpočet věku | dateComponents(from:to:) | [.year] z rozdílu |
| Seskupování dat | dateComponents(_:from:) | year + month klíč |
| Hledání dne v týdnu | nextDate(after:matching:) | weekday: N |
Často kladené otázky
Příčiny: neexistující datum (31. dubna), protichůdná pole (weekday=1 s day=5), neplatná kombinace polí pro vybraný kalendář. Calendar se pokouší interpretovat komponenty ve svém systému – pokud je kombinace nemožná, výsledkem je nil. Vždy používejte guard let nebo if let při převodu.
Ano, pomocí operátoru ==. DateComponents implementuje Equatable, porovnávající všechna pole. Dvě struktury jsou si rovny, pokud jsou všechna jejich pole stejná (nil == nil se považuje za pravdu). Pro porovnání pouze části polí – extrahujte stejnou sadu pomocí Calendar.dateComponents.
Date – absolutní okamžik v čase bez vazby na kalendář. DateComponents – sada člověkem čitelných čísel (rok, měsíc, den), která mají smysl pouze v kontextu Calendar. Date lze porovnávat, odečítat, serializovat do ISO 8601. DateComponents – zprostředkující reprezentace pro interakci s kalendářem.
Nastavte pouze pole year a month, ostatní ponechte jako nil. Při převodu na Date přes Calendar.date(from:) Calendar automaticky nastaví den = 1, hodina = 0, minuta = 0. Výsledkem je Date odpovídající prvnímu dni zadaného měsíce o půlnoci.
DateComponents neukládá informaci o časovém pásmu v polích – hodnoty polí (rok, měsíc, den) samy závisí na timeZone, ve které byly extrahovány. Komponenty „21. července 2026 14:00 MSK‟ a „21. července 2026 10:00 UTC‟ představují stejné Date, ale pole DateComponents jsou různá.
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é