DateComponents to struktura Foundation, która przechowuje komponenty daty kalendarzowej jako osobne pola: rok, miesiąc, dzień, godzina, minuta, sekunda i inne. W przeciwieństwie do Date, reprezentującego absolutny moment w czasie, DateComponents zawiera wartości czytelne dla człowieka, zależne od kalendarza i strefy czasowej. Według Apple Developer Documentation (2025), DateComponents jest używana jako pośrednie ogniwo między Date i Calendar – za jej pomocą wyodrębnia się i konstruuje daty kalendarzowe, wykonuje obliczenia i przesunięcia dat bez ręcznej arytmetyki.
Najważniejsze
DateComponents to typ wartościowy Foundation przeznaczony do przechowywania kalendarzowych komponentów czasu. Każdy komponent jest reprezentowany jako opcjonalne pole Int: year, month, day, hour, minute, second, nanosecond, weekday, weekOfMonth, weekOfYear, quarter, yearForWeekOfYear i inne.
Główna różnica w porównaniu z Date to powiązanie z kalendarzem. Date przechowuje absolutny czas (liczbę sekund od reference date), a DateComponents – czytelną dla człowieka reprezentację, która ma sens tylko w kontekście konkretnego Calendar. Ten sam Date może być reprezentowany przez różne DateComponents w różnych kalendarzach i strefach czasowych.
DateComponents to nie samodzielny typ czasu, a kontener danych. Do interpretacji DateComponents jako daty wymagany jest Calendar, który rozumie, jak komponenty odnoszą się do systemu kalendarzowego. Calendar.dateComponents(from: Date) wykonuje wyodrębnianie komponentów, Calendar.date(from: DateComponents) – odwrotne składanie.
Każde pole DateComponents jest opcjonalne (Int?), co ma zasadnicze znaczenie dla pracy z niepełnymi datami. Jeśli podasz tylko rok i miesiąc, Calendar uzupełni brakujące polami wartościami domyślnymi: dzień = 1, godzina = 0, minuta = 0. Jest to wygodne do tworzenia dat początku okresu – wystarczy określić tylko interesujące komponenty.
Przy porównywaniu DateComponents operatorem == porównywane są tylko określone (nie nil) pola. Dwie struktury DateComponents z rokiem 2026, ale różnymi miesiącami, są uznawane za różne. isEqual z NSObjectProtocol nie jest używane dla DateComponents – DateComponents nie dziedziczy po NSObject.
Podstawowe pola DateComponents obejmują year, month, day, hour, minute, second, nanosecond. Każde pole przechowuje wartość liczbową w odpowiedniej jednostce: rok – 2026, miesiąc – 1..12, dzień – 1..31, godzina – 0..23, minuta – 0..59, sekunda – 0..59. Nanosekundy mogą przyjmować wartości 0..999999999.
Pola tygodnia – weekday (1..7, gdzie 1 = niedziela w kalendarzu gregoriańskim), weekOfMonth, weekOfYear. Te pola zależą od Calendar i nie mają sensu poza jego kontekstem. weekday zależy od ustawienia firstWeekday kalendarza: w polskiej lokalizacji tydzień zaczyna się od poniedziałku (weekday = 2 w systemie gregoriańskim), a w amerykańskiej – od niedzieli (weekday = 1).
Pola specjalistyczne – quarter (1..4), yearForWeekOfYear (rok, do którego należy tydzień), isLeapMonth (flaga logiczna dla miesięcy przestępnych w kalendarzu hebrajskim lub chińskim). Pola calendar i timeZone przechowują odniesienia do odpowiednich obiektów, z którymi struktura została utworzona.
| Kategoria | Pola | Zakres |
|---|---|---|
| Kalendarzowe | year, month, day | 1..∞, 1..12, 1..31 |
| Czasowe | hour, minute, second, nanosecond | 0..23, 0..59, 0..59, 0..999999999 |
| Tygodniowe | weekday, weekOfMonth, weekOfYear | 1..7, 1..5, 1..53 |
| Specjalne | quarter, yearForWeekOfYear | 1..4, zależne |
Przy wyodrębnianiu komponentów przez Calendar.dateComponents ważne jest, aby żądać tylko potrzebnych pól dla wydajności. Calendar wyodrębnia wszystkie żądane pola w jednym przebiegu – jest to znacznie szybsze niż wywoływanie Calendar.component dla każdego pola osobno.
Inicjalizacja DateComponents – najprostszy sposób: tworzysz pustą strukturę i wypełniasz potrzebne pola. Wszystkie nieokreślone pola automatycznie otrzymują nil. Data utworzona z częściowych komponentów nie jest walidowana na etapie inicjalizacji – błąd może wystąpić dopiero przy konwersji na Date przez Calendar.
Inicjalizator DateComponents(calendar:timeZone:era:year:month:day:hour:minute:second:nanosecond:weekday:…) pozwala ustawić wszystkie pola w jednym wywołaniu. Ten inicjalizator jest wygodny do tworzenia pełnej daty z gotowych wartości, ale rzadko używany z więcej niż 5-6 argumentami ze względu na czytelność.
Calendar.dateComponents(_:from:) – podstawowy sposób uzyskiwania DateComponents z istniejącego Date. Drugi argument to zestaw składowych, które należy wyodrębnić. Calendar wykonuje obliczenia kalendarzowe z uwzględnieniem strefy czasowej i zwraca strukturę tylko z żądanymi polami, pozostałe pola pozostają nil.
import Foundation
// Tworzenie przez inicjalizator pól
var components = DateComponents()
components.year = 2026
components.month = 7
components.day = 21
// Wyodrębnianie z Date
let now = Date()
let extracted = Calendar.current.dateComponents(
[.year, .month, .day],
from: now
)
print("Today: \(extracted.day!).\(extracted.month!).\(extracted.year!)")
// Tworzenie przez rozszerzony inicjalizator
let birthday = DateComponents(
calendar: Calendar.current,
year: 1990, month: 5, day: 15
)
Przy tworzeniu DateComponents przez ręczne pola zawsze sprawdzaj Calendar przed konwersją na Date. Calendar przy konwersji date(from:) może zwrócić nil, jeśli komponenty tworzą nieistniejącą datę – na przykład 31 lutego lub 30 lutego w roku nieprzestępnym. Walidacja daty jest odpowiedzialnością Calendar, nie DateComponents.
Calendar.date(from:) – podstawowa metoda konwersji DateComponents na Date. Calendar interpretuje komponenty zgodnie ze swoim kalendarzem i strefą czasową. Jeśli jakieś pola nie są ustawione (nil), Calendar używa wartości domyślnych: dzień = 1, godzina = 0, minuta = 0, sekunda = 0.
Metoda zwraca opcjonalny Date – nil występuje, gdy komponenty są ze sobą sprzeczne lub tworzą nieprawidłową datę. Typowe przyczyny nil: nieistniejąca data (32 stycznia, 29 lutego 2023), sprzeczne pola (weekday=1, day=5 w jednym zestawie), niemożliwy rok dla danego kalendarza (rok 0 w kalendarzu gregoriańskim).
DateComponents z timeZone – jeśli DateComponents zawiera timeZone, Calendar używa go przy konwersji. Jeśli timeZone nie jest określona, Calendar używa swojej bieżącej timeZone. Jeśli Calendar.timeZone nie odpowiada oczekiwanej strefie czasowej daty, wynik może różnić się o kilka godzin – upewnij się, że timeZone jest jawnie ustawiona w jednym z obiektów.
let calendar = Calendar(identifier: .gregorian)
// Tworzenie 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)")
}
// Tworzenie z określeniem 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 dla różnicy dat – kolejny scenariusz użycia DateComponents. Calendar.dateComponents([.year, .month, .day], from: Date(), to: futureDate) zwraca różnicę w latach, miesiącach i dniach między dwiema datami. Jest to prawidłowy sposób obliczania wieku zamiast dzielenia TimeInterval przez liczbę sekund w roku, ponieważ Calendar uwzględnia lata przestępne.
Calendar – centralna klasa, która pracuje z DateComponents. Wszystkie operacje wyodrębniania, składania i porównywania dat przechodzą przez Calendar. Bez Calendar DateComponents to tylko zbiór liczb nie mający znaczenia czasowego. Calendar nadaje komponentom interpretację: określa, że miesiąc 2 to luty, a weekday 2 to poniedziałek.
Calendar.nextDate i Calendar.enumerateDates – dwie metody oparte na DateComponents. nextDate(after: Date(), matching: DateComponents) znajduje następną datę odpowiadającą określonym komponentom – na przykład następny poniedziałek po dzisiejszym. enumerateDates(startingAfter:matching:matchingPolicy:using:) iteruje wszystkie daty pasujące do wzorca do określonego limitu.
Calendar.dateInterval – metoda zwracająca DateInterval dla określonego komponentu. dateInterval(of: .month, for: Date()) zwraca początek i koniec bieżącego miesiąca. Wewnętrznie ta metoda używa DateComponents do znalezienia granic okresu: tworzy DateComponents z pierwszym i ostatnim dniem miesiąca, konwertuje je na Date przez Calendar.
let calendar = Calendar.current
// Następny poniedziałek
let nextMonday = calendar.nextDate(
after: Date(),
matching: DateComponents(weekday: 2),
matchingPolicy: .nextTime
)!
// Różnica między datami w dniach
let diff = calendar.dateComponents(
[.day], from: Date(), to: nextMonday
)
// Zakres miesiąca
let monthInterval = calendar.dateInterval(
of: .month, for: Date()
)!
let startOfMonth = monthInterval.start
let endOfMonth = monthInterval.end
MatchingPolicy – ważny parametr metod Calendar przy pracy z DateComponents. strictPolicy wymaga dokładnego dopasowania wszystkich komponentów, nextTimePolicy wybiera następne w czasie dopasowanie, nextTimePreservingSmallerComponents zachowuje mniejsze komponenty (minuty, sekundy) z oryginalnej daty. Wybór polityki wpływa na wynik wyszukiwania dat, szczególnie przy przesunięciu przez zmianę czasu na letni/zimowy.
Rozważmy praktyczne scenariusze zastosowania DateComponents w aplikacji. Każdy przykład demonstruje typowe zadanie, z którym spotyka się programista iOS podczas pracy z datami kalendarzowymi.
Calendar.nextDate z DateComponents(day: 1) znajduje pierwszy dzień następnego miesiąca. Calendar automatycznie określa liczbę dni w bieżącym miesiącu i przechodzi do następnego. Do powtarzających się powiadomień używaj enumerateDates lub Combine.Timer z kluczem 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
)!
}
// Obliczanie wieku w latach
func ageInYears(from birthDate: Date) -> Int {
let calendar = Calendar.current
let ageComponents = calendar.dateComponents(
[.year], from: birthDate, to: Date()
)
return ageComponents.year ?? 0
}
// Grupowanie zdarzeń według roku i miesiąca
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!)"
}
}
Obliczanie wieku przez Calendar.dateComponents([.year], from:to:) – jedyny prawidłowy sposób uwzględniający lata przestępne. Obliczenia oparte na TimeInterval (sekundy / 31536000) dają błąd dla osób urodzonych 29 lutego. Calendar poprawnie określa, czy urodziny były w bieżącym roku, i zwraca dokładny wiek.
Grupowanie według roku i miesiąca – częste zadanie dla ekranów z historią lub kalendarzem. DateComponents służy jako klucz grupowania: wyodrębniasz rok i miesiąc z daty zdarzenia, tworzysz klucz w postaci stringa i grupujesz przez Dictionary(grouping:). Do wyświetlania używaj DateFormatter z szablonem „LLLL yyyy” dla zlokalizowanej nazwy miesiąca.
| Zadanie | Metoda Calendar | Rola DateComponents |
|---|---|---|
| Pierwszy dzień miesiąca | nextDate(after:matching:) | day: 1 |
| Obliczanie wieku | dateComponents(from:to:) | [.year] z różnicy |
| Grupowanie dat | dateComponents(_:from:) | year + month klucz |
| Wyszukiwanie dnia tygodnia | nextDate(after:matching:) | weekday: N |
Często zadawane pytania
Przyczyny: nieistniejąca data (31 kwietnia), sprzeczne pola (weekday=1 z day=5), nieprawidłowa kombinacja pól dla wybranego kalendarza. Calendar próbuje zinterpretować komponenty w swoim systemie – jeśli kombinacja jest niemożliwa, wynik to nil. Zawsze używaj guard let lub if let przy konwersji.
Tak, przez operator ==. DateComponents implementuje Equatable, porównując wszystkie pola. Dwie struktury są równe, jeśli wszystkie ich pola są równe (nil == nil jest uznawane za prawdę). Do porównania tylko części pól – wyodrębnij ten sam zestaw przez Calendar.dateComponents.
Date – absolutny moment w czasie bez powiązania z kalendarzem. DateComponents – zestaw czytelnych dla człowieka liczb (rok, miesiąc, dzień), które mają sens tylko w kontekście Calendar. Date można porównać, odjąć, serializować do ISO 8601. DateComponents – reprezentacja pośrednia do interakcji z kalendarzem.
Ustaw tylko pola year i month, pozostawiając pozostałe jako nil. Przy konwersji na Date przez Calendar.date(from:) Calendar automatycznie ustawi dzień = 1, godzina = 0, minuta = 0. Wynik – Date odpowiadający pierwszemu dniowi określonego miesiąca o północy.
DateComponents nie przechowuje informacji o strefie czasowej w polach – wartości pól (rok, miesiąc, dzień) same zależą od timeZone, w której zostały wyodrębnione. Komponenty „21 lipca 2026 14:00 MSK” i „21 lipca 2026 10:00 UTC” reprezentują tę samą datę Date, ale pola DateComponents są różne.
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ż