DateComponents — це структура Foundation, яка зберігає компоненти календарної дати як окремі поля: рік, місяць, день, година, хвилина, секунда та інші. На відміну від Date, що представляє абсолютний момент часу, DateComponents містить зрозумілі людині значення, які залежать від календаря та часового поясу. За даними Apple Developer Documentation (2025), DateComponents використовується як проміжна ланка між Date та Calendar — через неї витягуються та конструюються календарні дати, виконуються розрахунки та зсуви дат без ручної арифметики.
Головне
DateComponents — це тип значення Foundation, призначений для зберігання календарних компонентів часу. Кожен компонент представлений опціональним Int-полем: year, month, day, hour, minute, second, nanosecond, weekday, weekOfMonth, weekOfYear, quarter, yearForWeekOfYear та інші.
Головна відмінність від Date — прив'язка до календаря. Date зберігає абсолютний час (кількість секунд від reference date), а DateComponents — зрозуміле людині представлення, яке має сенс лише в контексті конкретного Calendar. Одна й та сама Date може бути представлена різними DateComponents у різних календарях та часових поясах.
DateComponents — не самостійний тип часу, а контейнер даних. Для інтерпретації DateComponents як дати потрібен Calendar, який розуміє, як компоненти співвідносяться з календарною системою. Calendar.dateComponents(from: Date) виконує витягнення компонентів, Calendar.date(from: DateComponents) — зворотне збирання.
Кожне поле DateComponents — опціональне (Int?), що принципово для роботи з неповними датами. Якщо вказати лише рік та місяць, Calendar добудує відсутні поля значеннями за замовчуванням: день = 1, година = 0, хвилина = 0. Це зручно для створення дат початку періоду — потрібно задати лише потрібні компоненти.
При порівнянні DateComponents через оператор == порівнюються лише задані (не nil) поля. Дві структури DateComponents з роком 2026, але різними місяцями, вважаються різними. isEqual з NSObjectProtocol для DateComponents не застосовується — DateComponents не наслідує NSObject.
Основні поля DateComponents включають year, month, day, hour, minute, second, nanosecond. Кожне поле зберігає числове значення у відповідній одиниці: рік — 2026, місяць — 1..12, день — 1..31, година — 0..23, хвилина — 0..59, секунда — 0..59. Наносекунди можуть приймати значення 0..999999999.
Поля тижня — weekday (1..7, де 1 = неділя в григоріанському календарі), weekOfMonth, weekOfYear. Ці поля залежать від Calendar і не мають сенсу поза його контекстом. weekday залежить від налаштування firstWeekday календаря: в українській локалі тиждень починається з понеділка (weekday = 2 в григоріанській системі), а в американській — з неділі (weekday = 1).
Спеціалізовані поля — quarter (1..4), yearForWeekOfYear (рік, до якого належить тиждень), isLeapMonth (логічний прапорець для високосних місяців в івритському або китайському календарях). Поля calendar та timeZone зберігають посилання на відповідні об'єкти, з якими структура була створена.
| Категорія | Поля | Діапазон |
|---|---|---|
| Календарні | year, month, day | 1..∞, 1..12, 1..31 |
| Часові | hour, minute, second, nanosecond | 0..23, 0..59, 0..59, 0..999999999 |
| Тижневі | weekday, weekOfMonth, weekOfYear | 1..7, 1..5, 1..53 |
| Спеціальні | quarter, yearForWeekOfYear | 1..4, залежне |
При витягненні компонентів через Calendar.dateComponents важливо запитувати лише потрібні поля для продуктивності. Calendar витягує всі запитані поля за один прохід — це значно швидше, ніж викликати Calendar.component для кожного поля окремо.
Ініціалізація DateComponents — найпростіший спосіб: створюєте порожню структуру та заповнюєте потрібні поля. Всі не вказані поля автоматично отримують nil. Дата, створена з часткових компонентів, не валідується на етапі ініціалізації — помилка може виникнути лише при перетворенні на Date через Calendar.
Ініціалізатор DateComponents(calendar:timeZone:era:year:month:day:hour:minute:second:nanosecond:weekday:…) дозволяє задати всі поля в одному виклику. Цей ініціалізатор зручний для створення повної дати з готових значень, але рідко використовується з більш ніж 5-6 аргументами через читабельність.
Calendar.dateComponents(_:from:) — основний спосіб отримання DateComponents з існуючої Date. Другий аргумент — набір складових, які потрібно витягти. Calendar виконує календарні розрахунки з урахуванням часового поясу та повертає структуру лише із запитаними полями, решта полів залишаються nil.
import Foundation
// Створення через ініціалізатор полів
var components = DateComponents()
components.year = 2026
components.month = 7
components.day = 21
// Витягнення з Date
let now = Date()
let extracted = Calendar.current.dateComponents(
[.year, .month, .day],
from: now
)
print("Today: \(extracted.day!).\(extracted.month!).\(extracted.year!)")
// Створення через розширений ініціалізатор
let birthday = DateComponents(
calendar: Calendar.current,
year: 1990, month: 5, day: 15
)
При створенні DateComponents через поля вручну завжди перевіряйте Calendar перед перетворенням на Date. Calendar при перетворенні date(from:) може повернути nil, якщо компоненти утворюють неіснуючу дату — наприклад, 31 лютого або 30 лютого в невисокосний рік. Валідація дати — відповідальність Calendar, не DateComponents.
Calendar.date(from:) — основний метод перетворення DateComponents на Date. Calendar інтерпретує компоненти згідно зі своїм календарем та часовим поясом. Якщо якісь поля не задані (nil), Calendar використовує значення за замовчуванням: день = 1, година = 0, хвилина = 0, секунда = 0.
Метод повертає опціональний Date — nil виникає, якщо компоненти суперечать один одному або утворюють невалідну дату. Типові причини nil: неіснуюча дата (32 січня, 29 лютого 2023), суперечливі поля (weekday=1, day=5 в одному наборі), неможливий рік для даного календаря (рік 0 в григоріанському календарі).
DateComponents з timeZone — якщо DateComponents містить timeZone, Calendar використовує його при перетворенні. Якщо timeZone не вказана, Calendar використовує свою поточну timeZone. Якщо Calendar.timeZone не збігається з очікуваним часовим поясом дати, результат може відрізнятися на кілька годин — переконайтеся, що timeZone явно задана в одному з об'єктів.
let calendar = Calendar(identifier: .gregorian)
// Створення Date з 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)")
}
// Створення із зазначенням 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 для різниці дат — ще один сценарій використання DateComponents. Calendar.dateComponents([.year, .month, .day], from: Date(), to: futureDate) повертає різницю в роках, місяцях та днях між двома датами. Це правильний спосіб обчислення віку замість ділення TimeInterval на кількість секунд у році, оскільки Calendar враховує високосні роки.
Calendar — центральний клас, який працює з DateComponents. Всі операції з витягнення, збирання та порівняння дат проходять через Calendar. Без Calendar DateComponents — просто набір чисел, що не має часового сенсу. Calendar надає компонентам інтерпретацію: визначає, що місяць 2 — лютий, а weekday 2 — понеділок.
Calendar.nextDate та Calendar.enumerateDates — два методи, засновані на DateComponents. nextDate(after: Date(), matching: DateComponents) знаходить наступну дату, що відповідає заданим компонентам, — наприклад, наступний понеділок після сьогодні. enumerateDates(startingAfter:matching:matchingPolicy:using:) перебирає всі дати, що відповідають шаблону, до вказаної межі.
Calendar.dateInterval — метод, що повертає DateInterval для вказаного компонента. dateInterval(of: .month, for: Date()) повертає початок та кінець поточного місяця. Всередині цей метод використовує DateComponents для знаходження меж періоду: створює DateComponents з першим та останнім днем місяця, перетворює їх на Date через Calendar.
let calendar = Calendar.current
// Наступний понеділок
let nextMonday = calendar.nextDate(
after: Date(),
matching: DateComponents(weekday: 2),
matchingPolicy: .nextTime
)!
// Різниця між датами в днях
let diff = calendar.dateComponents(
[.day], from: Date(), to: nextMonday
)
// Діапазон місяця
let monthInterval = calendar.dateInterval(
of: .month, for: Date()
)!
let startOfMonth = monthInterval.start
let endOfMonth = monthInterval.end
MatchingPolicy — важливий параметр методів Calendar при роботі з DateComponents. strictPolicy вимагає точного збігу всіх компонентів, nextTimePolicy вибирає наступний за часом збіг, nextTimePreservingSmallerComponents зберігає менші компоненти (хвилини, секунди) з вихідної дати. Вибір політики впливає на результат пошуку дат, особливо при зсуві через перехід на літній час.
Розглянемо практичні сценарії застосування DateComponents у додатку. Кожен приклад демонструє типову задачу, з якою стикається iOS-розробник при роботі з календарними датами.
Calendar.nextDate з DateComponents(day: 1) знаходить перший день наступного місяця. Calendar автоматично визначає кількість днів у поточному місяці та переходить до наступного. Для повторюваних сповіщень використовуйте enumerateDates або Combine.Timer з 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
)!
}
// Розрахунок віку в роках
func ageInYears(from birthDate: Date) -> Int {
let calendar = Calendar.current
let ageComponents = calendar.dateComponents(
[.year], from: birthDate, to: Date()
)
return ageComponents.year ?? 0
}
// Групування подій за роком і місяцем
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!)"
}
}
Розрахунок віку через Calendar.dateComponents([.year], from:to:) — єдиний правильний спосіб, що враховує високосні роки. TimeInterval-базований розрахунок (секунди / 31536000) дає помилку для людей, народжених 29 лютого. Calendar коректно визначає, чи був день народження в поточному році, та повертає точний вік.
Групування за роком та місяцем — поширена задача для екранів з історією або календарем. DateComponents слугує ключем групування: витягуєте рік та місяць з дати події, формуєте рядок-ключ та групуєте через Dictionary(grouping:). Для відображення використовуйте DateFormatter з шаблоном «LLLL yyyy» для локалізованої назви місяця.
| Задача | Метод Calendar | Роль DateComponents |
|---|---|---|
| Перший день місяця | nextDate(after:matching:) | day: 1 |
| Розрахунок віку | dateComponents(from:to:) | [.year] з різниці |
| Групування дат | dateComponents(_:from:) | year + month ключ |
| Пошук дня тижня | nextDate(after:matching:) | weekday: N |
Часті запитання
Причини: неіснуюча дата (31 квітня), суперечливі поля (weekday=1 з day=5), невалідне поєднання полів для вибраного календаря. Calendar намагається інтерпретувати компоненти у своїй системі — якщо комбінація неможлива, результат nil. Завжди використовуйте guard let або if let при перетворенні.
Так, через оператор ==. DateComponents реалізує Equatable, порівнюючи всі поля. Дві структури рівні, якщо всі їхні поля рівні (nil == nil вважається істиною). Для порівняння лише частини полів — витягніть однаковий набір через Calendar.dateComponents.
Date — абсолютний момент часу без прив'язки до календаря. DateComponents — набір зрозумілих людині чисел (рік, місяць, день), які мають сенс лише в контексті Calendar. Date можна порівняти, відняти, серіалізувати в ISO 8601. DateComponents — проміжне представлення для взаємодії з календарем.
Задайте лише поля year та month, залишивши решту nil. При перетворенні на Date через Calendar.date(from:) Calendar автоматично встановить день = 1, година = 0, хвилина = 0. Результат — Date, що відповідає першому дню вказаного місяця опівночі.
DateComponents не зберігає інформацію про часовий пояс у полях — значення полів (рік, місяць, день) самі залежать від timeZone, в якому вони витягувалися. Компоненти «21 липня 2026 14:00 MSK» та «21 липня 2026 10:00 UTC» представляють одну й ту саму Date, але поля DateComponents різні.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також