Calendar — какво е това, календар Date и изчисления в Swift

Автор: IT Sectr Публикувано: 2026-07-12 Време за четене: 7 мин

Calendar — е клас Foundation, който дефинира календарната система и предоставя методи за календарни изчисления: извличане на компоненти на дата, изчисляване на разлика между дати, намиране на граници на периоди и изместване на дати. Календарът свързва абсолютното време (Date) с четими за човека компоненти и отчита регионалните особености: начало на седмицата, часова зона и лятно часово време. Според Apple Developer Documentation (2025), Foundation поддържа 17 календарни системи — от григорианска до будистка и японска, което прави Calendar универсален инструмент за интернационализирани приложения.

Основни точки

  • Calendar — клас Foundation за календарни изчисления: извличане на компоненти, сравняване и изместване на дати.
  • Calendar.current — системният календар на потребителя, автоматично отчитащ регионалните настройки.
  • 17 календарни системи — Foundation поддържа григориански, будистки, японски, еврейски, ислямски и други.
  • Calendar.dateComponents извлича компоненти (година, месец, ден) от Date, като отчита часовата зона.
  • Calendar.dateInterval връща началната и крайната дата на посочения период (ден, седмица, месец).

Какво е Calendar в Foundation?

Calendar — е клас Foundation, който имплементира календарни изчисления на базата на ICU (International Components for Unicode). Календарът определя как абсолютното време (Date) се преобразува в календарни компоненти: година, месец, ден, час, минута, секунда. Без Calendar е невъзможно да се разбере коя година, месец и ден са днес — Date сам по себе си не съдържа тази информация.

Календарът отчита три групи параметри: календарна система (григорианска, будистка, японска), часова зона и локал. Calendar.current комбинира и трите от системните настройки на потребителя. Calendar.autoupdatingCurrent — специална версия, която се актуализира автоматично при промяна на настройките без рестартиране на приложението чрез NotificationCenter.

Календарът е стойностен тип (value type) във Foundation. Calendar(identifier:) създава нов екземпляр с фиксирани параметри. Календарът може да се копира, сравнява чрез == и използва като ключ в речник. Това позволява създаването на календари с конкретни настройки на timeZone и locale за тестване.

Calendar и NSCalendar

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

Foundation поддържа 17 календарни системи чрез изброяването Calendar.Identifier. Всяка система има свои правила за високосни години, брой месеци и начало на ера. Изборът на календар влияе на всички изчисления: dateComponents, dateInterval, nextDate.

Основни календарни системи:

  • .gregorian — международен стандарт, 12 месеца, 365/366 дни, ера от Рождество Христово.
  • .buddhist — будистки календар, използван в Тайланд, Камбоджа, Лаос, ерата е с 543 години преди григорианската.
  • .japanese — японски календар по ери на управление на императорите, 12 месеца като в григорианския.
  • .hebrew — еврейски календар, 12/13 месеца, високосен месец Адар II.
  • .islamic — ислямски календар, 12 лунни месеца, 354/355 дни.
  • .indian — индийски национален календар (Saka), 12 месеца.

Calendar(identifier: .gregorian) — най-често използваният. Той отговаря на международния стандарт ISO 8601 и е календарът по подразбиране в повечето страни. За приложения с международна аудитория използвайте Calendar.current — той автоматично съответства на системния календар на потребителя.

ИдентификаторТипРегион на използване
.gregorianСлънчевМеждународен
.buddhistСлънчевТайланд, Камбоджа
.japaneseСлънчевЯпония
.hebrewЛунно-слънчевИзраел
.islamicЛуненИслямски държави
.chineseЛунно-слънчевКитай

Calendar и DateComponents

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 — година, месец, ден. Това е удобно за проверка дали две дати принадлежат към един и същи ден, без да се отчита времето.

swift
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 изчисления

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 — изисква точно съвпадение.

swift
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 и Locale

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 за избор на календар и за проверка на наличието на конкретна календарна система на устройството.

swift
// Календар с конкретна часова зона
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

Нека разгледаме практически сценарии, демонстриращи възможностите на Calendar. Всеки пример решава конкретна задача от iOS разработката и показва правилния начин за използване на календарни изчисления.

Проверка: датата в този месец ли е?

Calendar.dateInterval(of: .month, for:) връща границите на текущия месец. Проверката дали Date попада в този интервал — най-бързият начин да се определи дали датата принадлежи към текущия месец. Алтернативен начин — Calendar.compare с granularity .month: ако резултатът е .orderedSame, месецът съвпада.

swift
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 връща календара от системните настройки на потребителя — той може да не е григориански (например будистки в Тайланд). Calendar(identifier: .gregorian) винаги създава григориански календар независимо от настройките. За показване на дати използвайте Calendar.current, за бизнес логика — изрично избрания идентификатор.

Защо Calendar.date(byAdding: .month, value: 1) понякога връща същата дата?

Това е свързано с различната дължина на месеците. Ако текущата дата е 31 януари, добавянето на 1 месец дава 28 февруари, тъй като февруари няма 31 дни. Calendar автоматично завършва датата до последния допустим ден от месеца. За точен контрол използвайте DateComponents с day: 1 за преминаване към първия ден от месеца.

Кой календар използва DateFormatter по подразбиране?

DateFormatter използва Calendar.current — системния календар на потребителя. Ако приложението трябва винаги да показва дати в григорианския календар независимо от настройките, задайте formatter.calendar = Calendar(identifier: .gregorian). Това гарантира еднообразно показване за всички потребители.

Как да проверя чрез Calendar дали годината е високосна?

Calendar.range(of: .day, in: .year, for: date) връща 365 или 366 дни. По-просто: Calendar.date(from: DateComponents(year: година, month: 2, day: 29)) != nil — ако 29 февруари съществува, годината е високосна. Calendar сам отчита правилата за конкретната календарна система.

Може ли firstWeekday да се промени след създаване на Calendar?

Да, свойството firstWeekday е достъпно за запис. Промяната влияе на weekOfMonth, weekOfYear и всички изчисления, свързани с номерата на седмиците. При задаване на locale = Locale(identifier: "bg_BG") firstWeekday автоматично става 2 (понеделник). Ръчната настройка замества стойността от locale.

Заключение

  • Calendar — клас Foundation за календарни изчисления, който свързва Date с четими за човека компоненти чрез DateComponents.
  • 17 календарни системи се поддържат от Foundation — от григорианска до будистка и японска, с автоматично отчитане на регионални правила.
  • Calendar.current съответства на системния календар на потребителя — използвайте го за UI и DateFormatter.
  • Calendar.dateInterval и Calendar.range — ключови методи за работа с граници на периоди и диапазони на компоненти.
  • Calendar.date(byAdding:) коректно обработва различната дължина на месеците и високосните години — не го заменяйте с аритметика на TimeInterval.
  • TimeZone и Locale като свойства на Calendar влияят на всички календарни изчисления — задавайте ги изрично за предвидимо поведение.
  • firstWeekday се определя от локала и влияе на номерата на седмиците — за глобални приложения го задайте изрично или използвайте Calendar.current.

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също