DateFormatter هي فئة من Foundation مصممة للتحويل ثنائي الاتجاه بين كائنات Date وتمثيلها النصي. تراعي الفئة الإعدادات المحلية والمنطقة الزمنية والتقويم للمستخدم، مما يضمن عرضاً صحيحاً للتواريخ في أي منطقة من العالم. وفقاً لوثائق مطوري Apple (2025)، يدعم DateFormatter أربعة أنماط محددة مسبقاً للتاريخ والوقت، بالإضافة إلى تنسيقات مخصصة بالكامل من خلال سلسلة قالب. بدون DateFormatter، من المستحيل عرض التاريخ بشكل صحيح للمستخدم في تطبيق دولي.
الوجبات الرئيسية
DateFormatter هي فئة من إطار Foundation تنفذ التحويل ثنائي الاتجاه بين Date وسلسلة نصية. ظهرت لأول مرة في OpenStep باسم NSDateFormatter ومنذ ذلك الحين بقيت الأداة الرئيسية لتنسيق التواريخ عبر جميع منصات Apple. ترث الفئة من Formatter وتوفر واجهة برمجة تطبيقات مناسبة للعرض المترجم للتواريخ.
يعمل DateFormatter بناءً على أنماط Unicode LDML — نفس تلك المستخدمة في ICU (المكونات الدولية لـ Unicode). يتم تعيين النمط عبر خاصية dateFormat، حيث تتوافق الرموز y وM وd وH وm وs مع السنة والشهر واليوم والساعات والدقائق والثواني. يحدد تكرار الرمز التنسيق: "y" — سنة مكونة من رقمين، "yyyy" — سنة مكونة من أربعة أرقام.
إنشاء DateFormatter هي عملية مكلفة، حيث يتم تحميل بيانات الإعدادات المحلية والتقويم أثناء التهيئة. توصي Apple بإنشاء منسق مرة واحدة لكل نوع تنسيق وإعادة استخدامه. في SwiftUI وUIKit، غالباً ما يتم تخزين المنسقات في خصائص ثابتة أو يتم إنشاؤها بشكل كسول عند الوصول الأول.
DateFormatter يُستخدم في العديد من مكونات النظام في iOS. يستخدم UIDatePicker DateFormatter داخلياً لعرض التواريخ في وضع countDownTimer. يمكن لـ TextField مع منسق أن يتحقق تلقائياً من صحة التواريخ التي يدخلها المستخدم. يدعم Core Data سمات من نوع Date، ولكن تمثيلها النصي يتم دائماً عبر DateFormatter.
أمان الخيوط — DateFormatter ليس آمناً للخيوط. يؤدي تعديل خصائص المنسق من خيوط مختلفة إلى سلوك غير محدد. للاستخدام متعدد الخيوط، قم بإنشاء نسخ منفصلة من المنسق لكل خيط أو استخدم المزامنة عبر NSLock أو قائمة انتظار تسلسلية.
dateStyle وtimeStyle هما أبسط الطرق لتكوين عرض التاريخ. كل نمط له أربعة متغيرات: .short و.medium و.long و.full. يتيح الجمع بين dateStyle وtimeStyle تكويناً مستقلاً لتنسيق التاريخ والوقت، وتقوم الخاصية .none بتعطيل الجزء المقابل.
للإعدادات المحلية الأمريكية، يقوم .short بتنسيق التاريخ كـ "7/21/26"، وللروسية كـ "21.07.2026". النمط .long للإعدادات المحلية الروسية يخرج "21 يوليو 2026"، والنمط .full يخرج "الثلاثاء، 21 يوليو 2026" مع يوم الأسبوع. تتكيف جميع الأنماط الأربعة تلقائياً مع المعايير الإقليمية، بما في ذلك ترتيب المكونات والفواصل.
SFDateFormatter في iOS 15+ يوفر نهجاً بديلاً عبر RelativeDateFormatter وDateIntervalFormatter. يعرض RelativeDateFormatter "اليوم"، "أمس"، "بعد 3 أيام" للسياق الفوري. يعرض DateIntervalFormatter نطاقات التواريخ: "21–25 يوليو 2026" — للحجوزات والتخطيط.
| النمط | مثال (ru_RU) | مثال (en_US) |
|---|---|---|
| .short | 21.07.2026 | 7/21/26 |
| .medium | 21 يوليو 2026 | Jul 21, 2026 |
| .long | 21 يوليو 2026 | July 21, 2026 |
| .full | الثلاثاء، 21 يوليو 2026 | Tuesday, July 21, 2026 |
عند الجمع بين الأنماط، يختار DateFormatter تلقائياً الفاصل: لـ .short.date + .short.time قد تكون النتيجة "21.07.2026، 14:30". لـ .full.date + .full.time — "الثلاثاء، 21 يوليو 2026، 14:30:00 MSK". تتم إدارة الفاصل بواسطة الإعدادات المحلية وليس المطور — وهذا يضمن الامتثال لتوقعات المستخدم الإقليمية.
dateFormat يسمح بتعيين نمط تنسيق عشوائي باستخدام رموز مواصفات Unicode LDML. هذا يعطي تحكماً كاملاً في العرض: يمكن عرض السنة والشهر فقط، أو يوم الأسبوع بدون التاريخ، أو الوقت بدون ثوانٍ. التنسيق المخصص لا غنى عنه لمتطلبات التصميم المحددة.
الرموز الرئيسية — yyyy (السنة: 2026)، MM (الشهر: 07)، dd (اليوم: 21)، HH (الساعات: 14)، mm (الدقائق: 30)، ss (الثواني: 00). لاسم الشهر الكامل استخدم MMMM (يوليو)، للمختصر — MMM (يوليو). يوم الأسبوع — EEEE (الثلاثاء)، المختصر — E (الثلاثاء).
عند استخدام dateFormat، من المهم تعيين الإعدادات المحلية للمنسق. إذا لم يتم تعيين locale، يستخدم المنسق الإعدادات المحلية للنظام، وهو ما قد يكون غير مرغوب فيه لتنسيق ثابت في API. توصي Apple بتعيين locale = Locale(identifier: "en_US_POSIX") لتنسيق ثابت عبر المناطق، خاصة عند تحليل التواريخ من استجابات الخادم.
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ru_RU")
formatter.dateFormat = "d MMMM yyyy"
let customString = formatter.string(from: Date())
// "21 July 2026"
// تحليل سلسلة مخصصة
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let date = formatter.date(from: "2026-07-21 14:30:00")!
الخطأ في dateFormat هو أحد الأسباب الشائعة لتعطل التطبيق. إذا كان التنسيق لا يتطابق مع السلسلة، فإن طريقة date(from:) ترجع nil. استخدم guard let أو ?? للفك الآمن للقيم الاختيارية. للتحقق من صحة التنسيق، اختبره على جميع اللغات المدعومة — بعض رموز LDML تعمل بشكل مختلف في الإعدادات المحلية المختلفة.
Locale يحدد كيفية عرض أسماء الأشهر وأيام الأسبوع والفواصل المستخدمة. يستخدم DateFormatter Locale.current افتراضياً، ولكن في بعض السيناريوهات يلزم تحديد إعدادات محلية محددة: لتنسيق ثابت في السجلات استخدم en_US_POSIX، لتواريخ الخادم — الإعدادات المحلية المطابقة للخادم.
خاصية TimeZone تحدد المنطقة الزمنية للعرض. افتراضياً، يتم استخدام المنطقة الزمنية للنظام، ولكن للتطبيقات ذات الجمهور الدولي، غالباً ما يكون من الضروري عرض التواريخ في المنطقة الزمنية للمستخدم أو في UTC. يؤثر تغيير timeZone على العرض فقط — تبقى قيمة Date دون تغيير.
ميزة مهمة: إذا تم استخدام DateFormatter لتحليل سلسلة وكانت السلسلة تحتوي على إشارة إلى المنطقة الزمنية (على سبيل المثال، "2026-07-21T14:30:00Z" مع Z لـ UTC)، يتم تجاهل خاصية timeZone — يستخدم المنسق المنطقة الزمنية من السلسلة. إذا كانت المنطقة الزمنية غائبة عن السلسلة، يتم تطبيق timeZone الخاص بالمنسق.
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "ru_RU")
formatter.timeZone = TimeZone(identifier: "Europe/Moscow")
formatter.dateStyle = .long
formatter.timeStyle = .short
let moscowTime = formatter.string(from: Date())
// "21 July 2026, 14:30"
// تحليل بدون منطقة زمنية في السلسلة
formatter.timeZone = TimeZone(secondsFromGMT: 0)
formatter.dateFormat = "yyyy-MM-dd HH:mm"
let utcDate = formatter.date(from: "2026-07-21 10:30")!
AutoupdatingCurrentLocale — نوع خاص من الإعدادات المحلية يتم تحديثه تلقائياً عند تغيير إعدادات النظام للمستخدم. يدعمه DateFormatter افتراضياً. إذا كان التطبيق يعمل في الخلفية وقام المستخدم بتغيير لغة النظام، فإن المنسق الذي تم إنشاؤه قبل التغيير سيستمر في استخدام الإعدادات المحلية القديمة — لتحديثه، يلزم إنشاء نسخة جديدة.
ISO8601DateFormatter هو منسق متخصص للعمل مع التواريخ بتنسيق ISO 8601. هذا التنسيق هو المعيار الفعلي لواجهات برمجة تطبيقات REST وJSON وتبادل البيانات. يعمل ISO8601DateFormatter بشكل أسرع بكثير من DateFormatter لأنه لا يعتمد على الإعدادات المحلية ويستخدم قواعد تحليل ثابتة.
الخيارات الرئيسية للمنسق — .withInternetDateTime (2026-07-21T14:30:00Z)، .withFractionalSeconds (يضيف أجزاء الثانية)، .withTimeZone (يتضمن إزاحة المنطقة الزمنية). من خلال الجمع بين الخيارات، يمكن الحصول على أي متغير ISO 8601: بأجزاء الثانية، مع المنطقة الزمنية، بالتاريخ فقط.
JSONEncoder.DateEncodingStrategy يسمح بتكوين ترميز التواريخ عالمياً لجميع نماذج Codable. الخيارات — .iso8601 (يستخدم ISO8601DateFormatter)، .formatted(DateFormatter)، .millisecondsSince1970، .secondsSince1970. يؤثر اختيار الاستراتيجية على دورة حياة التسلسل بأكملها ويجب أن يكون متسقاً عبر جميع نقاط نهاية API.
// ISO8601DateFormatter
let isoFormatter = ISO8601DateFormatter()
isoFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let isoString = isoFormatter.string(from: Date())
// "2026-07-21T14:30:00.000Z"
// JSONEncoder مع ISO8601
let encoder = JSONEncoder()
encoder.dateEncodingStrategy = .iso8601
// بديل: JSONEncoder مع منسق مخصص
let customEncoder = JSONEncoder()
customEncoder.dateEncodingStrategy = .formatted(myFormatter)
DateFormatter مقابل ISO8601DateFormatter — اختر ISO8601DateFormatter لتسلسل وتحليل التواريخ في API، حيث أنه أسرع 5-10 مرات من DateFormatter وغير عرضة لأخطاء التدويل. استخدم DateFormatter لواجهة المستخدم حيث يكون العرض المترجم بأسماء الأشهر والأيام باللغة الأم للمستخدم مطلوباً.
لنلقِ نظرة على سيناريوهات حقيقية لاستخدام DateFormatter في تطبيق iOS: عرض التواريخ في قائمة الأخبار، وإدخال تاريخ الميلاد، وتصدير تقرير بتواريخ في مناطق زمنية مختلفة.
RelativeDateFormatter مثالي لخلاصات الأخبار. يعرض "الآن فقط"، "منذ 5 دقائق"، "أمس" للأخبار الجديدة ويتبدل إلى التاريخ الكامل للقديمة. يتم تكوين عتبة التبديل عبر calendar: للأخبار استخدم عتبة 24 ساعة، للمراسلة — أسبوع.
func formatRelativeDate(_ date: Date) -> String {
let relative = RelativeDateFormatter()
relative.unitsStyle = .full
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
let daysDiff = Calendar.current.dateComponents(
[.day], from: date, to: Date()
).day ?? 0
return daysDiff < 1
? relative.localizedString(for: date, relativeTo: Date())
: formatter.string(from: date)
}
إدخال تاريخ الميلاد — سيناريو شائع آخر. يتم تكوين DateFormatter باستخدام dateFormat محدد "dd.MM.yyyy" وlocale "ru_RU". عند تحليل السلسلة المدخلة، من المهم معالجة الأخطاء المحتملة: يعيد المنسق nil للسلسلة غير الصالحة. بعد التحليل الناجح، يتم التحقق من أن التاريخ ضمن نطاق مقبول — ليس قبل عام 1900، وليس بعد اليوم.
تصدير تقرير بتواريخ يتطلب تنسيقاً ثابتاً لا يعتمد على الإعدادات المحلية للمستخدم. استخدم dateFormat "yyyy-MM-dd HH:mm:ss" مع locale en_US_POSIX والمنطقة الزمنية UTC. يضمن هذا النهج فتح الملف بشكل صحيح في أي دولة بغض النظر عن الإعدادات الإقليمية للنظام.
| السيناريو | المنسق | الإعداد الرئيسي |
|---|---|---|
| خلاصة الأخبار | RelativeDateFormatter | unitsStyle = .full |
| إدخال التاريخ | DateFormatter | dateFormat + fallback |
| تسلسل API | ISO8601DateFormatter | withInternetDateTime |
| تصدير التقرير | DateFormatter | en_US_POSIX + UTC |
الأسئلة الشائعة
السبب الأكثر شيوعاً — عدم تطابق dateFormat مع تنسيق السلسلة. على سبيل المثال، التنسيق "dd.MM.yyyy" لن يحلل السلسلة "2026-07-21". السبب الثاني — عدم تطابق الإعدادات المحلية: السلسلة "July 21, 2026" لن تُحلَّل مع الإعدادات المحلية ru_RU. السبب الثالث — أخطاء كتابية في رموز LDML: استخدم yyyy، وليس YYYY (معنى مختلف).
لا. DateFormatter كائن ثقيل، يتضمن تهيئته تحميل بيانات الإعدادات المحلية. قم بإنشاء نسخة واحدة لكل نوع تنسيق وأعد استخدامها. في بيئة متعددة الخيوط، استخدم تخزين الخيط المحلي أو مجموعة من المنسقات مع قائمة انتظار تسلسلية للمزامنة.
DateFormatter يعرض تاريخاً مطلقاً (21 يوليو 2026)، بينما RelativeDateFormatter يعرض تاريخاً نسبياً (اليوم، أمس، بعد 3 أيام). تم تقديم RelativeDateFormatter في iOS 15+ ويستخدم نفس قالب LDML لكنه يختار تلقائياً العرض النسبي.
قم بتعيين timeZone للمنسق إلى UTC قبل التحليل. إذا كان الخادم يُرجع التاريخ بالتوقيت المحلي دون تحديد المنطقة الزمنية، تحقق من مواصفات API — على الأرجح يُقصد بها UTC. بالنسبة لـ ISO 8601 مع Z في النهاية، لا حاجة لـ timeZone — فالمنسق يحلل الإزاحة من السلسلة.
لا تستخدم نسخة واحدة من خيوط مختلفة دون مزامنة. قم بإنشاء نسخة جديدة في كل خيط أو استخدم Thread.current.threadDictionary للتخزين. بديل آخر هو NSLock مع القفل طوال مدة string(from:) وdate(from:).
الخلاصة
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا