كيفية تنسيق نطاقات التواريخ مثل 1 يناير - 5 يناير

استخدم JavaScript لعرض نطاقات التواريخ بتنسيق مناسب للغة مع إزالة التكرار الذكي

مقدمة

تظهر نطاقات التواريخ في جميع أنحاء تطبيقات الويب. تعرض أنظمة الحجز التوافر من 1 يناير إلى 5 يناير، وتعرض تقاويم الأحداث المؤتمرات متعددة الأيام، وتعرض لوحات التحليلات البيانات لفترات زمنية محددة، وتغطي التقارير الأرباع المالية أو نطاقات التواريخ المخصصة. تنقل هذه النطاقات أن شيئًا ما ينطبق على جميع التواريخ بين نقطتي نهاية.

عندما تقوم بتنسيق نطاقات التواريخ يدويًا عن طريق ربط تاريخين منسقين بشرطة، فإنك تنشئ مخرجات مطولة بشكل غير ضروري. لا يحتاج النطاق من 1 يناير 2024 إلى 5 يناير 2024 إلى تكرار الشهر والسنة في التاريخ الثاني. يوصل المخرج "1 - 5 يناير 2024" نفس المعلومات بشكل أكثر إيجازًا من خلال حذف المكونات الزائدة.

توفر JavaScript طريقة formatRange() على Intl.DateTimeFormat للتعامل مع تنسيق نطاق التواريخ تلقائيًا. تحدد هذه الطريقة مكونات التاريخ التي يجب تضمينها في كل جزء من النطاق، مع تطبيق الاصطلاحات الخاصة باللغة للفواصل والتنسيق مع إزالة التكرار غير الضروري.

لماذا تحتاج نطاقات التواريخ إلى تنسيق ذكي

تتطلب نطاقات التواريخ المختلفة مستويات مختلفة من التفاصيل. يحتاج النطاق ضمن نفس الشهر إلى معلومات أقل من النطاق الذي يمتد لعدة سنوات. يعتمد التنسيق الأمثل على مكونات التاريخ التي تختلف بين تاريخي البداية والنهاية.

بالنسبة لنطاق ضمن نفس اليوم، تحتاج فقط إلى إظهار فرق الوقت: "10:00 صباحًا - 2:00 مساءً". إن تكرار التاريخ الكامل لكلا الوقتين لا يضيف أي معلومات.

بالنسبة لنطاق ضمن نفس الشهر، تعرض الشهر مرة واحدة وتسرد رقمي اليومين: "1-5 يناير 2024". تضمين "يناير" مرتين يجعل الناتج أصعب في القراءة دون إضافة وضوح.

بالنسبة لنطاق يمتد عبر أشهر مختلفة في نفس السنة، تعرض كلا الشهرين ولكن يمكنك حذف السنة من التاريخ الأول: "25 ديسمبر 2024 - 2 يناير 2025" يحتاج إلى المعلومات الكاملة، لكن "15 يناير - 20 فبراير 2024" يمكن حذف السنة من التاريخ الأول في بعض اللغات المحلية.

بالنسبة للنطاقات التي تمتد عبر سنوات متعددة، يجب تضمين السنة لكلا التاريخين: "1 ديسمبر 2023 - 15 مارس 2024".

يتطلب التنفيذ اليدوي لهذه القواعد التحقق من مكونات التاريخ المختلفة وإنشاء سلاسل التنسيق وفقاً لذلك. تطبق اللغات المحلية المختلفة أيضاً هذه القواعد بشكل مختلف، باستخدام فواصل واصطلاحات ترتيب مختلفة. تغلف طريقة formatRange() هذا المنطق.

استخدام formatRange لتنسيق نطاقات التواريخ

تقبل طريقة formatRange() كائني Date وتعيد سلسلة منسقة. أنشئ نسخة من Intl.DateTimeFormat باللغة المحلية والخيارات المطلوبة، ثم استدع formatRange() مع تواريخ البداية والنهاية.

const formatter = new Intl.DateTimeFormat("en-US");

const start = new Date(2024, 0, 1);
const end = new Date(2024, 0, 5);

console.log(formatter.formatRange(start, end));
// Output: "1/1/24 – 1/5/24"

يطبق المنسق تنسيق التاريخ الافتراضي للإنجليزية الأمريكية على كلا التاريخين ويربطهما بشرطة en. بالنسبة للتواريخ ضمن نفس الشهر، لا يزال الناتج يعرض كلا التاريخين الكاملين لأن التنسيق الافتراضي قصير جداً.

يمكنك تحديد مكونات التاريخ المراد تضمينها باستخدام نفس الخيارات المتاحة لتنسيق التاريخ العادي.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const start = new Date(2024, 0, 1);
const end = new Date(2024, 0, 5);

console.log(formatter.formatRange(start, end));
// Output: "January 1 – 5, 2024"

الآن يحذف المنسق بذكاء الشهر والسنة من التاريخ الثاني لأنهما يطابقان التاريخ الأول. يعرض الناتج رقم اليوم فقط لتاريخ النهاية، مما يجعل النطاق أكثر قابلية للقراءة.

كيف تُحسّن formatRange مخرجات نطاق التاريخ

تفحص الدالة formatRange() كلا التاريخين وتحدد المكونات المختلفة. وتتضمن فقط المكونات الضرورية في كل جزء من النطاق.

بالنسبة للتواريخ في نفس الشهر والسنة، يظهر يوم الانتهاء فقط في الجزء الثاني.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(formatter.formatRange(
  new Date(2024, 0, 1),
  new Date(2024, 0, 5)
));
// Output: "January 1 – 5, 2024"

console.log(formatter.formatRange(
  new Date(2024, 0, 15),
  new Date(2024, 0, 20)
));
// Output: "January 15 – 20, 2024"

يُظهر كلا النطاقين الشهر والسنة مرة واحدة، مع ربط أرقام الأيام فقط بفاصل النطاق.

بالنسبة للتواريخ في أشهر مختلفة من نفس السنة، يظهر كلا الشهرين لكن السنة تظهر مرة واحدة فقط.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(formatter.formatRange(
  new Date(2024, 0, 15),
  new Date(2024, 1, 20)
));
// Output: "January 15 – February 20, 2024"

يتضمن المُنسّق اسمي الشهرين لكنه يضع السنة في النهاية، مطبقاً إياها على النطاق بأكمله.

بالنسبة للتواريخ التي تمتد عبر سنوات مختلفة، يظهر كلا التاريخين الكاملين.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(formatter.formatRange(
  new Date(2023, 11, 25),
  new Date(2024, 0, 5)
));
// Output: "December 25, 2023 – January 5, 2024"

يتضمن كل تاريخ سنته الكاملة لأن السنوات مختلفة. لا يمكن للمُنسّق حذف أي من السنتين دون خلق غموض.

تنسيق نطاقات التاريخ والوقت

عندما يتضمن التنسيق مكونات الوقت، تطبق الدالة formatRange() نفس الحذف الذكي على حقول الوقت.

بالنسبة للأوقات في نفس اليوم، تختلف مكونات الوقت فقط في المخرجات.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric",
  hour: "numeric",
  minute: "numeric"
});

const start = new Date(2024, 0, 1, 10, 0);
const end = new Date(2024, 0, 1, 14, 30);

console.log(formatter.formatRange(start, end));
// Output: "January 1, 2024, 10:00 AM – 2:30 PM"

يظهر التاريخ مرة واحدة، متبوعاً بنطاق الوقت. يدرك المُنسّق أن تكرار التاريخ والوقت الكاملين للنهاية لن يضيف أي معلومات مفيدة.

بالنسبة للأوقات في أيام مختلفة، تظهر قيم التاريخ والوقت الكاملة لكليهما.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric",
  hour: "numeric",
  minute: "numeric"
});

const start = new Date(2024, 0, 1, 10, 0);
const end = new Date(2024, 0, 2, 14, 30);

console.log(formatter.formatRange(start, end));
// Output: "January 1, 2024, 10:00 AM – January 2, 2024, 2:30 PM"

تظهر كلا التواريخ والأوقات لأنها تمثل أياماً مختلفة. لا يمكن للمُنسّق حذف أي مكونات بأمان.

تنسيق نطاقات التاريخ في لغات مختلفة

يتكيف تنسيق النطاق مع اصطلاحات كل لغة لترتيب مكونات التاريخ والفواصل والمكونات التي يجب حذفها.

const enFormatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(enFormatter.formatRange(
  new Date(2024, 0, 1),
  new Date(2024, 0, 5)
));
// Output: "January 1 – 5, 2024"

const deFormatter = new Intl.DateTimeFormat("de-DE", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(deFormatter.formatRange(
  new Date(2024, 0, 1),
  new Date(2024, 0, 5)
));
// Output: "1.–5. Januar 2024"

const jaFormatter = new Intl.DateTimeFormat("ja-JP", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(jaFormatter.formatRange(
  new Date(2024, 0, 1),
  new Date(2024, 0, 5)
));
// Output: "2024年1月1日~5日"

تضع اللغة الإنجليزية الشهر أولاً وتعرض السنة في النهاية. تضع اللغة الألمانية أرقام اليوم أولاً مع نقاط، ثم اسم الشهر، ثم السنة. تستخدم اللغة اليابانية ترتيب السنة-الشهر-اليوم والشرطة الموجية (~) كفاصل للنطاق. يطبق كل إعداد محلي اصطلاحاته الخاصة لتحديد المكونات التي يتم عرضها مرة واحدة مقابل مرتين.

تمتد هذه الاختلافات إلى النطاقات عبر الأشهر.

const enFormatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(enFormatter.formatRange(
  new Date(2024, 0, 15),
  new Date(2024, 1, 20)
));
// Output: "January 15 – February 20, 2024"

const deFormatter = new Intl.DateTimeFormat("de-DE", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(deFormatter.formatRange(
  new Date(2024, 0, 15),
  new Date(2024, 1, 20)
));
// Output: "15. Januar – 20. Februar 2024"

const frFormatter = new Intl.DateTimeFormat("fr-FR", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

console.log(frFormatter.formatRange(
  new Date(2024, 0, 15),
  new Date(2024, 1, 20)
));
// Output: "15 janvier – 20 février 2024"

تعرض جميع الإعدادات المحلية الثلاثة اسمي الشهرين ولكنها تضعهما بشكل مختلف بالنسبة لأرقام اليوم. تتعامل أدوات التنسيق مع هذه الاختلافات تلقائياً.

تنسيق نطاقات التاريخ بأنماط مختلفة

يمكنك استخدام خيار dateStyle للتحكم في طول التنسيق الإجمالي، تماماً كما هو الحال مع تنسيق التاريخ الفردي.

const shortFormatter = new Intl.DateTimeFormat("en-US", {
  dateStyle: "short"
});

console.log(shortFormatter.formatRange(
  new Date(2024, 0, 1),
  new Date(2024, 0, 5)
));
// Output: "1/1/24 – 1/5/24"

const mediumFormatter = new Intl.DateTimeFormat("en-US", {
  dateStyle: "medium"
});

console.log(mediumFormatter.formatRange(
  new Date(2024, 0, 1),
  new Date(2024, 0, 5)
));
// Output: "Jan 1 – 5, 2024"

const longFormatter = new Intl.DateTimeFormat("en-US", {
  dateStyle: "long"
});

console.log(longFormatter.formatRange(
  new Date(2024, 0, 1),
  new Date(2024, 0, 5)
));
// Output: "January 1 – 5, 2024"

const fullFormatter = new Intl.DateTimeFormat("en-US", {
  dateStyle: "full"
});

console.log(fullFormatter.formatRange(
  new Date(2024, 0, 1),
  new Date(2024, 0, 5)
));
// Output: "Monday, January 1 – Friday, January 5, 2024"

ينتج نمط short تواريخ رقمية ولا يطبق الحذف الذكي لأن التنسيق مضغوط بالفعل. تقوم أنماط medium وlong باختصار أو كتابة الشهر كاملاً وحذف المكونات الزائدة. يتضمن نمط full أسماء أيام الأسبوع لكلا التاريخين.

استخدام formatRangeToParts للتنسيق المخصص

تُرجع دالة formatRangeToParts() مصفوفة من الكائنات التي تمثل مكونات النطاق المنسق. يتيح لك ذلك تنسيق أو معالجة أجزاء فردية من مخرجات النطاق.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const parts = formatter.formatRangeToParts(
  new Date(2024, 0, 1),
  new Date(2024, 0, 5)
);

console.log(parts);

المخرجات عبارة عن مصفوفة من الكائنات، كل منها يحتوي على خصائص type وvalue وsource.

[
  { type: "month", value: "January", source: "startRange" },
  { type: "literal", value: " ", source: "startRange" },
  { type: "day", value: "1", source: "startRange" },
  { type: "literal", value: " – ", source: "shared" },
  { type: "day", value: "5", source: "endRange" },
  { type: "literal", value: ", ", source: "shared" },
  { type: "year", value: "2024", source: "shared" }
]

تحدد خاصية type المكون: الشهر أو اليوم أو السنة أو النص الحرفي. تحتوي خاصية value على النص المنسق. تشير خاصية source إلى ما إذا كان المكون ينتمي إلى تاريخ البداية أو تاريخ النهاية أو مشترك بينهما.

يمكنك استخدام هذه الأجزاء لإنشاء HTML مخصص مع تنسيق لمكونات مختلفة.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const parts = formatter.formatRangeToParts(
  new Date(2024, 0, 1),
  new Date(2024, 0, 5)
);

let html = "";

parts.forEach(part => {
  if (part.type === "month") {
    html += `<span class="month">${part.value}</span>`;
  } else if (part.type === "day") {
    html += `<span class="day">${part.value}</span>`;
  } else if (part.type === "year") {
    html += `<span class="year">${part.value}</span>`;
  } else if (part.type === "literal" && part.source === "shared" && part.value.includes("–")) {
    html += `<span class="separator">${part.value}</span>`;
  } else {
    html += part.value;
  }
});

console.log(html);
// Output: <span class="month">January</span> <span class="day">1</span><span class="separator"> – </span><span class="day">5</span>, <span class="year">2024</span>

تحافظ هذه التقنية على التنسيق الخاص بكل لغة مع السماح بتنسيق مرئي مخصص.

ماذا يحدث عندما تكون التواريخ متساوية

عندما تمرر نفس التاريخ لكل من معاملي البداية والنهاية، يُخرج formatRange() تاريخاً واحداً منسقاً بدلاً من نطاق.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const date = new Date(2024, 0, 1);

console.log(formatter.formatRange(date, date));
// Output: "January 1, 2024"

يتعرف المنسق على أن النطاق ذا النقاط الطرفية المتطابقة ليس نطاقاً حقيقياً ويقوم بتنسيقه كتاريخ واحد. ينطبق هذا السلوك حتى عندما تكون كائنات Date مثيلات مختلفة بنفس القيمة.

const formatter = new Intl.DateTimeFormat("en-US", {
  year: "numeric",
  month: "long",
  day: "numeric"
});

const start = new Date(2024, 0, 1, 10, 0);
const end = new Date(2024, 0, 1, 10, 0);

console.log(formatter.formatRange(start, end));
// Output: "January 1, 2024"

على الرغم من أن هذه كائنات Date منفصلة، إلا أنها تمثل نفس التاريخ والوقت. يُخرج المنسق تاريخاً واحداً لأن النطاق له مدة صفرية عند مستوى دقة خيارات التنسيق. نظراً لأن التنسيق لا يتضمن مكونات الوقت، فإن الأوقات غير ذات صلة وتُعتبر التواريخ متساوية.

متى تستخدم formatRange مقابل التنسيق اليدوي

استخدم formatRange() عند عرض نطاقات التواريخ للمستخدمين. ينطبق هذا على نطاقات تواريخ الحجز، ومدة الحدث، وفترات التقارير، ونوافذ التوفر، أو أي فترات زمنية أخرى. تضمن الطريقة التنسيق الصحيح الخاص بكل لغة والحذف الأمثل للمكونات.

تجنب formatRange() عندما تحتاج إلى عرض تواريخ متعددة غير مرتبطة. يجب أن تستخدم قائمة المواعيد النهائية مثل "1 يناير، 15 يناير، 1 فبراير" استدعاءات format() العادية لكل تاريخ بدلاً من معاملتها كنطاقات.

تجنب أيضاً formatRange() عند عرض المقارنات أو الفروقات بين التواريخ. إذا كنت تعرض مدى تقدم أو تأخر تاريخ مقارنة بآخر، فهذا يمثل حساب وقت نسبي بدلاً من نطاق.