ISO8601DateFormatter هي فئة Foundation في iOS وmacOS مصممة لتنسيق وتحليل التواريخ وفق المعيار الدولي ISO 8601. وفقاً لـ Apple Developer Documentation, 2024، ISO8601DateFormatter يعالج تلقائياً التنسيقات مع الميلي ثانية والمناطق الزمنية وأجزاء الثانية دون الحاجة لتعيين DateFormat يدوياً. على عكس DateFormatter، هذه الفئة لا تعتمد على Locale أو TimeZone — فهي تعمل بدقة وفقاً لمواصفات ISO 8601، مما يجعلها مثالية لتبادل التواريخ بين الخادم والعميل. الفئة متاحة بدءاً من iOS 10 وmacOS 10.12.
الخلاصة
ISO8601DateFormatter هو فئة فرعية متخصصة من Formatter في Foundation تنفذ التحويل ثنائي الاتجاه بين Date وسلاسل تنسيق ISO 8601. معيار ISO 8601 (International Standard for the Representation of Dates and Times) يحدد تنسيقاً دولياً لتبادل التواريخ والأوقات: 2024-07-21T14:30:00+00:00. على عكس DateFormatter، هذه الفئة لا تتطلب تحديد dateFormat وتحدد تلقائياً بنية السلسلة بناءً على الخيارات المعطاة.
المزايا الرئيسية لـ ISO8601DateFormatter مقارنة بـ DateFormatter: عدم الاعتماد على الإعدادات المحلية (التحليل يعمل بشكل متماثل على أي جهاز)، دعم مدمج لأجزاء الثانية (بأي عدد من المنازل العشرية) واكتشاف تلقائي للتنسيق حسب الخيارات الممررة. الفئة تتعامل أيضاً بشكل صحيح مع لاحقة Z (تعيين UTC)، المناطق الزمنية بتنسيق +HH:mm والدقة المخفضة (التاريخ فقط بدون وقت).
وفقاً لـ مواصفات ISO (ISO 8601-1:2019)، المعيار يدعم أربعة مستويات من الدقة: سنة (2024)، سنة-شهر (2024-07)، تاريخ كامل (2024-07-21) وتاريخ-وقت مع منطقة زمنية (2024-07-21T14:30:00+00:00). ISO8601DateFormatter يغطي جميع هذه المستويات من خلال مجموعة من خيارات التنسيق، مما يحرر المطور من بناء سلاسل dateFormat يدوياً.
مبدأ العمل لـ ISO8601DateFormatter يعتمد على مجموعة من خيارات البت (formatOptions)، كل منها يضم مكوناً محدداً للتاريخ أو الوقت في الإخراج. على سبيل المثال، الخيار .withFullDate يتضمن السنة والشهر واليوم؛ .withTime يتضمن الساعات والدقائق والثواني. بدمج الخيارات، يحصل المطور على مستوى الدقة المطلوب دون كتابة سلسلة dateFormat.
داخلياً، ISO8601DateFormatter يستخدم مكتبة ICU للتحليل، ولكن بقواعد ISO 8601 ثابتة. هذا يعني أنه يتجاهل إعدادات Locale وTimeZone على الجهاز — النتيجة دائماً قابلة للتنبؤ. لتعيين المنطقة الزمنية، تُستخدم الخاصية timeZone، والتي تكون قيمتها الافتراضية UTC. إذا تم تعيين timeZone إلى nil، يُستخدم الوقت المحلي للجهاز.
| خيار | الوصف | مثال الإخراج |
|---|---|---|
| .withFullDate | السنة، الشهر، اليوم | 2024-07-21 |
| .withTime | الساعات، الدقائق، الثواني | 14:30:00 |
| .withMilliseconds | أجزاء الثانية (حتى 3 أرقام) | .123 |
| .withFractionalSeconds | أجزاء الثانية (أي دقة) | .123456 |
| .withTimeZone | المنطقة الزمنية | +03:00 |
| .withColonSeparatorInTimeZone | فاصل النقطتين في المنطقة الزمنية | +03:00 (مقابل +0300) |
| .withInternetDateTime | التنسيق الكامل (تاريخ + وقت + منطقة) | 2024-07-21T14:30:00+00:00 |
دمج الخيارات: .withInternetDateTime مكافئ لدمج .withFullDate و .withTime و .withTimeZone. لتحليل السلاسل مع الميلي ثانية، أضف .withFractionalSeconds. من المهم تذكر أن .withMilliseconds يحد أجزاء الثانية إلى ثلاثة أرقام، بينما .withFractionalSeconds يدعم أي دقة — من رقم إلى تسعة أرقام بعد الفاصلة العشرية.
خيارات التنسيق لـ ISO8601DateFormatter تنقسم إلى ثلاث مجموعات: مكونات التاريخ (withFullDate، withYear، withMonth، withDay، withWeekOfYear)، مكونات الوقت (withTime، withHours، withMinutes، withSeconds) وإعدادات إضافية (withMilliseconds، withFractionalSeconds، withTimeZone، withColonSeparatorInTimeZone، withDashSeparatorInDate، withFullTime). بدمجها، يمكن الحصول على أي تنسيق فرعي تقريباً من ISO 8601.
فارق مهم: .withFractionalSeconds و .withMilliseconds متنافيان — إذا تم تعيين كليهما، يتم تطبيق .withFractionalSeconds. لتحليل الميلي ثانية من بيانات الخادم، يُوصى باستخدام .withFractionalSeconds، لأن العديد من الخوادم ترسل أجزاء الثانية بثلاثة أو ستة أو تسعة أرقام، و .withFractionalSeconds يعالج أي طول.
import Foundation
// Configure ISO8601DateFormatter
let formatter = ISO8601DateFormatter()
formatter.timeZone = TimeZone(secondsFromGMT: 0)
// Different format option combinations
formatter.formatOptions = [.withFullDate]
let dateOnly = formatter.string(from: Date())
print("Date: \(dateOnly)")
formatter.formatOptions = [.withFullDate, .withTime]
let dateTime = formatter.string(from: Date())
print("DateTime: \(dateTime)")
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let full = formatter.string(from: Date())
print("Full: \(full)")
// Parse string with milliseconds
let serverString = "2024-07-21T14:30:00.123456+03:00"
if let parsed = formatter.date(from: serverString) {
print("Parsed: \(parsed)")
}
الاستخدام الأساسي لـ ISO8601DateFormatter يتلخص في إنشاء مثيل، ضبط timeZone (يُوصى بـ UTC لبيانات الخادم) و formatOptions، بعد ذلك يمكن استدعاء string(from:) للتنسيق و date(from:) للتحليل. على عكس DateFormatter، لا داعي للقلق بشأن Locale — الفئة تتجاهل الإعدادات الإقليمية.
import Foundation
let formatter = ISO8601DateFormatter()
// Parse different ISO 8601 formats
let strings: [String] = [
"2024-07-21T14:30:00Z",
"2024-07-21T14:30:00+03:00",
"2024-07-21T14:30:00.123Z",
"2024-07-21"
]
for str in strings {
if let autoParsed = formatter.date(from: str) {
print("Parsed '\(str)': \(autoParsed)")
} else {
// Use withFullDate for date-only strings
formatter.formatOptions = [.withFullDate]
if let fallback = formatter.date(from: str) {
print("Fallback parsed '\(str)': \(fallback)")
}
formatter.formatOptions = [.withInternetDateTime]
}
}
// Serialize to RFC 3339 (GitHub API)
formatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
let rfc3339 = formatter.string(from: Date())
print("RFC 3339: \(rfc3339)")
تحليل التواريخ بأجزاء ثانية متغيرة الطول — ميزة للعديد من API الحديثة. قد يرسل الخادم إما 2024-07-21T14:30:00.123Z (3 أرقام) أو 2024-07-21T14:30:00.123456Z (6 أرقام). ISO8601DateFormatter مع خيار .withFractionalSeconds سيعالج كلا الحالتين بشكل صحيح، بينما DateFormatter مع dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" سيعالج فقط الميلي ثانية ثلاثية الأرقام.
import Foundation
let variantFormatter = ISO8601DateFormatter()
variantFormatter.formatOptions = [
.withInternetDateTime,
.withFractionalSeconds
]
// Different fractional second precision
let variants: [String] = [
"2024-07-21T14:30:00.1Z",
"2024-07-21T14:30:00.12Z",
"2024-07-21T14:30:00.123Z",
"2024-07-21T14:30:00.123456Z",
"2024-07-21T14:30:00.123456789Z"
]
for variant in variants {
if let parsed = variantFormatter.date(from: variant) {
print("OK: \(variant) -> \(parsed)")
} else {
print("FAIL: \(variant)")
}
}
// Use withMilliseconds (3 digits only)
variantFormatter.formatOptions = [
.withInternetDateTime,
.withMilliseconds
]
let milliParsed = variantFormatter.string(from: Date())
print("With milliseconds: \(milliParsed)")
اختبار تحليل جميع المتغيرات: الكود المعروض يوضح أن ISO8601DateFormatter مع .withFractionalSeconds يعالج بنجاح أجزاء الثانية بأي طول من 1 إلى 9 أرقام. هذا مهم للتوافق مع منصات الخادم المختلفة: .NET غالباً ما يولد 7 أرقام (تكات 100 نانوثانية)، Python — 6، Java — 3 أو 9 حسب الإصدار.
DateFormatter يمكنه أيضاً تحليل ISO 8601، لكنه يتطلب ضبطاً يدوياً لـ dateFormat و locale و timeZone. المشكلة الرئيسية هي أن DateFormatter يعتمد على Locale، وإذا لم يتم تعيين en_US_POSIX، قد يفشل التحليل للمستخدمين من المناطق ذات تنسيقات التاريخ غير القياسية. ISO8601DateFormatter يحل هذه المشكلة على مستوى البنية: فهو لا يستخدم Locale.
| المعلمة | ISO8601DateFormatter | DateFormatter |
|---|---|---|
| ضبط Locale | غير مطلوب (يتجاهله) | en_US_POSIX إلزامي |
| DateFormat | تلقائي (عبر خيارات) | سلسلة تنسيق يدوية |
| أجزاء الثانية | أي دقة (.withFractionalSeconds) | SSS ثابت |
| لاحقة Z | يعالج بشكل صحيح | عبر dateFormat |
| الأداء | أعلى (متخصص) | أقل (عام) |
| المعيار | ISO 8601 فقط | أي تنسيق |
| إصدار iOS | iOS 10+ | iOS 2+ |
متى تستخدم DateFormatter: إذا كنت بحاجة لتنسيق تاريخ بتنسيق غير ISO 8601 (مثل "21 يوليو 2024" لواجهة المستخدم) أو إذا كنت بحاجة لدعم iOS 9 والإصدارات الأقدم. لجميع مهام تبادل التواريخ بين الخادم والعميل، استخدم ISO8601DateFormatter — إنه أكثر أماناً وأداءً ويتطلب كوداً أقل. DateFormatter لـ ISO 8601 هو مصدر أخطاء محتملة تتعلق بالإعدادات المحلية والإقليمية.
الترحيل من DateFormatter إلى ISO8601DateFormatter: استبدل إنشاء DateFormatter + ضبط dateFormat + locale + timeZone بإنشاء ISO8601DateFormatter + ضبط formatOptions + timeZone. تحليل السلسلة يبقى دون تغيير عبر date(from:). للتوافق مع الإصدارات السابقة، يمكن استخدام #available(iOS 10, *) مع fallback إلى DateFormatter.
ضبط formatOptions المنسي يؤدي إلى استخدام القيمة الافتراضية — .withInternetDateTime. إذا أرسل الخادم تاريخاً بدون وقت (2024-07-21)، سيعيد التحليل nil. تحقق دائماً من أن formatOptions تغطي جميع التنسيقات الممكنة التي قد تصل من الخادم. لـ API ذات التنسيقات المتغيرة، استخدم محاولات fallback بمجموعات مختلفة من الخيارات.
الخلط بين withMilliseconds و withFractionalSeconds — خطأ شائع عند تحليل التواريخ بأجزاء الثانية. withMilliseconds يتوقع بالضبط 3 أرقام بعد الفاصلة العشرية. إذا أرسل الخادم 6 أرقام (ميكروثانية)، سيفشل التحليل مع withMilliseconds. استخدم .withFractionalSeconds للتوافق مع أي عدد من الأرقام. .withFractionalSeconds ظهر في iOS 13؛ للإصدارات الأقدم، استخدم DateFormatter مع dateFormat.
تجاهل المنطقة الزمنية — مشكلة شائعة أخرى. إذا أرسل الخادم تاريخاً بمنطقة زمنية (+03:00) وكان formatter مضبوطاً على UTC، التحليل لن يفشل، لكن النتيجة ستكون في UTC. غالباً ما يتوقع المطورون أن Date يحتفظ بالمنطقة الزمنية، لكن Date هي لحظة زمنية مطلقة — لا تخزن معلومات المنطقة الزمنية. للعرض الصحيح، احفظ المنطقة الزمنية بشكل منفصل أو استخدم ISO8601DateFormatter مع timeZone الصحيح.
وفقاً لمنتدى Apple (2024)، حوالي 20% من الأسئلة حول ISO8601DateFormatter تتعلق بالتنسيق حيث الثواني اختيارية. معيار ISO 8601 يسمح بتنسيق بدون ثوانٍ: 2024-07-21T14:30+03:00. ISO8601DateFormatter مع .withInternetDateTime لا يدعم هذا التنسيق — لتحليله ستحتاج DateFormatter مع dateFormat = "yyyy-MM-dd'T'HH:mmZ". هذا القيد مهم للنظر فيه عند العمل مع API التي تستخدم تنسيق الوقت المختصر.
الأسئلة الشائعة
ISO8601DateFormatter — فئة Foundation متخصصة لتنسيق وتحليل التواريخ بتنسيق ISO 8601، متاحة منذ iOS 10. تعالج تلقائياً التنسيقات القياسية دون ضبط dateFormat يدوياً.
ISO8601DateFormatter لا يعتمد على Locale، يستخدم خيارات بدلاً من dateFormat ويعالج بشكل صحيح أجزاء الثانية بأي طول. DateFormatter شامل لكنه يتطلب ضبطاً يدوياً وعرضة للأخطاء المتعلقة بالإعدادات الإقليمية.
استخدم خيار .withFractionalSeconds — يدعم من 1 إلى 9 أرقام بعد الفاصلة العشرية. لا تستخدم .withMilliseconds إذا كانت الدقة قد تختلف. .withFractionalSeconds متاح منذ iOS 13.
UTC افتراضياً. لتغييرها، اضبط الخاصية timeZone. إذا كان timeZone = nil، يُستخدم الوقت المحلي للجهاز. عند تحليل سلسلة بمنطقة زمنية صريحة بتنسيق +HH:MM، يراعيها formatter تلقائياً.
لأن formatOptions الافتراضي هو .withInternetDateTime، الذي يتوقع تاريخ + وقت + منطقة زمنية. لتحليل تاريخ فقط، اضبط formatOptions = [.withFullDate]. لدعم كلا الخيارين، استخدم fallback بخيارات مختلفة.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.