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 с учётом timezone.
  • Calendar.dateInterval возвращает начальную и конечную дату указанного периода (день, неделя, месяц).

Что такое Calendar в Foundation?

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

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

Календарь — ценностный объект (value type) в Foundation. Calendar(identifier:) создаёт новый экземпляр с фиксированными параметрами. Calendar можно копировать, сравнивать через == и использовать как ключ в словаре. Это позволяет создавать календари с конкретными настройками 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 потокобезопасен для чтения. Созданный экземпляр можно безопасно читать из нескольких потоков. Модификация свойств (timeZone, locale) не потокобезопасна — для разных конфигураций создавайте разные экземпляры 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()

// 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 вычисления

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()

// 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 и 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
// 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

Рассмотрим практические сценарии, демонстрирующие возможности 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)
}

// 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 возвращает календарь из системных настроек пользователя — он может быть не григорианским (например, буддийским в Таиланде). 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: year, month: 2, day: 29)) != nil — если 29 февраля существует, год високосный. Calendar сам учитывает правила для конкретной календарной системы.

Можно ли изменить firstWeekday после создания Calendar?

Да, свойство firstWeekday доступно для записи. Изменение влияет на weekOfMonth, weekOfYear и все расчёты, связанные с номерами недель. При установке locale = Locale(identifier: "ru_RU") 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 года. Мы проконсультируем вас и предложим наилучшее решение.

Обсудить проект

Читайте также