DateComponents — это структура Foundation, которая хранит компоненты календарной даты как отдельные поля: год, месяц, день, час, минута, секунда и другие. В отличие от Date, представляющего абсолютный момент времени, DateComponents содержит человекочитаемые значения, зависящие от календаря и часового пояса. По данным Apple Developer Documentation (2025), DateComponents используется как промежуточное звено между Date и Calendar — через неё извлекаются и конструируются календарные даты, выполняются расчёты и сдвиги дат без ручной арифметики.
Главное
DateComponents — это value-тип 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
// Sozdanie cherez initsializator poley
var components = DateComponents()
components.year = 2026
components.month = 7
components.day = 21
// Izvlechenie iz Date
let now = Date()
let extracted = Calendar.current.dateComponents(
[.year, .month, .day],
from: now
)
print("Today: \(extracted.day!).\(extracted.month!).\(extracted.year!)")
// Sozdanie cherez rasshirennyy initsializator
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)
// Sozdanie Date iz 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)")
}
// Sozdanie s ukazaniem 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
// Next monday
let nextMonday = calendar.nextDate(
after: Date(),
matching: DateComponents(weekday: 2),
matchingPolicy: .nextTime
)!
// Raznitsa mezhdu datami v dnyakh
let diff = calendar.dateComponents(
[.day], from: Date(), to: nextMonday
)
// Diapazon mesyatsa
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
)!
}
// Raschyot vozrasta v godakh
func ageInYears(from birthDate: Date) -> Int {
let calendar = Calendar.current
let ageComponents = calendar.dateComponents(
[.year], from: birthDate, to: Date()
)
return ageComponents.year ?? 0
}
// Gruppirovka sobytiy po godu i mesyatsu
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-based расчёт (секунды / 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 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также