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.
Calendar — це тип значення (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-опціональністю.
Потокобезпека — 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()
// Вилучення одного компонента
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 — один з найкорисніших методів для аналітики та інтерфейсу користувача. Він повертає 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 повертає список усіх підтримуваних ідентифікаторів календаря. Статична властивість Calendar.availableCalendarIdentifiers — масив рядків з тими самими ідентифікаторами. Використовується для побудови інтерфейсу вибору календаря та перевірки доступності певної календарної системи на пристрої.
// Calendar з певним часовим поясом
var utcCalendar = Calendar(identifier: .gregorian)
utcCalendar.timeZone = TimeZone(identifier: "UTC")!
// Calendar з російською локаллю
var russianCalendar = Calendar(identifier: .gregorian)
russianCalendar.locale = Locale(identifier: "ru_RU")
// Перший робочий день залежить від локалі
let firstWeekday = russianCalendar.firstWeekday
// 2 = понеділок (в ru_RU)
// Список доступних календарів
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 з гранулярністю .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: year, month: 2, day: 29)) != nil — якщо 29 лютого існує, рік високосний. Calendar сам враховує правила для конкретної календарної системи.
Так, властивість firstWeekday доступна для запису. Зміна впливає на weekOfMonth, weekOfYear та всі розрахунки, пов’язані з номерами тижнів. При встановленні locale = Locale(identifier: “ru_RU”) firstWeekday автоматично стає 2 (понеділок). Ручне встановлення перевизначає значення з locale.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.
Читайте також