Calendar — е клас Foundation, който дефинира календарната система и предоставя методи за календарни изчисления: извличане на компоненти на дата, изчисляване на разлика между дати, намиране на граници на периоди и изместване на дати. Календарът свързва абсолютното време (Date) с четими за човека компоненти и отчита регионалните особености: начало на седмицата, часова зона и лятно часово време. Според Apple Developer Documentation (2025), Foundation поддържа 17 календарни системи — от григорианска до будистка и японска, което прави Calendar универсален инструмент за интернационализирани приложения.
Основни точки
Calendar — е клас Foundation, който имплементира календарни изчисления на базата на ICU (International Components for Unicode). Календарът определя как абсолютното време (Date) се преобразува в календарни компоненти: година, месец, ден, час, минута, секунда. Без Calendar е невъзможно да се разбере коя година, месец и ден са днес — Date сам по себе си не съдържа тази информация.
Календарът отчита три групи параметри: календарна система (григорианска, будистка, японска), часова зона и локал. Calendar.current комбинира и трите от системните настройки на потребителя. Calendar.autoupdatingCurrent — специална версия, която се актуализира автоматично при промяна на настройките без рестартиране на приложението чрез NotificationCenter.
Календарът е стойностен тип (value type) във Foundation. Calendar(identifier:) създава нов екземпляр с фиксирани параметри. Календарът може да се копира, сравнява чрез == и използва като ключ в речник. Това позволява създаването на календари с конкретни настройки на timeZone и locale за тестване.
Calendar — Swift версията на Objective-C NSCalendar, с моста as Calendar / as NSCalendar. В съвременния Swift навсякъде се използва Calendar. NSCalendar остава за обратна съвместимост с Objective-C API. Calendar има пълен набор от методи без префикс NS, с type-safe аргументи и Swift опционалност.
Thread Safety — Calendar е thread-safe за четене. Създаденият екземпляр може безопасно да се чете от множество нишки. Модифицирането на свойства (timeZone, locale) не е thread-safe — за различни конфигурации създавайте отделни екземпляри на Calendar.
Foundation поддържа 17 календарни системи чрез изброяването Calendar.Identifier. Всяка система има свои правила за високосни години, брой месеци и начало на ера. Изборът на календар влияе на всички изчисления: dateComponents, dateInterval, nextDate.
Основни календарни системи:
Calendar(identifier: .gregorian) — най-често използваният. Той отговаря на международния стандарт ISO 8601 и е календарът по подразбиране в повечето страни. За приложения с международна аудитория използвайте Calendar.current — той автоматично съответства на системния календар на потребителя.
| Идентификатор | Тип | Регион на използване |
|---|---|---|
| .gregorian | Слънчев | Международен |
| .buddhist | Слънчев | Тайланд, Камбоджа |
| .japanese | Слънчев | Япония |
| .hebrew | Лунно-слънчев | Израел |
| .islamic | Лунен | Ислямски държави |
| .chinese | Лунно-слънчев | Китай |
DateComponents и Calendar — неразделна двойка. Calendar.dateComponents(_:from:) извлича компоненти от Date, като отчита часовата зона на календара. Calendar.date(from:) създава Date от DateComponents, попълвайки липсващите полета със стойности по подразбиране: ден = 1, час = 0, минута = 0, секунда = 0.
Методът Calendar.component извлича един компонент, което е удобно за бърза проверка. Calendar.dateComponents извлича набор от компоненти в едно извикване — това е по-ефективно, тъй като Calendar извършва календарните изчисления веднъж, а не за всеки компонент поотделно. За списък от 3+ компонента винаги използвайте dateComponents.
Calendar.compare сравнява две Date с зададена точност. Параметърът toGranularity определя до кой компонент да се извърши сравнението: .year сравнява само годината, .month — година и месец, .day — година, месец, ден. Това е удобно за проверка дали две дати принадлежат към един и същи ден, без да се отчита времето.
let calendar = Calendar.current
let now = Date()
// Извличане на един компонент
let year = calendar.component(.year, from: now)
// Извличане на набор от компоненти
let comps = calendar.dateComponents(
[.year, .month, .day], from: now
)
// Сравнение с точност до ден
let isSameDay = calendar.compare(date1, to: date2,
toGranularity: .day) == .orderedSame
// Провери дали датата е днес
let isToday = calendar.isDateInToday(someDate)
Calendar.isDateInToday, isDateInTomorrow, isDateInYesterday — методи за относителни проверки. Calendar.isDate(_:inSameDayAs:) проверява дали две дати попадат на един и същи календарен ден, като отчита часовата зона на календара. Тези методи използват Calendar.compare вътрешно и са оптимизирани за често извикване.
Calendar.dateInterval — един от най-полезните методи за аналитика и UI. Той връща DateInterval за посочения компонент: начало и край на ден, седмица, месец, година. DateInterval съдържа start (Date) и end (Date) — границите на периода. Например dateInterval(of: .weekOfYear, for: Date()) връща началото на понеделник и края на неделя от текущата седмица.
Calendar.date с byAdding — метод за изместване на дата. Calendar.date(byAdding: .day, value: 7, to: Date()) връща дата след една седмица. Calendar.date(byAdding: DateComponents) — по-гъвкава версия, позволяваща изместване на няколко компонента наведнъж: +1 месец +3 дни. Calendar автоматично отчита различната дължина на месеците и високосните години.
Calendar.nextDate търси следващата дата, съвпадаща с посочените DateComponents. Параметърът matchingPolicy определя поведението при несъвпадение: .nextTime — следващо съвпадение по време, .nextTimePreservingSmallerComponents — запазва минутите и секундите от оригиналната дата, .strict — изисква точно съвпадение.
let calendar = Calendar.current
let today = Date()
// Начало и край на седмица
let weekInterval = calendar.dateInterval(
of: .weekOfYear, for: today
)!
// Изместване с 1 месец
let nextMonth = calendar.date(
byAdding: .month, value: 1, to: today
)!
// Изместване чрез DateComponents
var delta = DateComponents()
delta.month = 1
delta.day = 3
let shifted = calendar.date(byAdding: delta, to: today)!
// Следващият петък 13-ти
let friday13Components = DateComponents(
weekday: 6, day: 13
)
let nextFriday13 = calendar.nextDate(
after: today, matching: friday13Components,
matchingPolicy: .nextTime
)
EnumerateDates — мощен метод за итерация на дати по шаблон. Calendar.enumerateDates(startingAfter:matching:matchingPolicy:using:) извиква блок за всяко съвпадение, докато блокът не върне stop = true. Използва се за генериране на повтарящи се събития в календари и графици. Методът е по-ефективен от ръчен цикъл с nextDate, тъй като е оптимизиран от ICU.
TimeZone — неразделна част от Calendar. Часовата зона определя на кое календарно време съответства абсолютният Date. Един и същи Date в UTC и в Москва дава различни компоненти: Date() в UTC може да показва 10:00, а в MSK — 13:00. Calendar.timeZone по подразбиране е равен на TimeZone.current.
Locale влияе на първия ден от седмицата, минималния брой дни в първата седмица на годината (minDaysInFirstWeek) и имената на месеците/дните от седмицата (при преобразуване чрез DateFormatter). Calendar.locale по подразбиране е равен на Locale.current. В българския локал седмицата започва в понеделник, в американския — в неделя.
Calendar.availableIdentifiers връща списък на всички поддържани календарни идентификатори. static property Calendar.availableCalendarIdentifiers — масив от низове със същите идентификатори. Използва се за изграждане на UI за избор на календар и за проверка на наличието на конкретна календарна система на устройството.
// Календар с конкретна часова зона
var utcCalendar = Calendar(identifier: .gregorian)
utcCalendar.timeZone = TimeZone(identifier: "UTC")!
// Календар с български локал
var russianCalendar = Calendar(identifier: .gregorian)
russianCalendar.locale = Locale(identifier: "ru_RU")
// Първият ден от седмицата зависи от локала
let firstWeekday = russianCalendar.firstWeekday
// 2 = понеделник (в bg_BG)
// Списък на наличните календари
for identifier in Calendar.availableIdentifiers {
print(identifier)
}
firstWeekday — свойство на Calendar, което определя кой ден от седмицата се счита за първи. В българския локал Sunday = 2 (понеделник е първи). В американския Sunday = 1. Това влияе на работата на weekOfMonth и weekOfYear: една и съща дата може да бъде причислена към различни номера на седмици в различни локали. За приложения с дати използвайте Calendar.current или изрично задайте firstWeekday.
Нека разгледаме практически сценарии, демонстриращи възможностите на Calendar. Всеки пример решава конкретна задача от iOS разработката и показва правилния начин за използване на календарни изчисления.
Calendar.dateInterval(of: .month, for:) връща границите на текущия месец. Проверката дали Date попада в този интервал — най-бързият начин да се определи дали датата принадлежи към текущия месец. Алтернативен начин — Calendar.compare с granularity .month: ако резултатът е .orderedSame, месецът съвпада.
func isInCurrentMonth(_ date: Date) -> Bool {
let calendar = Calendar.current
let monthInterval = calendar.dateInterval(
of: .month, for: Date()
)!
return monthInterval.contains(date)
}
// Брой дни в месеца
func daysInMonth(for date: Date) -> Int {
let calendar = Calendar.current
return calendar.range(
of: .day, in: .month, for: date
)?.count ?? 0
}
// Добавяне на месеци с правилно завършване
func addMonths(_ months: Int, to date: Date) -> Date {
let calendar = Calendar.current
return calendar.date(
byAdding: .month, value: months, to: date
)!
}
Calendar.range(of:in:for:) връща диапазон от допустими стойности за посочения компонент в контекста на друг компонент. Например range(of: .day, in: .month, for: date) връща 1..<32 за месеци с 31 дни или 1..<29 за февруари на невисокосна година. Това е правилният начин да се разбере броят на дните в месеца, а не използването на хардкодирани стойности.
Добавяне на месеци чрез Calendar.date(byAdding:value:to:) коректно обработва граничните дати. Ако към 31 януари се добави 1 месец, Calendar връща 28 февруари (или 29 през високосна година), а не 3 март, както би се получило при просто добавяне на 30 дни чрез TimeInterval. Това е още една причина да не използвате TimeInterval за календарни изчисления.
| Метод на Calendar | Предназначение | Пример |
|---|---|---|
| dateInterval | Граници на период | Начало и край на месец |
| range(of:in:for:) | Диапазон на компонент | Дни в текущия месец |
| date(byAdding:) | Изместване на дата | +1 месец от днес |
| isDateInToday | Проверка за днес | Дали датата е днешна |
| compare(toGranularity:) | Сравнение с точност | Същият ден без време |
Често задавани въпроси
Calendar.current връща календара от системните настройки на потребителя — той може да не е григориански (например будистки в Тайланд). Calendar(identifier: .gregorian) винаги създава григориански календар независимо от настройките. За показване на дати използвайте Calendar.current, за бизнес логика — изрично избрания идентификатор.
Това е свързано с различната дължина на месеците. Ако текущата дата е 31 януари, добавянето на 1 месец дава 28 февруари, тъй като февруари няма 31 дни. Calendar автоматично завършва датата до последния допустим ден от месеца. За точен контрол използвайте DateComponents с day: 1 за преминаване към първия ден от месеца.
DateFormatter използва Calendar.current — системния календар на потребителя. Ако приложението трябва винаги да показва дати в григорианския календар независимо от настройките, задайте formatter.calendar = Calendar(identifier: .gregorian). Това гарантира еднообразно показване за всички потребители.
Calendar.range(of: .day, in: .year, for: date) връща 365 или 366 дни. По-просто: Calendar.date(from: DateComponents(year: година, month: 2, day: 29)) != nil — ако 29 февруари съществува, годината е високосна. Calendar сам отчита правилата за конкретната календарна система.
Да, свойството firstWeekday е достъпно за запис. Промяната влияе на weekOfMonth, weekOfYear и всички изчисления, свързани с номерата на седмиците. При задаване на locale = Locale(identifier: "bg_BG") firstWeekday автоматично става 2 (понеделник). Ръчната настройка замества стойността от locale.
Заключение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също