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 съхранява абсолютно време (брой секунди от референтна дата), докато 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 на календара: в българския locale седмицата започва от понеделник (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 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също