Calendar is a Foundation class that defines a calendar system and provides methods for calendar calculations: extracting date components, calculating differences between dates, finding period boundaries, and shifting dates. The Calendar connects absolute time (Date) with human-readable components and takes regional features into account: the start of the week, time zone, and daylight saving time. According to Apple Developer Documentation (2025), Foundation supports 17 calendar systems — from Gregorian to Buddhist and Japanese — making Calendar a universal tool for internationalized applications.
Key Takeaways
Calendar is a Foundation class that implements calendar calculations based on ICU (International Components for Unicode). The Calendar defines how absolute time (Date) maps to calendar components: year, month, day, hour, minute, second. Without Calendar it is impossible to determine what year, month, and day it is — Date itself does not contain this information.
The Calendar takes three groups of parameters into account: the calendar system (Gregorian, Buddhist, Japanese), the time zone, and the locale. Calendar.current combines all three from the user’s system settings. Calendar.autoupdatingCurrent is a special version that automatically updates when settings change without restarting the app via NotificationCenter.
The Calendar is a value type in Foundation. Calendar(identifier:) creates a new instance with fixed parameters. Calendar can be copied, compared via ==, and used as a dictionary key. This allows creating calendars with specific timeZone and locale settings for testing.
Calendar is the Swift version of Objective-C NSCalendar, bridged via as Calendar / as NSCalendar. In modern Swift, Calendar is used everywhere. NSCalendar remains for backward compatibility with Objective-C APIs. Calendar has a complete set of methods without the NS prefix, with type-safe arguments and Swift optionals.
Thread Safety — Calendar is thread-safe for reading. A created instance can be safely read from multiple threads. Modifying properties (timeZone, locale) is not thread-safe — create separate Calendar instances for different configurations.
Foundation supports 17 calendar systems via the Calendar.Identifier enumeration. Each system has its own rules for leap years, number of months, and the start of the era. The choice of calendar affects all calculations: dateComponents, dateInterval, nextDate.
Main calendar systems:
Calendar(identifier: .gregorian) — the most commonly used. It conforms to the international standard ISO 8601 and is the default calendar in most countries. For applications with an international audience, use Calendar.current — it automatically matches the user’s system calendar.
| Identifier | Type | Region of Use |
|---|---|---|
| .gregorian | Solar | International |
| .buddhist | Solar | Thailand, Cambodia |
| .japanese | Solar | Japan |
| .hebrew | Lunisolar | Israel |
| .islamic | Lunar | Islamic countries |
| .chinese | Lunisolar | China |
DateComponents and Calendar are an inseparable pair. Calendar.dateComponents(_:from:) extracts components from Date respecting the calendar’s time zone. Calendar.date(from:) assembles a Date from DateComponents, filling missing fields with default values: day = 1, hour = 0, minute = 0, second = 0.
The Calendar.component method extracts a single component, convenient for quick checks. Calendar.dateComponents extracts multiple components in one call — this is more performant since Calendar performs calendar calculations once rather than for each component separately. For a list of 3+ components, always use dateComponents.
Calendar.compare compares two Dates with a specified granularity. The toGranularity parameter determines the component precision: .year compares only the year, .month — year and month, .day — year, month, day. This is useful for checking whether two dates fall on the same day, ignoring time.
let calendar = Calendar.current
let now = Date()
// Extracting a single component
let year = calendar.component(.year, from: now)
// Extracting a set of components
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 — methods for relative checks. Calendar.isDate(_:inSameDayAs:) checks whether two dates fall on the same calendar day considering the calendar’s time zone. These methods use Calendar.compare internally and are optimized for frequent calls.
Calendar.dateInterval is one of the most useful methods for analytics and UI. It returns a DateInterval for the specified component: the start and end of a day, week, month, year. DateInterval contains start (Date) and end (Date) — the period boundaries. For example, dateInterval(of: .weekOfYear, for: Date()) returns the start of Monday and the end of Sunday of the current week.
Calendar.date with byAdding — a method for shifting dates. Calendar.date(byAdding: .day, value: 7, to: Date()) returns the date one week later. Calendar.date(byAdding: DateComponents) is a more flexible version that allows shifting multiple components at once: +1 month +3 days. Calendar automatically accounts for varying month lengths and leap years.
Calendar.nextDate searches for the next date matching the specified DateComponents. The matchingPolicy parameter defines the behavior on mismatch: .nextTime — the next matching time, .nextTimePreservingSmallerComponents — preserves minutes and seconds from the original date, .strict — requires an exact match.
let calendar = Calendar.current
let today = Date()
// Start and end of the week
let weekInterval = calendar.dateInterval(
of: .weekOfYear, for: today
)!
// Shift by 1 month
let nextMonth = calendar.date(
byAdding: .month, value: 1, to: today
)!
// Shift via DateComponents
var delta = DateComponents()
delta.month = 1
delta.day = 3
let shifted = calendar.date(byAdding: delta, to: today)!
// Next Friday the 13th
let friday13Components = DateComponents(
weekday: 6, day: 13
)
let nextFriday13 = calendar.nextDate(
after: today, matching: friday13Components,
matchingPolicy: .nextTime
)
EnumerateDates — a powerful method for iterating over dates by pattern. Calendar.enumerateDates(startingAfter:matching:matchingPolicy:using:) calls a block for each match until the block returns stop = true. Used for generating recurring events in calendars and schedules. This method is more efficient than a manual loop with nextDate, as it is optimized by ICU.
TimeZone is an integral part of Calendar. The time zone determines which calendar time corresponds to an absolute Date. The same Date in UTC and in Moscow yields different components: Date() in UTC might show 10:00, while in MSK — 13:00. Calendar.timeZone defaults to TimeZone.current.
Locale affects the first day of the week, the minimum number of days in the first week of the year (minDaysInFirstWeek), and month/weekday names (when converting via DateFormatter). Calendar.locale defaults to Locale.current. In the Russian locale the week starts on Monday, in the American locale — on Sunday.
Calendar.availableIdentifiers returns a list of all supported calendar identifiers. The static property Calendar.availableCalendarIdentifiers is an array of strings with the same identifiers. Used for building a calendar selection UI and for checking availability of a specific calendar system on the device.
// 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)
// List of available calendars
for identifier in Calendar.availableIdentifiers {
print(identifier)
}
firstWeekday — a Calendar property that determines which day of the week is considered the first. In the Russian locale Sunday = 2 (Monday is first). In the American locale Sunday = 1. This affects weekOfMonth and weekOfYear: the same date can belong to different week numbers in different locales. For applications dealing with dates, use Calendar.current or explicitly set firstWeekday.
Let us consider practical scenarios demonstrating the capabilities of Calendar. Each example solves a specific iOS development task and shows the correct way to use calendar calculations.
Calendar.dateInterval(of: .month, for:) returns the boundaries of the current month. Checking whether a Date falls within this interval is the fastest way to determine if a date belongs to the current month. An alternative approach is Calendar.compare with granularity .month: if the result is .orderedSame, the month matches.
func isInCurrentMonth(_ date: Date) -> Bool {
let calendar = Calendar.current
let monthInterval = calendar.dateInterval(
of: .month, for: Date()
)!
return monthInterval.contains(date)
}
// Number of days in a month
func daysInMonth(for date: Date) -> Int {
let calendar = Calendar.current
return calendar.range(
of: .day, in: .month, for: date
)?.count ?? 0
}
// Adding months with correct wrapping
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:) returns the range of valid values for a specified component in the context of another component. For example, range(of: .day, in: .month, for: date) returns 1..<32 for months with 31 days or 1..<29 for February in a non-leap year. This is the correct way to determine the number of days in a month, rather than using hardcoded values.
Adding months via Calendar.date(byAdding:value:to:) correctly handles edge dates. If you add 1 month to January 31, Calendar returns February 28 (or 29 in a leap year), rather than March 3, which would result from simply adding 30 days via TimeInterval. This is another reason not to use TimeInterval for calendar calculations.
| Calendar Method | Purpose | Example |
|---|---|---|
| dateInterval | Period boundaries | Start and end of a month |
| range(of:in:for:) | Component range | Days in the current month |
| date(byAdding:) | Date shifting | +1 month from today |
| isDateInToday | Check for today | Does the date belong to today |
| compare(toGranularity:) | Comparison with precision | Same day ignoring time |
Frequently Asked Questions
Calendar.current returns the calendar from the user’s system settings — it may not be Gregorian (e.g., Buddhist in Thailand). Calendar(identifier: .gregorian) always creates a Gregorian calendar regardless of settings. Use Calendar.current for displaying dates, and an explicitly chosen identifier for business logic.
This is due to varying month lengths. If the current date is January 31, adding 1 month yields February 28, since February has no 31st day. Calendar automatically caps the date to the last valid day of the month. For precise control, use DateComponents with day: 1 to move to the first day of the month.
DateFormatter uses Calendar.current — the user’s system calendar. If an application should always display dates in the Gregorian calendar regardless of settings, set formatter.calendar = Calendar(identifier: .gregorian). This ensures uniform display for all users.
Calendar.range(of: .day, in: .year, for: date) returns 365 or 366 days. Simpler: Calendar.date(from: DateComponents(year: year, month: 2, day: 29)) != nil — if February 29 exists, the year is a leap year. Calendar automatically handles the rules for the specific calendar system.
Yes, the firstWeekday property is writable. Changing it affects weekOfMonth, weekOfYear, and all calculations related to week numbers. When setting locale = Locale(identifier: “ru_RU”), firstWeekday automatically becomes 2 (Monday). Manual setting overrides the value from locale.
Summary
We will develop a mobile application turnkey
IT Sectr creates iOS and Android applications for startups and businesses since 2017. We will advise you and propose the best solution.
Read also