Calendar는 캘린더 시스템을 정의하고 날짜 구성 요소 추출, 날짜 간 차이 계산, 기간 경계 찾기 및 날짜 이동을 위한 메서드를 제공하는 Foundation 클래스입니다. 캘린더는 절대 시간(Date)을 사람이 읽을 수 있는 구성 요소와 연결하고 주의 시작, 시간대, 서머타임과 같은 지역별 특성을 고려합니다. Apple Developer Documentation(2025)에 따르면 Foundation은 그레고리력부터 불교력, 일본력까지 17개의 캘린더 시스템을 지원하여 Calendar를 국제화된 애플리케이션을 위한 범용 도구로 만듭니다.
주요 내용
Calendar는 ICU(International Components for Unicode)를 기반으로 캘린더 계산을 구현하는 Foundation 클래스입니다. 캘린더는 절대 시간(Date)이 년, 월, 일, 시, 분, 초와 같은 캘린더 구성 요소에 매핑되는 방식을 정의합니다. Calendar 없이는 현재 연도, 월, 일을 알 수 없습니다 — Date 자체에는 이 정보가 포함되어 있지 않습니다.
캘린더는 세 가지 매개변수 그룹을 고려합니다: 캘린더 시스템(그레고리력, 불교력, 일본력), 시간대 및 로케일입니다. Calendar.current는 사용자의 시스템 설정에서 이 세 가지를 결합합니다. 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는 한 번의 호출로 여러 구성 요소를 추출합니다 — 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은 분석 및 UI에 가장 유용한 메서드 중 하나입니다. 지정된 구성 요소(일, 주, 월, 년)에 대한 DateInterval을 반환합니다. DateInterval에는 start(Date)와 end(Date) — 기간의 경계가 포함됩니다. 예를 들어, dateInterval(of: .weekOfYear, for: Date())는 현재 주의 월요일 시작과 일요일 종료를 반환합니다.
Calendar.date with 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을 사용하지 말아야 하는 또 다른 이유입니다.
| 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 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.