Calendarは、カレンダーシステムを定義し、日付コンポーネントの抽出、日付間の差の計算、期間の境界の検索、日付のシフトなどのカレンダー計算のためのメソッドを提供するFoundationクラスです。カレンダーは絶対時間(Date)を人間が読み取れるコンポーネントに結び付け、週の始まり、タイムゾーン、夏時間などの地域特性を考慮します。Apple Developer Documentation(2025)によると、Foundationはグレゴリオ暦から仏暦、和暦まで17のカレンダーシステムをサポートしており、Calendarは国際化されたアプリケーションのための汎用ツールとなっています。
重要なポイント
Calendarは、ICU(International Components for Unicode)に基づくカレンダー計算を実装するFoundationクラスです。カレンダーは、絶対時間(Date)が年、月、日、時、分、秒などのカレンダーコンポーネントにどのようにマッピングされるかを定義します。Calendarなしでは、現在の年、月、日を知ることは不可能です。Date自体にはこの情報が含まれていません。
カレンダーは3つのパラメータグループを考慮します:カレンダーシステム(グレゴリオ暦、仏暦、和暦)、タイムゾーン、およびロケールです。Calendar.currentは、ユーザーのシステム設定からこれら3つを組み合わせます。Calendar.autoupdatingCurrentは、NotificationCenterを介してアプリケーションを再起動せずに設定変更時に自動的に更新される特別なバージョンです。
CalendarはFoundationにおける値型(value type)です。Calendar(identifier:)は固定パラメータで新しいインスタンスを作成します。Calendarはコピー可能で、==で比較可能で、辞書のキーとして使用できます。これにより、テスト用に特定のtimeZoneとlocale設定を持つカレンダーを作成できます。
CalendarはObjective-CのNSCalendarのSwift版であり、as Calendar / as NSCalendarを介してブリッジされています。現代のSwiftではCalendarが至る所で使用されています。NSCalendarはObjective-C APIとの後方互換性のために残っています。CalendarにはNSプレフィックスなしの完全なメソッドセットがあり、type-safeな引数とSwiftのオプショナルを備えています。
スレッドセーフティ — Calendarは読み取りに関してスレッドセーフです。作成されたインスタンスは複数のスレッドから安全に読み取れます。プロパティ(timeZone、locale)の変更はスレッドセーフではありません — 異なる設定ごとに別々のCalendarインスタンスを作成してください。
FoundationはCalendar.Identifier列挙型を介して17のカレンダーシステムをサポートしています。各システムには、うるう年、月数、時代の始まりに関する独自のルールがあります。カレンダーの選択は、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:)はDateComponentsからDateを組み立て、欠落フィールドをデフォルト値(日=1、時=0、分=0、秒=0)で埋めます。
Calendar.componentメソッドは単一のコンポーネントを抽出し、簡単な確認に便利です。Calendar.dateComponentsは1回の呼び出しで複数のコンポーネントを抽出します — Calendarが各コンポーネントごとに個別に計算するのではなく、一度だけカレンダー計算を行うため、より効率的です。3つ以上のコンポーネントのリストには常にdateComponentsを使用してください。
Calendar.compareは、指定された精度で2つのDateを比較します。toGranularityパラメータはコンポーネントの精度を決定します:.yearは年のみ、.monthは年と月、.dayは年、月、日を比較します。時間を無視して2つの日付が同じ日であるかを確認するのに便利です。
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:)は、カレンダーのタイムゾーンを考慮して2つの日付が同じ暦日にあるかをチェックします。これらのメソッドは内部でCalendar.compareを使用し、頻繁な呼び出しに最適化されています。
Calendar.dateIntervalは、分析やUIにとって最も有用なメソッドの1つです。指定されたコンポーネント(日、週、月、年)の開始と終了を表すDateIntervalを返します。DateIntervalにはstart(Date)とend(Date) — 期間の境界が含まれます。例えば、dateInterval(of: .weekOfYear, for: Date())は現在の週の月曜日の開始と日曜日の終了を返します。
Calendar.dateとbyAdding — 日付をシフトするメソッドです。Calendar.date(byAdding: .day, value: 7, to: Date())は1週間後の日付を返します。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を返すまで各一致に対してブロックを呼び出します。カレンダーやスケジュールで繰り返しイベントを生成するために使用されます。このメソッドはICUによって最適化されているため、nextDateを使った手動ループよりも効率的です。
TimeZoneはCalendarの不可欠な部分です。タイムゾーンは、絶対的なDateがどのカレンダー時間に対応するかを決定します。UTCとモスクワで同じDateは異なるコンポーネントを生成します:UTCのDate()は10:00を表示する一方、MSKでは13:00を表示します。Calendar.timeZoneのデフォルトはTimeZone.currentです。
Localeは、週の最初の日、年の最初の週の最小日数(minDaysInFirstWeek)、および月/曜日の名前(DateFormatterを介した変換時)に影響します。Calendar.localeのデフォルトはLocale.currentです。ロシアのロケールでは週は月曜日から始まり、アメリカのロケールでは日曜日から始まります。
Calendar.availableIdentifiersは、サポートされているすべてのカレンダー識別子のリストを返します。静的プロパティCalendar.availableCalendarIdentifiersは、同じ識別子を持つ文字列の配列です。カレンダー選択UIの構築や、デバイス上の特定のカレンダーシステムの可用性を確認するために使用されます。
// 特定のタイムゾーンの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がこの間隔内にあるかを確認することは、日付が現在の月に属するかを判断する最速の方法です。別の方法としては、.monthの精度でCalendar.compareを使用する方法があります:結果が.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)は31日ある月では1..<32を、うるう年でない2月では1..<29を返します。ハードコードされた値を使う代わりに、月の日数を取得する正しい方法です。
月の加算はCalendar.date(byAdding:value:to:)を介してエッジケースを正しく処理します。1月31日に1ヶ月を追加すると、Calendarは2月28日(うるう年なら29日)を返します。TimeIntervalを介して単純に30日を追加した場合の3月3日ではありません。これもカレンダー計算にTimeIntervalを使用すべきでない理由の1つです。
| Calendarメソッド | 目的 | 例 |
|---|---|---|
| dateInterval | 期間の境界 | 月の開始と終了 |
| range(of:in:for:) | コンポーネントの範囲 | 現在の月の日数 |
| date(byAdding:) | 日付のシフト | 今日から+1ヶ月 |
| isDateInToday | 今日の確認 | 日付は今日か? |
| compare(toGranularity:) | 精度付き比較 | 時間を無視した同じ日 |
よくある質問
Calendar.currentはユーザーのシステム設定からカレンダーを返します — グレゴリオ暦ではない可能性があります(例:タイの仏暦)。Calendar(identifier: .gregorian)は設定に関係なく常にグレゴリオ暦を作成します。日付の表示にはCalendar.currentを、ビジネスロジックには明示的に選択した識別子を使用してください。
これは月の長さが異なるためです。現在の日付が1月31日の場合、1ヶ月追加すると2月28日になります。2月には31日がないからです。Calendarは自動的に日付をその月の最後の有効な日に調整します。正確に制御するには、月の最初の日に移動するためにday: 1を指定したDateComponentsを使用してください。
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 — 2月29日が存在すれば、その年はうるう年です。Calendarは特定のカレンダーシステムのルールを自動的に処理します。
はい、firstWeekdayプロパティは書き込み可能です。変更はweekOfMonth、weekOfYear、および週番号に関連するすべての計算に影響します。locale = Locale(identifier: “ru_RU”)を設定すると、firstWeekdayは自動的に2(月曜日)になります。手動設定はロケールの値を上書きします。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。