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 بهروزرسانی میشود.
تقویم در Foundation یک نوع مقداری (value type) است. Calendar(identifier:) یک نمونه جدید با پارامترهای ثابت ایجاد میکند. تقویم قابل کپی، مقایسه با == و استفاده به عنوان کلید در دیکشنری است. این امکان ایجاد تقویمهایی با تنظیمات خاص timeZone و locale را برای تست فراهم میکند.
Calendar — نسخه Swift از NSCalendar Objective-C است، با پل as Calendar / as NSCalendar. در Swift مدرن در همه جا از Calendar استفاده میشود. NSCalendar برای سازگاری معکوس با API Objective-C باقی مانده است. Calendar مجموعه کاملی از روشها بدون پیشوند NS، با آرگومانهای type-safe و اختیاری Swift دارد.
Thread Safety — Calendar برای خواندن thread-safe است. نمونه ایجاد شده را میتوان از چندین thread به طور امن خواند. تغییر ویژگیها (timeZone, locale) thread-safe نیست — برای پیکربندیهای مختلف نمونههای جداگانه Calendar ایجاد کنید.
Foundation از 17 سیستم تقویمی از طریق enum 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 — یکی از مفیدترین متدها برای تحلیل و 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 — نیاز به تطابق دقیق دارد.
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 لیست تمام شناسههای تقویمی پشتیبانی شده را برمیگرداند. static property Calendar.availableCalendarIdentifiers — آرایهای از رشتهها با همان شناسهها. برای ساختن UI انتخاب تقویم و بررسی در دسترس بودن یک سیستم تقویمی خاص روی دستگاه استفاده میشود.
// تقویم با منطقه زمانی خاص
var utcCalendar = Calendar(identifier: .gregorian)
utcCalendar.timeZone = TimeZone(identifier: "UTC")!
// تقویم با محل روسی
var russianCalendar = Calendar(identifier: .gregorian)
russianCalendar.locale = Locale(identifier: "ru_RU")
// اولین روز هفته به محل بستگی دارد
let firstWeekday = russianCalendar.firstWeekday
// 2 = دوشنبه (در fa_IR)
// لیست تقویمهای موجود
for identifier in Calendar.availableIdentifiers {
print(identifier)
}
firstWeekday — ویژگی Calendar که تعیین میکند کدام روز هفته اولین محسوب میشود. در محل فارسی Sunday = 7 (شنبه اولین). در آمریکایی Sunday = 1. این بر عملکرد weekOfMonth و weekOfYear تأثیر میگذارد: تاریخ یکسان ممکن است در محلهای مختلف به شماره هفتههای متفاوتی تعلق داشته باشد. برای برنامههای دارای تاریخ از Calendar.current استفاده کنید یا firstWeekday را به صراحت تنظیم کنید.
سناریوهای عملی را بررسی میکنیم که قابلیتهای Calendar را نشان میدهد. هر مثال یک وظیفه خاص توسعه iOS را حل میکند و روش صحیح استفاده از محاسبات تقویمی را نشان میدهد.
Calendar.dateInterval(of: .month, for:) مرزهای ماه جاری را برمیگرداند. بررسی قرارگیری Date در این بازه — سریعترین راه برای تعیین تعلق تاریخ به ماه جاری. روش جایگزین — Calendar.compare با granularity .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) برای ماههای 31 روزه 1..<32 و برای فوریه سال غیرکبیسه 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: سال, month: 2, day: 29)) != nil — اگر 29 فوریه وجود داشته باشد، سال کبیسه است. Calendar خود قوانین سیستم تقویمی خاص را در نظر میگیرد.
بله، ویژگی firstWeekday قابل نوشتن است. تغییر بر weekOfMonth، weekOfYear و تمام محاسبات مربوط به شماره هفتهها تأثیر میگذارد. هنگام تنظیم locale = Locale(identifier: "fa_IR") firstWeekday به طور خودکار 7 (شنبه) میشود. تنظیم دستی مقدار locale را لغو میکند.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید