Calendar هو كلاس في Foundation يحدد نظام التقويم ويوفر طرقاً للحسابات التقويمية: استخراج مكونات التاريخ، حساب الفرق بين التواريخ، إيجاد حدود الفترات وإزاحة التواريخ. يربط التقويم الوقت المطلق (Date) بمكونات قابلة للقراءة البشرية ويراعي الخصائص الإقليمية: بداية الأسبوع، المنطقة الزمنية والتوقيت الصيفي. وفقاً لوثائق مطوري Apple (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 من NSCalendar في Objective-C، مع جسر عبر as Calendar / as NSCalendar. في Swift الحديث يُستخدم Calendar في كل مكان. يبقى NSCalendar للتوافق العكسي مع APIs Objective-C. لدى 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 يقارن تاريخين بدقة محددة. تحدد المعلمة 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
)!
// إزاحة شهر واحد
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 يناير، يعيد 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 يناير، فإن إضافة شهر واحد تعطي 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 (الاثنين). التعيين اليدوي يتجاوز القيمة من الإعدادات الإقليمية.
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا