Calendar — ما هو، تقويم Date والحسابات في Swift

المؤلف: IT Sectr نُشر: 2026-07-12 وقت القراءة: 7 دق

Calendar هو كلاس في Foundation يحدد نظام التقويم ويوفر طرقاً للحسابات التقويمية: استخراج مكونات التاريخ، حساب الفرق بين التواريخ، إيجاد حدود الفترات وإزاحة التواريخ. يربط التقويم الوقت المطلق (Date) بمكونات قابلة للقراءة البشرية ويراعي الخصائص الإقليمية: بداية الأسبوع، المنطقة الزمنية والتوقيت الصيفي. وفقاً لوثائق مطوري Apple (2025)، يدعم Foundation 17 نظاماً تقويمياً — من الميلادي إلى البوذي والياباني، مما يجعل Calendar أداة عالمية للتطبيقات الدولية.

الرئيسية

  • Calendar — كلاس في Foundation للحسابات التقويمية: استخراج المكونات والمقارنة وإزاحة التواريخ.
  • Calendar.current — تقويم نظام المستخدم، الذي يدمج تلقائياً الإعدادات الإقليمية.
  • 17 نظاماً تقويمياً — يدعم Foundation التقويم الميلادي والبوذي والياباني والعبراني والإسلامي وغيرها.
  • Calendar.dateComponents يستخرج المكونات (السنة، الشهر، اليوم) من Date مع مراعاة المنطقة الزمنية.
  • Calendar.dateInterval يعيد تاريخ البداية والنهاية لفترة محددة (يوم، أسبوع، شهر).

ما هو Calendar في Foundation؟

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 و NSCalendar

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

يدعم Foundation 17 نظاماً تقويمياً عبر تعداد Calendar.Identifier. كل نظام له قواعده الخاصة للسنوات الكبيسة وعدد الأشهر وبداية الحقبة. يؤثر اختيار التقويم على جميع الحسابات: dateComponents، dateInterval، nextDate.

أنظمة التقويم الرئيسية:

  • .gregorian — المعيار الدولي، 12 شهراً، 365/366 يوماً، الحقبة من ميلاد المسيح.
  • .buddhist — التقويم البوذي، يُستخدم في تايلاند وكمبوديا ولاوس، الحقبة تسبق الميلادية بـ 543 سنة.
  • .japanese — التقويم الياباني حسب عصور حكم الأباطرة، 12 شهراً مثل الميلادي.
  • .hebrew — التقويم العبراني، 12/13 شهراً، الشهر الكبيس آدار الثاني.
  • .islamic — التقويم الإسلامي، 12 شهراً قمرياً، 354/355 يوماً.
  • .indian — التقويم الوطني الهندي (ساكا)، 12 شهراً.

Calendar(identifier: .gregorian) — الأكثر استخداماً. يتوافق مع المعيار الدولي ISO 8601 وهو التقويم الافتراضي في معظم البلدان. للتطبيقات ذات الجمهور الدولي، استخدم Calendar.current — فهو يطابق تلقائياً تقويم نظام المستخدم.

المعرفالنوعمنطقة الاستخدام
.gregorianشمسيدولي
.buddhistشمسيتايلاند، كمبوديا
.japaneseشمسياليابان
.hebrewقمري شمسيإسرائيل
.islamicقمريالدول الإسلامية
.chineseقمري شمسيالصين

Calendar و DateComponents

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 — السنة والشهر واليوم. هذا مفيد للتحقق مما إذا كان تاريخان يقعان في نفس اليوم، بغض النظر عن الوقت.

swift
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

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 — يتطلب تطابقاً تاماً.

swift
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 و Locale

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 هي مصفوفة سلاسل بنفس المعرفات. تُستخدم لبناء واجهة اختيار التقويم وللتحقق من توفر نظام تقويمي معين على الجهاز.

swift
// 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

لننظر في سيناريوهات عملية توضح إمكانيات Calendar. كل مثال يحل مهمة محددة في تطوير iOS ويظهر الطريقة الصحيحة لاستخدام الحسابات التقويمية.

تحقق: هل التاريخ في هذا الشهر؟

Calendar.dateInterval(of: .month, for:) يعيد حدود الشهر الحالي. التحقق مما إذا كان Date يقع ضمن هذه الفترة هو أسرع طريقة لتحديد ما إذا كان التاريخ ينتمي إلى الشهر الحالي. بديل آخر هو Calendar.compare بدقة .month: إذا كانت النتيجة .orderedSame، فإن الشهر متطابق.

swift
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 يعيد التقويم من إعدادات نظام المستخدم — قد لا يكون ميلادياً (مثلاً بوذياً في تايلاند). Calendar(identifier: .gregorian) ينشئ دائماً تقويماً ميلادياً بغض النظر عن الإعدادات. استخدم Calendar.current لعرض التواريخ، ومعرفاً محدداً صراحةً لمنطق الأعمال.

لماذا يعيد Calendar.date(byAdding: .month, value: 1) أحياناً نفس التاريخ؟

هذا بسبب اختلاف أطوال الأشهر. إذا كان التاريخ الحالي هو 31 يناير، فإن إضافة شهر واحد تعطي 28 فبراير، لأن فبراير لا يحتوي على 31 يوماً. يقوم Calendar تلقائياً بتعديل التاريخ إلى آخر يوم صالح في الشهر. للتحكم الدقيق، استخدم DateComponents مع day: 1 للانتقال إلى أول يوم من الشهر.

أي تقويم يستخدمه DateFormatter افتراضياً؟

DateFormatter يستخدم Calendar.current — تقويم نظام المستخدم. إذا كان يجب على التطبيق عرض التواريخ دائماً بالتقويم الميلادي بغض النظر عن الإعدادات، فعيّن formatter.calendar = Calendar(identifier: .gregorian). وهذا يضمن عرضاً موحداً لجميع المستخدمين.

كيف تتحقق مما إذا كانت السنة كبيسة عبر Calendar؟

Calendar.range(of: .day, in: .year, for: date) يعيد 365 أو 366 يوماً. أبسط: Calendar.date(from: DateComponents(year: year, month: 2, day: 29)) != nil — إذا كان 29 فبراير موجوداً، فإن السنة كبيسة. Calendar يتعامل تلقائياً مع قواعد نظام التقويم المحدد.

هل يمكن تغيير firstWeekday بعد إنشاء Calendar؟

نعم، خاصية firstWeekday قابلة للكتابة. يؤثر التغيير على weekOfMonth و weekOfYear وجميع الحسابات المتعلقة بأرقام الأسابيع. عند تعيين locale = Locale(identifier: “ru_RU”)، يصبح firstWeekday تلقائياً 2 (الاثنين). التعيين اليدوي يتجاوز القيمة من الإعدادات الإقليمية.

الخلاصة

  • Calendar — كلاس في Foundation للحسابات التقويمية، يربط Date بمكونات قابلة للقراءة البشرية عبر DateComponents.
  • 17 نظاماً تقويمياً يدعمها Foundation — من الميلادي إلى البوذي والياباني، مع معالجة تلقائية للقواعد الإقليمية.
  • Calendar.current يطابق تقويم نظام المستخدم — استخدمه لواجهة المستخدم و DateFormatter.
  • Calendar.dateInterval و Calendar.range طريقتان أساسيتان للعمل مع حدود الفترات ونطاقات المكونات.
  • Calendar.date(byAdding:) يعالج بشكل صحيح أطوال الأشهر المختلفة والسنوات الكبيسة — لا تستبدله بحسابات TimeInterval.
  • TimeZone و Locale كخصائص لـ Calendar تؤثر على جميع الحسابات التقويمية — عيّنها صراحةً للحصول على سلوك متوقع.
  • firstWeekday يُحدد بواسطة الإعدادات الإقليمية ويؤثر على أرقام الأسابيع — للتطبيقات العالمية عيّنه صراحةً أو استخدم Calendar.current.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا