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:) создаёт новый экземпляр с фиксированными параметрами. Calendar можно копировать, сравнивать через == и использовать как ключ в словаре. Это позволяет создавать календари с конкретными настройками 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 потокобезопасен для чтения. Созданный экземпляр можно безопасно читать из нескольких потоков. Модификация свойств (timeZone, locale) не потокобезопасна — для разных конфигураций создавайте разные экземпляры 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()
// Izvlechenie odnogo komponenta
let year = calendar.component(.year, from: now)
// Izvlechenie nabora komponentov
let comps = calendar.dateComponents(
[.year, .month, .day], from: now
)
// Compare with day granularity
let isSameDay = calendar.compare(date1, to: date2,
toGranularity: .day) == .orderedSame
// Check if date is today
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()
// Nachalo i konets nedeli
let weekInterval = calendar.dateInterval(
of: .weekOfYear, for: today
)!
// Sdvig na 1 mesyats
let nextMonth = calendar.date(
byAdding: .month, value: 1, to: today
)!
// Sdvig cherez DateComponents
var delta = DateComponents()
delta.month = 1
delta.day = 3
let shifted = calendar.date(byAdding: delta, to: today)!
// Sleduyushchaya pyatnitsa 13-go
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 выбора календаря и для проверки доступности конкретной календарной системы на устройстве.
// Calendar with specific time zone
var utcCalendar = Calendar(identifier: .gregorian)
utcCalendar.timeZone = TimeZone(identifier: "UTC")!
// Calendar with Russian locale
var russianCalendar = Calendar(identifier: .gregorian)
russianCalendar.locale = Locale(identifier: "ru_RU")
// First weekday depends on locale
let firstWeekday = russianCalendar.firstWeekday
// 2 = Monday (in ru_RU)
// Spisok dostupnykh kalendarey
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)
}
// Kolichestvo dney v mesyatse
func daysInMonth(for date: Date) -> Int {
let calendar = Calendar.current
return calendar.range(
of: .day, in: .month, for: date
)?.count ?? 0
}
// Dobavlenie mesyatsev s korrektnym zaversheniem
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: year, month: 2, day: 29)) != nil — если 29 февраля существует, год високосный. Calendar сам учитывает правила для конкретной календарной системы.
Да, свойство firstWeekday доступно для записи. Изменение влияет на weekOfMonth, weekOfYear и все расчёты, связанные с номерами недель. При установке locale = Locale(identifier: "ru_RU") firstWeekday автоматически становится 2 (понедельник). Ручная установка переопределяет значение из locale.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также