معالجة التقويم الياباني والعطلات الرسمية وتاريخ الإغلاق في تطبيقات الأعمال ── تصميم يقاوم تغيّر العصر، وJapaneseCalendar، وحساب أيام العمل
· آخر تحديث: · غو كومورا · C#, .NET, التقويم الياباني, العطلات الرسمية, تاريخ الإغلاق, معالجة التاريخ والوقت, تطبيقات الأعمال, تطوير Windows, الاستشارات التقنية
سجل التعديلات (2 تحديثات، آخر تحديث 2 Sep، 2026)
سجل بالتغييرات التي أُجريت على هذا المقال. وحيثما حُفظت نسخة سابقة، تبقى متاحة للقراءة عبر رابط دائم يحمل معرّف DOI.
- أُعيدَت الترجمة العربية كترجمة كاملة عن النص الياباني الأصلي، وأُضيفَت خريطة المعرفة.
- أعيدت الترجمة كترجمة كاملة عن النص الياباني الأصلي. كانت النسخة العربية السابقة مختصراً يسقط أبواباً وجداول ورسوم Mermaid وتعليقات الأشكال وFAQ. أُعيدت هذه العناصر وفق الأصل الياباني، والادّعاءات التقنية مطابقة للنسخة اليابانية.
- النشر الأول
الاستشهاد بهذا المقال(DOI: 10.5281/zenodo.21621684)
هذا المقال محفوظ على Zenodo. يرد أدناه معرّف DOI الذي يشير دائمًا إلى أحدث نسخة، ومعرّف DOI المثبَّت على النسخة التي تقرؤها.
غو كومورا (2026). معالجة التقويم الياباني والعطلات الرسمية وتاريخ الإغلاق في تطبيقات الأعمال ── تصميم يقاوم تغيّر العصر، وJapaneseCalendar، وحساب أيام العمل. شركة كومورا سوفت ذ.م.م.. https://doi.org/10.5281/zenodo.21621684 https://comcomponent.com/ar/blog/japanese-calendar-holidays-closing-date-guide/
- DOI (أحدث نسخة)
- 10.5281/zenodo.21621684
- DOI (هذه النسخة)
- 10.5281/zenodo.22241031
«يجب أن يكون تاريخ التقرير بالتقويم الياباني.» «موعد الدفع عند إغلاق يقع في اليوم العشرين يُسدَّد في نهاية الشهر التالي، أو في يوم العمل السابق إن صادف ذلك عطلة رسمية.» «يجب تجميع الإجماليات حسب السنة المالية.» تأتي تطبيقات الأعمال في اليابان مزوّدة بشكل قياسي بمتطلبات تاريخ لا معنى لها في أي مكان آخر في العالم. ويخفي هذا المجال نوعاً مختلفاً من الفخاخ عن معالجة التاريخ والوقت المعتادة. تتضاعف أسماء العصور مع كل انتقال، وتتحرك العطلات الرسمية كلما تغيّر القانون، ولـ«نهاية الشهر» طول مختلف بحسب الشهر. بعبارة أخرى، كل ذلك يندرج ضمن «مواصفات تتغيّر لاحقاً».
في المقال السابق، «التاريخ والوقت والمناطق الزمنية في تطبيقات الأعمال»، تناولنا مبدأ التخزين بتوقيت UTC وكيفية التعامل مع المناطق الزمنية. في هذا المقال، وكمتابعة لذلك، نتناول المتطلبات المحلية اليابانية الثلاثة الكبرى: التقويم الياباني، والعطلات الرسمية، وتاريخ الإغلاق. نستعرض التصميم المقاوم لتغيّر العصر، وأسباب عدم كتابة العطلات الرسمية بشكل ثابت في الكود وكيفية حفظها كبيانات، وحساب تاريخ الإغلاق مع مراعاة سلوك التقريب في AddMonths — مع أمثلة برمجية وجداول قرار.
1. الخلاصة أولاً
- تبقى المعالجة الداخلية والتخزين بالتقويم الميلادي (الغريغوري)؛ ولا يوجد التقويم الياباني إلا في لحظة العرض. يؤدي تخزين سلاسل التقويم الياباني إلى مشكلة ثلاثية: استحالة الفرز، واختلاط الصيغ بعد تغيّر العصر، وخطر تسلل تواريخ غير صالحة.
- فوّض تحويل العصر إلى
JapaneseCalendarبدل جدول تناظر معدّ يدوياً. تُوفَّر تعريفات العصور من نظام التشغيل، لذا يكفي تحديث المنصة لمواكبة تغيّر العصر.1 ولا يتخلف عن الركب سوى السلاسل الثابتة مثل «هيسي» أو «ريوا» المتبقية داخل التطبيق. - لا يمكن تحديد العطلات الرسمية بمعادلة حسابية. إذ يُحدَّد الاعتدالان الربيعي والخريفي في السنة السابقة فقط، وقد تُنقَل العطلات الرسمية نفسها بتعديل قانوني عادي أو بقانون تدابير خاصة.2 صمّم النظام حول جدول مرجعي للعطلات الرسمية + عملية تحديث، ويمكنك استخدام ملف CSV للعطلات الرسمية الصادر عن مكتب مجلس الوزراء الياباني كمصدر بيانات.
- ينبغي تعريف «نهاية الشهر» و«يوم العمل» و«السنة المالية» في المواصفات قبل تعريفها في الكود. وعلى وجه الخصوص تقرّب
AddMonthsنهاية الشهر تلقائياً، لذا ينبغي أن يُحسَب تاريخ الإغلاق دائماً انطلاقاً من تاريخ مرجعي.3
فيما يلي ملخص لكيفية حفظ كل نوع من أنواع البيانات، أولاً.
| البيانات | طريقة الحفظ | لحظة التحويل/التقييم |
|---|---|---|
| التاريخ نفسه (تاريخ الطلب، موعد استحقاق الدفع) | DateOnly / DateTime بالتقويم الميلادي (عمود من نوع date في قاعدة البيانات) |
— |
| صيغة التقويم الياباني (التقارير، الشاشات) | لا تُحفَظ | تحويل عبر JapaneseCalendar فقط في لحظة العرض/الطباعة |
| العطلات الرسمية | جدول مرجعي للعطلات الرسمية (جدول أو ملف تهيئة) | يُرجَع إليه عند حساب أيام العمل |
| أيام إغلاق الشركة (العطلة الصيفية، ذكرى التأسيس) | جدول مرجعي منفصل عن العطلات الرسمية | نفس ما سبق (لا يُخلَط مع العطلات الرسمية) |
| تاريخ الإغلاق / شروط الدفع | خاصية في سجل الطرف التجاري («إغلاق في اليوم 20، دفع في نهاية الشهر التالي») | يُحسَب من تاريخ مرجعي عند إصدار الفاتورة |
| السنة المالية | لا تُحفَظ (قيمة مشتقة) | تُحسَب من التاريخ عند العرض/التجميع |
في المخطّط، يشير الخطّ المتّصل إلى علاقة قائمة دائماً، ويشير الخطّ المتقطّع إلى علاقة مشروطة (شروط قيامها مذكورة في شرح كلّ علاقة في الصفحة التفصيليّة). القائمة الكاملة للعلاقات (المجموع 24، مع الأدلّة ودرجة اليقين) وتعريفات المفاهيم الرئيسة مجمّعة في صفحة تفاصيل خريطة المعرفة (باليابانية). البيانات: JSON-LD / Turtle
2. التقويم الياباني ── اجعله مقصوراً على العرض
2.1 لماذا يجب توحيد المعالجة الداخلية على التقويم الميلادي
التقويم الياباني صيغة عرض موجّهة للبشر، وليس تمثيلاً مناسباً للحساب. لا يوجد ترتيب محدد لـ«هيسي 9» و«ريوا 9» عند المقارنة النصية، وأي حساب لفترة تعبر حدود العصر ينتهي بالحاجة إلى التحويل إلى التقويم الميلادي على أي حال. والأسوأ أن نظام العصور يتوسّع مع كل انتقال، لذا فإن البيانات التي تبقى محفوظة بصيغة التقويم الياباني تتراكم فيها صيغ قديمة وجديدة مختلطة مع كل تغيّر. كما أن تسلل بيانات غير صالحة مثل «شووا 64، 10 يناير» (تاريخ لم يوجد قط، إذ كانت هيسي قد بدأت اعتباراً من 8 يناير) مشكلة كلاسيكية في الأنظمة التي تحفظ إدخال العصر الخام كما هو.
هناك قيد آخر يُغفَل بسهولة: لا تدعم JapaneseCalendar في .NET سوى التواريخ ابتداءً من 8 سبتمبر من السنة الأولى لعصر مييجي (1868).4 فالتواريخ الأقدم — مثل تاريخ ميلاد قديم مستمد من سجل عائلي — لا يمكن معالجتها بشكل صحيح إن بقيت بصيغة العصر. بتوحيد المعالجة الداخلية على التقويم الميلادي الغريغوري، تنحصر كل هذه المشكلات في طبقة العرض وحدها.
2.2 العرض والإدخال باستخدام JapaneseCalendar
فئتا التقويم الوحيدتان في .NET القادرتان على التعامل مع عصور متعددة (era) هما JapaneseCalendar (وJapaneseLunisolarCalendar)، ويقابل محدّد التنسيق g اسم العصر.1 فيما يلي الحد الأدنى من الكود لعرض تاريخ بالتقويم الياباني.
using System.Globalization;
var jaJpWareki = new CultureInfo("ja-JP");
jaJpWareki.DateTimeFormat.Calendar = new JapaneseCalendar();
var date = new DateOnly(2026, 7, 11);
Console.WriteLine(date.ToString("ggy年M月d日", jaJpWareki)); // 令和8年7月11日
Console.WriteLine(date.ToString("ggyy/MM/dd", jaJpWareki)); // 令和08/07/11
بالنسبة لقبول الإدخال بالتقويم الياباني، من الأكثر أماناً تصميم واجهة يختار فيها المستخدم العصر من قائمة منسدلة ويُدخِل السنة والشهر واليوم كأرقام، بدل تحليل سلسلة نصية حرة. يكفي الترتيب التالي على الشاشة.
[元号 ▼] [ 8 ] 年 [ 7 ] 月 [ 11 ] 日 西暦: 2026-07-11
- العصر ── قائمة منسدلة. القيمة الافتراضية هي العصر الحالي. في شاشة إدخال بيانات سابقة، اعرض فقط العصور التي يغطيها النطاق المعالَج.
- السنة والشهر واليوم ── إدخال رقمي. الحد الأعلى للسنة يختلف حسب العصر، لذا بدّل النطاق المسموح وفق العصر المختار.
- معاينة التقويم الميلادي ── اعرض التقويم الميلادي بعد التحويل في المكان. الطريقة الوحيدة التي يلاحظ بها المُدخِل تركيبة مثل «شووا 64، 10 يناير» المذكورة لاحقاً هي وجود هذا العرض.
- القيمة المحتفَظ بها ── ما تحتفظ به الشاشة هو التقويم الميلادي. بنود التقويم الياباني الثلاثة أداة إدخال فحسب.
وإذا كان لا بد من قبول نص حر، فثبّت التنسيق باستخدام ParseExact.
var input = "令和8年7月11日";
var parsed = DateTime.ParseExact(input, "ggy年M月d日", jaJpWareki);
Console.WriteLine(parsed.ToString("yyyy-MM-dd", CultureInfo.InvariantCulture)); // 2026-07-11
تتطلّب صيغة السنة الأولى من كل عصر حذراً. في البيئات التي طُبِّقت فيها تحديثات دعم العصور، تُنسَّق السنة الأولى من كل عصر افتراضياً كـ«ريوا غان-نن» (元年) بدل رقم، ويتوفر مفتاح توافقية — Switch.System.Globalization.FormatJapaneseFirstYearAsANumber — لاستعادة الصيغة الرقمية «ريوا 1».5 تحقّق قبل التنفيذ، كجزء من المواصفات، مما إذا كان تنسيق التقرير يتوقع «ريوا 1» أم «ريوا غان-نن». نقطة أخرى: بشكل افتراضي يكون التحقق من نطاق سنوات كل عصر متساهلاً (relaxed)، لذا تُقبَل قيمة غير موجودة مثل «هيسي 33» وتُحوَّل إلى التاريخ الفعلي المقابل (ريوا 3). إذا احتجت إلى تحقق صارم من الإدخال يرفض مثل هذه القيم، يمكنك فرض ذلك عبر Switch.System.Globalization.EnforceJapaneseEraYearRanges.5 وسيتوقف قرار إبقاء التحقق متساهلاً أو جعله صارماً على مدى «تلوّث» البيانات القائمة — وتحديداً كيفية التعامل مع قيمة مثل «هيسي 32» طُبعت أو أُدخلت قبل تغيّر العصر.
هذه المفاتيح لا تُضبَط من الكود بل من ملف التهيئة.5 في .NET (Core) / .NET 5 وما بعده تُكتَب في configProperties لتهيئة زمن التشغيل.
{
"runtimeOptions": {
"configProperties": {
"Switch.System.Globalization.FormatJapaneseFirstYearAsANumber": true,
"Switch.System.Globalization.EnforceJapaneseEraYearRanges": true
}
}
}
لإدارتها في المشروع، اكتب جزء configProperties في runtimeconfig.template.json فينعكس عند البناء في <اسم التطبيق>.runtimeconfig.json المُولَّد. أما .NET Framework 4.6 وما بعده فيستخدم AppContextSwitchOverrides في app.config.
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<runtime>
<AppContextSwitchOverrides value="Switch.System.Globalization.EnforceJapaneseEraYearRanges=true" />
</runtime>
</configuration>
عند صيانة .NET Framework 4.5.2 أو أقدم، توضع إدخالات بنفس الاسم كـ REG_SZ تحت HKEY_LOCAL_MACHINE\Software\Microsoft\.NETFramework\AppContext.5
وما يمنعه هذا المفتاح وما لا يمنعه يجب تثبيته بأمثلة وإلا أخطأت الحكم. تذكر الوثائق الرسمية مثالاً: مع الإبقاء على الوضع المتساهل، تعيين «شووا 65، 9 يناير» يُعامَل كـهيسي 2، 9 يناير (1990-01-09 ميلادي)، ومع التشديد يصبح ArgumentOutOfRangeException برسالة «القيم الصالحة من 1 إلى 64».5 بتطبيق هذه القاعدة كما هي نحصل على التالي.
| تعيين التقويم الياباني | الافتراضي (متساهل) | بعد التشديد |
|---|---|---|
| هيسي 33، يوم ◯ شهر ◯ | يُقبَل ويصبح تاريخاً يعادل ريوا 3 | ArgumentOutOfRangeException |
| شووا 65، 9 يناير | يُقبَل ويصبح 1990-01-09 (هيسي 2، 9 يناير) | ArgumentOutOfRangeException (السنوات الصالحة لشووا من 1 إلى 64) |
| شووا 64، 10 يناير | يُقبَل ويصبح 1989-01-10 | يمر (السنة ضمن النطاق 1–64) |
السطر الثالث يستدعي الانتباه. شووا يوجد حتى السنة 64، لذا فحص نطاق السنة وحده لا يرفض «شووا 64، 10 يناير». فقد انتهى شووا في 7 يناير 1989، لكن ما يُفحَص هو قيمة السنة فقط. البيانات الملوّثة مثل «شووا 64، 10 يناير» المذكورة في 2.1 تدخل من هذا الفراغ تحديداً. إذا أردت التحقق بدقة حتى لتواريخ الشهر الذي يعبر تغيّر العصر، فلا تعتمد على المفتاح؛ اكتب تحققاً خاصاً يحمل تاريخي البداية والنهاية لكل عصر ويطابق على مستوى اليوم.
2.3 كود مقاوم لتغيّر العصر، وما يتخلف عن الركب
ما يمكن استخلاصه من تجربة انتقال عصر ريوا (2019) هو أن المنصة تواكب التغيير، أما التنفيذ المعدّ يدوياً للتطبيق فلا يواكبه. تعريف أسماء العصور غير مكتوب بشكل ثابت في .NET — إذ تعتمد الآلية على الرجوع إلى معلومات العصر التي يحتفظ بها نظام التشغيل — لذا فبمواصلة تطبيق تحديثات نظام التشغيل و.NET، يواكب تحويل JapaneseCalendar وتنسيقه العصر الجديد تلقائياً.1 وما يتخلف عن الركب هو أصول على جانب التطبيق مثل التالية:
- جداول أو فروع تحويل عصر معدّة يدوياً، مثل
if (year >= 2019) era = "令和"; - أسماء عصور مكتوبة مباشرة داخل قوالب التقارير، أو تنسيقات خلايا Excel، أو النماذج المطبوعة مسبقاً
- كود يعالج بقواعد خاصة اختصار العصر بحرف واحد مثل «R» أو «H» (شائع في تنسيقات التبادل مع الأنظمة الأساسية)
- البيانات المخزَّنة نفسها، التي تبقى بصيغة التقويم الياباني
عند تقدير تكلفة إصلاح متعلق بتغيّر العصر، ابدأ بتمشيط الكود المصدري والقوالب بالكامل آلياً بحثاً عن «شووا» و«هيسي» و«ريوا» و«غان-نن» لإجراء جرد شامل. من شبه المؤكد أن تطبيقات الأعمال التي تعمل منذ عصر VB6 أو Access تحتوي في مكان ما على جدول تحويل معدّ يدوياً، لذا غالباً ما تُعالَج هذه المسألة جنبًا إلى جنب مع قرار الاستمرارية الموضّح في «إطالة عمر تطبيقات VB6 / Access للأعمال وترحيلها».
2.4 كمين خفي: إعدادات المنطقة في Windows
فخ آخر يُغفَل بسهولة. في إعدادات المنطقة في Windows، يمكن للمستخدم تغيير نوع تقويم نظام التشغيل إلى التقويم الياباني. وبما أن CultureInfo.CurrentCulture تعكس افتراضياً تغيير إعدادات المستخدم (ما يُعرف بـ«تجاوز المستخدم»)،6 فعلى جهاز يحمل هذا الإعداد يبدأ DateTime.ToString() بالتنسيق الافتراضي للثقافة فجأة بإرجاع سلاسل بالتقويم الياباني مثل «令和08/07/11». فإذا كان الكود يكتب التواريخ إلى السجلات أو ملفات CSV أو قاعدة البيانات باستخدام ToString() الافتراضي للثقافة، فإن التقويم الياباني يتسلل إلى البيانات المخزَّنة على ذلك الجهاز فقط، مما يعطّل عملية التحليل لاحقاً.
يعود الحل إلى نفس استنتاج المقال السابق: يجب دائماً كتابة أي سلسلة تاريخ تعبر حدّاً — التخزين، الاتصال، التسجيل — باستخدام CultureInfo.InvariantCulture وتنسيق صريح. التنسيق الافتراضي للثقافة مخصّص للعرض على الشاشة فقط. لمزيد من التفاصيل حول تأثير الثقافة على العرض بشكل عام، راجع أيضاً «تعريب تطبيقات WinForms/WPF».
3. العطلات الرسمية ── لماذا لا يجب كتابتها بشكل ثابت في الكود، وكيفية إدارة الجدول المرجعي
3.1 العطلات الرسمية «تتغيّر»
لا تكتب أبداً اكتشاف العطلات الرسمية على هيئة switch (month, day). تُحدَّد العطلات الرسمية بموجب قانون العطلات الوطنية، وقد تغيّرت مراراً بفعل التعديلات القانونية والممارسات التشغيلية.2 وفيما يلي الأمثلة الحديثة وحدها:
| السنة | التغيير |
|---|---|
| 2016 | استُحدث يوم الجبل (11 أغسطس) |
| 2019 | عقب تغيّر العصر، انتقل عيد ميلاد الإمبراطور من 23 ديسمبر إلى 23 فبراير. لم يكن هناك عيد ميلاد للإمبراطور إطلاقاً في عام 2019 |
| 2019 | بموجب نظام خاص مرتبط بخلافة العرش، أصبح 1 مايو (يوم التتويج) وتواريخ أخرى عطلات استثنائية |
| 2020 | أُعيدت تسمية يوم الرياضة والصحة إلى يوم الرياضة |
| 2020 و2021 | بموجب قانون تدابير خاصة مرتبط بالأولمبياد، انتقل يوم البحر ويوم الرياضة ويوم الجبل لهذين العامين فقط |
إضافة إلى ذلك، لا يحمل يوم الاعتدال الربيعي ولا يوم الاعتدال الخريفي أصلاً أي تاريخ مكتوب في القانون. فهما يُحدَّدان ويُعلَنان في السنة السابقة استناداً إلى حسابات فلكية، لذا فإن الطريقة الوحيدة الموثوقة لمعرفة اعتدال مستقبلي هي انتظار الإعلان الرسمي.2 إن أي معادلة حسابية تفترض أن «العطلات الرسمية متطابقة كل عام» غير قابلة للتحقيق من حيث المبدأ.
3.2 جدول قرار لمصادر البيانات وطريقة الحفظ
صمّم تحديد العطلات الرسمية على أنه «بيانات + عملية تحديث». وفيما يلي مقارنة بين الخيارات الرئيسية.
| الأسلوب | المحتوى | الحالات المناسبة | التنبيهات |
|---|---|---|---|
| جدول مرجعي للعطلات الرسمية (إدارة ذاتية) | جدول يحتوي على التاريخ والاسم، يُحدَّث عبر شاشة إدارية | الأنظمة التجارية عموماً التي ترغب في إدارته جنبًا إلى جنب مع أيام إغلاق الشركة | دمج التحديث ضمن العملية التشغيلية (تحويل الفحص السنوي إلى تذكير في التقويم) |
| استيراد ملف CSV من مكتب مجلس الوزراء | استيراد ملف CSV للعطلات الرسمية الصادر عن مكتب مجلس الوزراء لتحديث الجدول المرجعي | عندما تكون هناك حاجة إلى مصدر أساسي موثوق لتحديثات الجدول (يُوصى به) | يحتوي فقط على الفترة المنشورة بالفعل. انتبه لترميز الأحرف |
| مكتبة عطلات رسمية (NuGet وغيرها) | التحديد عبر منطق حسابي + بيانات مضمَّنة | استخدامات صغيرة النطاق مثل الأدوات الداخلية التي لا يمكنها الحفاظ على عملية تحديث | تعتمد التعديلات القانونية المستقبلية على تحديث المكتبة. يجب التحقق من القواعد المضمَّنة |
| الكتابة الثابتة في الكود | تواريخ مكتوبة مباشرة في الكود المصدري | لا يوجد | كل تعديل قانوني يفرض إعادة توزيع على جميع العملاء |
كمصدر أساسي، يمكنك استخدام ملف CSV لتواريخ وأسماء العطلات الرسمية من عصر شووا 30 (1955) وحتى السنة التالية، الذي ينشره مكتب مجلس الوزراء في صفحته «العطلات الوطنية».2 هناك احتياطان عمليان عند الاستيراد: (1) بما أن المنشور هو فقط الفترة المؤكدة، حدّد ضمن المواصفات السلوك المتّبع عند الاستعلام عن عطلة رسمية بعد سنتين أو أكثر (إرجاع خطأ، أو التعبئة بقيمة مؤقتة مبنية على القانون مع علامة «مؤقت»)؛ (2) ترميز أحرف هذا الملف ينتمي إلى عائلة Shift_JIS، لذا يجب تحديد الترميز صراحةً عند القراءة، كما هو موضّح في «ملف CSV ليس مجرد «نص عادي»».
ولنحدّد أيضاً شكل الجدول المرجعي. العطلة الرسمية ليست أكثر من «تاريخ واسم»، لذا يمكن أن يكون الجدول مباشراً. العمودان اللذان يستحقان الإضافة هما علامة مؤقت ومصدر الاستيراد.
-- 祝日マスタ。振替休日・国民の休日も1行として持つ
CREATE TABLE public_holidays (
holiday_date date NOT NULL PRIMARY KEY, -- 1日1行。日付そのものが主キー
holiday_name nvarchar(64) NOT NULL, -- 内閣府CSVの名称をそのまま格納('元日' '休日' など)
is_provisional bit NOT NULL, -- 1 = 暫定(公表前を法律ベースで先埋めした行)
source nvarchar(32) NOT NULL, -- 'cao-csv' / 'manual' など取り込み元
updated_at datetime2(0) NOT NULL
);
-- 会社休業日マスタ。祝日とは分けて持つ(理由は3.3節の末尾)
CREATE TABLE company_holidays (
holiday_date date NOT NULL PRIMARY KEY,
reason nvarchar(64) NOT NULL, -- '夏季休業' '創立記念日' など
updated_at datetime2(0) NOT NULL
);
is_provisional هو النقطة الجوهرية. ما يدخل CSV مكتب مجلس الوزراء هو الفترة المؤكدة فقط، لذا في الأنظمة التي تحتاج إلى سنوات أبعد تُدرَج صفوف مولَّدة آلياً من قواعد القانون بـ is_provisional = 1، ثم تُستبدَل عند النشر بصفوف 0. بذلك يمكن لاحقاً شرح ما إذا كان تحديد موعد الدفع هذا قد اختلط بعطلة مؤقتة. وعلى العكس، في أعمال لا تسمح بالصفوف المؤقتة (مثل تثبيت موعد التحويل)، يكفي أن يضيّق المستدعي على is_provisional = 0 دون تقسيم الجداول.
أما جانب الاستيراد فيأخذ شكلاً مثل التالي. CSV مكتب مجلس الوزراء صفّه الأول ترويسة، والتاريخ بصيغة 1955/1/1 بلا أصفار بادئة، لذا ثبّت التنسيق عند التحليل.
using System.Globalization;
using System.Text;
// 内閣府「国民の祝日について」で公開されている syukujitsu.csv を読む。
// 1行目: 国民の祝日・休日月日,国民の祝日・休日名称
// 2行目以降: 1955/1/1,元日 ── 文字コードはShift_JIS(CP932)
public static List<(DateOnly Date, string Name)> ReadCabinetOfficeHolidayCsv(string path)
{
// .NET(Core)/.NET 5以降はこの登録がないと GetEncoding が例外になる(CSV記事4.2節)
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
var sjis = Encoding.GetEncoding("shift_jis");
var holidays = new List<(DateOnly, string)>();
var isFirstLine = true;
foreach (var line in File.ReadLines(path, sjis))
{
if (isFirstLine) { isFirstLine = false; continue; } // ヘッダー行を捨てる
if (string.IsNullOrWhiteSpace(line)) continue;
var columns = line.Split(',');
if (columns.Length < 2) continue;
// 「1955/1/1」形式。カルチャ既定に任せず書式を固定する(2.4節と同じ理由)
var date = DateOnly.ParseExact(columns[0].Trim(), "yyyy/M/d", CultureInfo.InvariantCulture);
holidays.Add((date, columns[1].Trim()));
}
return holidays;
}
محتوى عملية التحديث هو عكس الصفوف المقروءة على public_holidays بـ is_provisional = 0 / source = 'cao-csv'، واستبدال أي صف مؤقت لنفس التاريخ. العطلة البديلة والعطلة الوطنية تدخل CSV أصلاً كصفوف باسم «休日».2
3.3 العطلة البديلة والعطلة الوطنية وحساب أيام العمل
بالإضافة إلى قائمة التواريخ، يعرّف قانون العطلات قاعدتين مشتقتين: العطلة البديلة (عندما تصادف عطلة رسمية يوم أحد، يصبح أقرب يوم تالٍ «ليس عطلة رسمية» يوم عطلة)، والعطلة الوطنية (يوم عمل محصور بين عطلتين رسميتين يصبح يوم عطلة).2 يتضمّن ملف CSV من مكتب مجلس الوزراء هذه القواعد أيضاً كصفوف بيانات، لذا يكفي مع أسلوب استيراد CSV الرجوع إلى الجدول المرجعي فقط. وحتى إذا نفّذت هذا المنطق بنفسك، فإنه مستقر نسبياً لأنه مستمد من القانون.
اكتب حساب أيام العمل بثلاثة شروط: ليس يوم سبت أو أحد، وغير موجود في الجدول المرجعي للعطلات الرسمية، وغير موجود في جدول أيام إغلاق الشركة.
public sealed class BusinessDayCalendar
{
private readonly HashSet<DateOnly> _publicHolidays; // 祝日マスタ(振替休日含む)
private readonly HashSet<DateOnly> _companyHolidays; // 会社休業日マスタ
public BusinessDayCalendar(IEnumerable<DateOnly> publicHolidays,
IEnumerable<DateOnly> companyHolidays)
{
_publicHolidays = [.. publicHolidays];
_companyHolidays = [.. companyHolidays];
}
public bool IsBusinessDay(DateOnly d) =>
d.DayOfWeek is not (DayOfWeek.Saturday or DayOfWeek.Sunday)
&& !_publicHolidays.Contains(d)
&& !_companyHolidays.Contains(d);
// 期日が休日なら「前営業日へ倒す」。後ろへ倒すかは契約仕様で決める
public DateOnly ToPreviousBusinessDay(DateOnly d)
{
while (!IsBusinessDay(d)) d = d.AddDays(-1);
return d;
}
}
الفصل بين العطلات الرسمية وأيام إغلاق الشركة في جدولين مرجعيين منفصلين أمر مقصود. فـ«تحديد موعد استحقاق تحويل بنكي ينظر إلى العطلات الرسمية فقط (عطلتنا الصيفية لا علاقة لها بذلك)»، بينما «تحديد تاريخ الشحن ينظر إلى كليهما» — أي أن مجموعة أيام العطلة الواجب مراعاتها تختلف باختلاف حالة الاستخدام. فإذا مزجتهما في جدول واحد، يتحول فصلهما لاحقاً إلى عملية تنقيب أثري في بيانات مخزَّنة بالفعل.
4. تاريخ الإغلاق وشروط الدفع والسنة المالية ── معالجة «نهاية الشهر» بشكل صحيح في الحسابات
4.1 سلوك التقريب في AddMonths
الفخ الأول في حساب تاريخ الإغلاق هو AddMonths. تقوم DateTime.AddMonths / DateOnly.AddMonths بالتقريب إلى آخر يوم في الشهر الناتج عندما يؤدي الحساب المباشر إلى يوم غير موجود في ذلك الشهر.3
var jan31 = new DateOnly(2026, 1, 31);
var feb = jan31.AddMonths(1); // 2026-02-28 ── 月末に丸められる(正しい)
var mar = feb.AddMonths(1); // 2026-03-28 ── 「月末」のつもりが28日に化ける
التقريب نفسه محدَّد بشكل صحيح، لكن تسلسل استدعاء AddMonths آخر على نتيجة مقرَّبة بالفعل يفقد النية المقصودة بـ«نهاية الشهر». يظهر هذا عملياً عندما يُبنى جدول فوترة شهري على أساس «تاريخ التنفيذ السابق + شهر واحد»: فبمجرد تجاوز فبراير، يبدأ الحساب بالانزياح بشكل دائم نحو اليوم 28 من كل شهر. هناك إجراءان مضادان:
- بالنسبة للتواريخ المتكررة، احسب دائماً انطلاقاً من تعريف نقطة المرجعية («نهاية كل شهر»، «اليوم 20 من كل شهر»)، وليس من النتيجة السابقة
- اشتقّ «نهاية الشهر» من
DateTime.DaysInMonth(y, m)بدل قيمة تاريخ حرفية
4.2 تنفيذ «إغلاق في اليوم 20، دفع في نهاية الشهر التالي»
تاريخ الإغلاق وشروط الدفع خصائص خاصة بكل طرف تجاري. تنفيذ «إغلاق في اليوم 20، دفع في نهاية الشهر التالي» بالكود يكون كالتالي:
public static class PaymentTerms
{
// 取引日 → 締め日(毎月20日締めの例)
public static DateOnly GetClosingDate(DateOnly tradeDate, int closingDay)
{
var dayInMonth = Math.Min(closingDay, DateTime.DaysInMonth(tradeDate.Year, tradeDate.Month));
var closing = new DateOnly(tradeDate.Year, tradeDate.Month, dayInMonth);
return tradeDate <= closing ? closing : closing.AddMonths(1);
// 注: closingDay=31(月末締め)は AddMonths の丸めで正しく翌月末になる
}
// 締め日 → 支払期日(翌月末払いの例)。休日調整は仕様(前倒し/後ろ倒し)を確認して適用
public static DateOnly GetPaymentDueDate(DateOnly closingDate, BusinessDayCalendar calendar)
{
var nextMonth = closingDate.AddMonths(1);
var endOfMonth = new DateOnly(nextMonth.Year, nextMonth.Month,
DateTime.DaysInMonth(nextMonth.Year, nextMonth.Month));
return calendar.ToPreviousBusinessDay(endOfMonth); // 「休日なら前営業日」の契約の場合
}
}
بعد التحذير من AddMonths في 4.1 مباشرة، نستخدم AddMonths في موضعين. هذا ليس تناقضاً، فنوضّح لماذا هو آمن هنا. ما كان خطراً في 4.1 هو جعل النتيجة المقرَّبة مدخلاً للحساب التالي كما هي. الكود أعلاه لا يفعل ذلك في أي من الدالتين.
GetClosingDateيعيد في كل مرة تجميع تاريخ إغلاق ذلك الشهر من تاريخ المعاملة. يُستخدمAddMonths(1)مرة واحدة فقط بمعنى «تجاوز إغلاق هذا الشهر فانتقل إلى إغلاق الشهر التالي»، ولا يعيد استخدام القيمة المُرجَعة السابقة. وعندما يكونclosingDay = 31(إغلاق نهاية الشهر)، فإنAddMonths(1)على إغلاق مبني من 31 يناير يعطي 28 فبراير، وهذه حالة يتطابق فيها ناتج التقريب تماماً مع معنى «إغلاق نهاية الشهر». أي أننا هنا لا نلغي التقريب بل نستخدمه.GetPaymentDueDateلا يقرأ من ناتجAddMonths(1)سوى السنة والشهر. اليوم يُعاد بناؤه بـDateTime.DaysInMonth، لذا حتى لو صار اليوم 28 بسبب التقريب لا يتأثر الناتج. إغلاق 31 يناير يُشتق منه «آخر يوم في فبراير» أي 28 فبراير، وإغلاق 31 أغسطس يُشتق منه «آخر يوم في سبتمبر» أي 30 سبتمبر.
باختصار، إذا قُصر AddMonths على «تقديم السنة والشهر خطوة واحدة» وأُعيد بناء اليوم دائماً من تعريف المرجعية (نهاية كل شهر / اليوم 20 من كل شهر) فهو آمن. الحركة المحظورة هي «الاستمرار في الإضافة إلى النتيجة السابقة».
الأهم من الكود هو تثبيت المواصفات مسبقاً. في «الإغلاق بنهاية الشهر»، هل تُحسَب نهاية الشهر بالأيام التقويمية أم بأيام العمل؟ إذا صادف الموعد يوم عطلة، هل يُقدَّم أم يُؤخَّر (يميل الطرف الدافع إلى التقديم، بينما يميل الطرف المستلِم إلى تأخير تاريخ القبض المتوقع — تختلف الممارسة باختلاف وجهة النظر)؟ كيف يُفسَّر في فبراير عقد يقع إغلاقه بين اليومين 29 و31؟ ما لم تُوثَّق هذه النقاط، يصبح كود المنفِّذ كما هو «مواصفة لم يوافق عليها أحد». تنتهي غالبية عمليات تحقيق الأخطاء المتعلقة بتاريخ الإغلاق ليس إلى خطأ في الكود، بل إلى غياب المواصفة.
4.3 السنة المالية والربع السنوي
في اليابان، تبدأ «السنة المالية» لتطبيق الأعمال في أبريل تقريباً دائماً. لا تحفظها؛ بل عاملها كقيمة مشتقة من التاريخ.
public static int GetFiscalYear(DateOnly d) => d.Month >= 4 ? d.Year : d.Year - 1;
// 2026-03-31 → 2025年度、2026-04-01 → 2026年度
public static int GetFiscalQuarter(DateOnly d) => ((d.Month + 8) % 12) / 3 + 1;
// 4-6月 → Q1、7-9月 → Q2、10-12月 → Q3、1-3月 → Q4
انتبه إلى عدم تخزين السنة المالية بشكل متكرر ودون داعٍ في قاعدة البيانات. فحفظ قيمة يمكن دائماً اشتقاقها من التاريخ في عمود منفصل يخلق خطر ظهور بيانات لا يتطابق فيها التاريخ مع السنة المالية عند نسيان التحديث أثناء تصحيح تاريخ. وإذا كان لا بد من الاحتفاظ بها لأسباب تتعلّق بأداء التجميع، اجعلها عموداً محسوباً (مولَّداً) وامنع الكتابة المباشرة إليه من التطبيق.
4.4 الاختبار ── استباق الأخطاء التي لا تظهر إلا «في ذلك اليوم بالذات»
لا تظهر أخطاء العصر والعطلات الرسمية وتاريخ الإغلاق إلا في تواريخ محددة. بالاستناد إلى حقن الوقت الحالي عبر TimeProvider الذي عُرض في المقال السابق (الفصل السابع من «التاريخ والوقت والمناطق الزمنية في تطبيقات الأعمال»)، إليك خمسة تواريخ ينبغي اختبارها كحد أدنى في هذا المجال:
- حدود تغيّر العصر: عرض والتحويل العكسي لإدخال بالتقويم الياباني حول 1989-01-07/08 (شووا ← هيسي) و2019-04-30/05-01 (هيسي ← ريوا)
- السنة الأولى من العصر: التحقق من صيغة «ريوا غان-نن» في التقارير (القسم 2.2)
- السنوات الكبيسة: حسابات الإغلاق وموعد استحقاق الدفع التي تشمل 29 فبراير (2024، 2028)
- سلسلة نهايات الشهور: توليد اثني عشر شهراً من تواريخ الإغلاق بدءاً من 31 يناير، والتحقق من الحفاظ على نهاية الشهر (القسم 4.1)
- حدود السنة المالية: تحديد السنة المالية والربع السنوي ليومي 31 مارس و1 أبريل
بالإضافة إلى ذلك، يمكن أثناء مراجعة الكود اكتشاف الأنماط التالية آلياً عبر grep.
| كود يُعثَر عليه، يستحق الشك | ما الذي قد يحدث | طريقة الإصلاح |
|---|---|---|
قيم حرفية للعصر مثل "平成" أو "令和" |
يتخلف عن الركب عند تغيّر العصر التالي | الانتقال إلى JapaneseCalendar مع تنسيق g (القسم 2.3) |
قيم حرفية لتواريخ العطلات الرسمية (مثل 5, 3) |
تحديد خاطئ بعد تعديل قانوني أو قانون تدابير خاصة | الانتقال إلى الرجوع إلى الجدول المرجعي للعطلات الرسمية (القسم 3.2) |
مسار حفظ/إخراج يستخدم ToString() بدون وسائط |
تسلل التقويم الياباني إلى البيانات المخزَّنة على جهاز مضبوط على عرض العصر | InvariantCulture مع تنسيق صريح (القسم 2.4) |
تكرار التاريخ السابق.AddMonths(1) |
استمرار انزياح نهاية الشهر بعد تجاوز فبراير | الحساب في كل مرة انطلاقاً من تاريخ مرجعي (القسم 4.1) |
new DateTime(y, m, 31) |
يُطلِق استثناءً في الأشهر ذات 30 يوماً أو أقل | اشتقاقه من DateTime.DaysInMonth |
5. الخلاصة
يشترك التقويم الياباني والعطلات الرسمية وتاريخ الإغلاق في التعامل مع «مواصفات تتغيّر لاحقاً». لذلك فإن مبدأ التصميم واحد في الحالات الثلاث: لا تكتب في الكود ما قد يتغيّر، بل اعزله في البيانات وفي طبقة تحويل. وحِّد المعالجة الداخلية والتخزين على التقويم الميلادي، مع الاحتفاظ بالتقويم الياباني كتحويل مخصّص للعرض فقط؛ واحتفظ بالعطلات الرسمية في جدول مرجعي مصحوب بعملية تحديث؛ ووثّق «نهاية الشهر» و«يوم العمل» و«تعديل الموعد عند وقوعه في عطلة رسمية» كمواصفة تعاقدية قبل حسابها انطلاقاً من تاريخ مرجعي. افعل كل ذلك، وسيصبح تغيّر العصر القادم، وكذلك التعديل القادم لقانون العطلات الرسمية، بالنسبة لتطبيقك، مجرد حدثين يُعالَجان بـ«تحديث الجدول المرجعي» و«تطبيق تحديث نظام التشغيل».
نتولى في شركة كومورا سوفت ذ.م.م. تصميم وتنفيذ معالجة التواريخ المتعلقة بالتقارير والفوترة، وجرد وإصلاح أسماء العصور والعطلات الرسمية المكتوبة بشكل ثابت داخل التطبيقات القائمة، ومراجعات التصميم التي تشمل توضيح مواصفات تاريخ الإغلاق. يمكننا المساعدة حتى من مرحلة عدم معرفتك بعدد المواضع في كودك التي تحتاج إلى إصلاح متعلق بتغيّر العصر.
مقالات ذات صلة
- التاريخ والوقت والمناطق الزمنية في تطبيقات الأعمال ── من فخاخ DateTime إلى مبدأ التخزين بتوقيت UTC وتصميم الاختبارات
- ملف CSV ليس مجرد «نص عادي» ── الممارسة العملية لملفات CSV في تطبيقات الأعمال بلغة C# (ترميز الأحرف، توافق Excel، إجراءات مكافحة الحقن)
- تعريب تطبيقات WinForms/WPF ── resx والتجميعات الفرعية وتبديل الثقافة في الممارسة العملية
- إطالة عمر تطبيقات VB6 / Access للأعمال وترحيلها ── جدول قرار الإبقاء والتغليف والاستبدال
- كيفية بناء إخراج تقارير Excel - COM/Open XML/القوالب
مجالات الاستشارة ذات الصلة
تتعامل شركة كومورا سوفت ذ.م.م. مع تصميم وتنفيذ معالجة التواريخ لتطبيقات الأعمال بما يتوافق مع الأعراف التجارية اليابانية (التقارير بالتقويم الياباني، وحساب أيام العمل، ومعالجة تاريخ الإغلاق)، إلى جانب جرد وإصلاح دعم تغيّر العصر والعطلات الرسمية في التطبيقات القائمة.
- تطوير تطبيقات Windows
- تعديل وصيانة برامج Windows القائمة
- الاستشارة التقنية ومراجعة التصميم
- تواصل معنا
روابط مرجعية
-
Microsoft Learn، Work with eras - .NET. حول كون JapaneseCalendar وJapaneseLunisolarCalendar هما فئتا التقويم الوحيدتان اللتان تتعرّفان على عصور متعددة (era)، وتنسيق العصر عبر محدّد التنسيق «g»، وكون معلومات العصر تُوفَّر من المنصة بدل الاعتماد على كتابة ثابتة في التطبيق. ↩ ↩2 ↩3
-
مكتب مجلس الوزراء الياباني، «عن العطلات الوطنية». حول قائمة العطلات الرسمية بموجب قانون العطلات الوطنية، وكون يومي الاعتدال الربيعي والخريفي يُحدَّدان ويُعلَنان في السنة السابقة، وأحكام العطلة البديلة والعطلة الوطنية، وتوفّر ملف CSV لتواريخ وأسماء العطلات الرسمية من عصر شووا 30 وحتى السنة التالية. ↩ ↩2 ↩3 ↩4 ↩5 ↩6
-
Microsoft Learn، DateTime.AddMonths(Int32) Method. حول كون النتيجة تُضبَط إلى آخر يوم في الشهر الناتج عندما يتجاوز اليوم الناتج عدد أيام ذلك الشهر (على سبيل المثال، 31 يناير زائد شهر واحد يصبح 28/29 فبراير). ↩ ↩2
-
Microsoft Learn، JapaneseCalendar Class. حول نطاق التواريخ الذي تدعمه JapaneseCalendar — ابتداءً من 8 سبتمبر من السنة الأولى لعصر مييجي (1868 ميلادياً) — وكيفية التعامل مع العصور (era). ↩
-
Microsoft Learn، Work with calendars - .NET. حول السلوك الافتراضي المتمثّل في تنسيق السنة الأولى من العصر كـ«غان-نن»، ومفتاح Switch.System.Globalization.FormatJapaneseFirstYearAsANumber الذي يستعيد الصيغة الرقمية، وكون التحقق من نطاق سنوات العصر متساهلاً افتراضياً لكن يمكن جعله صارماً عبر Switch.System.Globalization.EnforceJapaneseEraYearRanges. ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn، CultureInfo.CurrentCulture Property. حول كون CurrentCulture تعكس افتراضياً تغييرات إعدادات المنطقة لمستخدم Windows (تجاوز المستخدم)، وكون التنسيق الافتراضي يعتمد على هذه الثقافة. ↩
مقالات ذات صلة
أحدث المقالات التي تشترك في نفس الوسوم. عمّق فهمك بمواضيع مرتبطة.
استخدام WMI/CIM من C# وPowerShell ── دليل عمليّ لاسترجاع معلومات العتاد ومراقبة العمليّات والاستعلام عن بُعد
الإجابة الكلاسيكيّة للحصول على الرقم التسلسليّ للحاسوب، ومراقبة المساحة الحرّة على القرص، وكشف بدء عمليّة هي WMI/CIM. يشرح المقال أوامر C...
عندما يُعامَل تطبيق Windows الذي طوّرته شركتك بوصفه فيروساً ── التعامل مع الكشف الخاطئ في Microsoft Defender والتعايش مع أثره على الأداء
نرتّب الإجراء الرسميّ عند كشف Microsoft Defender تطبيق Windows الذي طوّرته شركتك خطأً. نشرح آليّة برامج مكافحة الفيروسات الحديثة، والإبلا...
السكون والإسبات وModern Standby والتطبيقات طويلة التشغيل ── منع «التوقّف في منتصف الليل» بالتصميم
نرتّب أسباب وصول تطبيقات Windows طويلة التشغيل إلى حالة «توقّفت عندما نظرت صباحاً»، انطلاقاً من الفرق بين سكون S3 والإسبات وModern Standb...
هل تعمل تطبيقات الأعمال على Windows بإصدار Arm؟ ── واقع محاكاة x64 (Prism) ومكتبات DLL وCOM الأصليّة
نجيب المطوّرين ومسؤولي الأنظمة عن سؤال «هل تعمل تطبيقات الأعمال على Windows بإصدار Arm؟». نستعرض آليّة محاكاة x64 (Prism)، والطبقات التي ...
مطبّات محرّك الشبكة ومسار UNC ── التعامل العملي مع خادم الملفّات (المجلّد المشترك) في تطبيقات الأعمال
نرتّب الأعطال الشائعة عند الكتابة إلى مجلّد مشترك أو مراقبته من تطبيق أعمال. نشرح سبب عدم ظهور حرف القرص (Z:) من الخدمة، والصلاحيّات المط...
أين يتصل هذا الموضوع
ترتبط هذه المقالة بشكل طبيعي بصفحات الخدمات التالية.
تطوير تطبيقات ويندوز
تنفيذ متطلبات العمل الخاصة باليابان مثل عرض التاريخ بالتقويم الياباني في التقارير، وحساب أيام العمل، ومعالجة تاريخ الإغلاق، يقع في صميم نطاق استشارات تطوير تطبيقات الأعمال على Windows.
صيانة وتحديث برامج ويندوز الحالية
جرد أسماء العصور والعطلات الرسمية المكتوبة بشكل ثابت داخل تطبيق قائم، وإعادة هيكلتها لتصمد أمام تغيّر العصر القادم أو أي تعديل قانوني، يندرج ضمن تعديل وصيانة برامج Windows القائمة.
الاستشارات التقنية ومراجعة التصميم
تحديد إلى أي مدى ينبغي توثيق مفاهيم مثل «نهاية الشهر» و«يوم العمل» و«السنة المالية» رسمياً وإدارتها عبر البيانات هو استشارة تقنية تتضمن مراجعة التصميم.
الأسئلة الشائعة
أسئلة شائعة حول موضوع هذه المقالة.
- هل من الخطأ حفظ التواريخ في قاعدة البيانات بصيغة التقويم الياباني كما هي؟
- لا نوصي بذلك، لأربعة أسباب. أولاً، سلاسل مثل «هيسي 9» و«ريوا 9» تفقد ترتيبها المنطقي عند المقارنة النصية بمجرد تجاوز حدود العصر، فيستحيل الفرز والبحث ضمن نطاق. ثانياً، في لحظة تغيّر العصر التالي تختلط الصيغ القديمة والجديدة في البيانات المخزّنة. ثالثاً، يسهل تسلل بيانات فاسدة تحتوي على تواريخ لم توجد قط، مثل «شووا 64، 8 يناير» (في ذلك اليوم كانت هيسي 1 قد بدأت، إذ تغيّر العصر في 8 يناير)، فتتحول إلى قنبلة موقوتة عند التحليل. رابعاً، لا تدعم JapaneseCalendar في .NET سوى التواريخ ابتداءً من 8 سبتمبر من السنة الأولى لعصر مييجي (1868)؛ فالتواريخ الأقدم — مثل تاريخ ميلاد قديم من سجل عائلي — لا تُعالَج بشكل صحيح إن بقيت بصيغة العصر. القاعدة هي توحيد المعالجة الداخلية والتخزين على التقويم الميلادي، والتحويل إلى التقويم الياباني فقط في لحظة العرض أو الطباعة.
- كيف ينبغي لتطبيق أن يستعد لتغيّر العصر القادم؟
- تعريف أسماء العصور غير مكتوب بشكل ثابت داخل .NET؛ بل تعتمد الآلية على الرجوع إلى معلومات العصر التي يوفّرها نظام التشغيل، لذا يكفي الاستمرار في تطبيق تحديثات نظام التشغيل و.NET حتى تتبع القدرة على «العرض بالعصر الجديد» تلقائياً. وحتى في انتقال عصر ريوا، تمثّل جوهر الاستجابة في تطبيق تحديثات المنصة فحسب. المشكلة الحقيقية في كود التطبيق وبياناته: الفروع التي تكتب «هيسي» أو «ريوا» كسلاسل ثابتة، ودوال التحويل اليدوية التي تملك جدول تناظر خاصاً بين العصر والسنة الميلادية، وأسماء العصور المكتوبة مباشرة داخل قوالب التقارير، وحقول «ريوا __» على النماذج المطبوعة مسبقاً — لا شيء من ذلك يتبع تلقائياً. أربعة إجراءات عملية: (1) توحيد المعالجة الداخلية والتخزين على التقويم الميلادي بحيث يقتصر التقويم الياباني على العرض؛ (2) التخلي عن منطق تحويل العصر اليدوي والاعتماد على JapaneseCalendar؛ (3) تمشيط الكود بالكامل آلياً بحثاً عن «شووا» و«هيسي» و«ريوا» لإجراء جرد؛ (4) إعداد اختبارات تفترض تغيّر عصر مستقبلي افتراضي.
- ألا يمكن تحديد العطلات الرسمية بمعادلة حسابية؟ ولماذا يجب حفظها كبيانات؟
- من المستحيل من حيث المبدأ بناء معادلة كاملة، لثلاثة أسباب. أولاً، يُحدَّد يوم الاعتدال الربيعي ويوم الاعتدال الخريفي في السنة السابقة عبر قرار من مجلس الوزراء استناداً إلى حسابات فلكية، ولا يردان في القانون كتاريخين ثابتين. ثانياً، تُضاف العطلات الرسمية أو تُنقَل أو يُعاد تسميتها عبر تعديلات قانونية: فقد استُحدث يوم الجبل في 2016، وانتقل عيد ميلاد الإمبراطور من 23 ديسمبر إلى 23 فبراير عام 2019 عقب تغيّر العصر (لم يكن هناك عيد ميلاد للإمبراطور إطلاقاً في عام 2019)، وأُعيدت تسمية يوم الرياضة والصحة إلى يوم الرياضة. ثالثاً، في عامي 2020 و2021 نقل قانون تدابير خاصة مرتبط بالأولمبياد يوم البحر ويوم الرياضة ويوم الجبل لهذين العامين فقط، مما يُظهر أن حتى قاعدة مثل «دائماً يوم الاثنين الثالث» قد تُلغى مؤقتاً. لذلك فإن التصميم الصحيح هو «جدول مرجعي للعطلات الرسمية + عملية تحديث»، مع اقتصار منطق الحساب على قواعد مساعدة مثل العطلة البديلة.
- انزاح موعد استحقاق الدفع عن نهاية الشهر بعد استخدام AddMonths(1). لماذا؟
- لأن DateTime.AddMonths مصمَّمة وفق المواصفات لتقريب النتيجة إلى آخر يوم في الشهر الناتج عندما يؤدي الحساب المباشر إلى يوم غير موجود في ذلك الشهر. تطبيق AddMonths(1) على 31 يناير يعطي 28 فبراير (أو 29 في السنة الكبيسة) — وهذا السلوك في حد ذاته صحيح. لكن إذا نُقلت هذه النتيجة المقرَّبة إلى استدعاء AddMonths آخر، فإن تطبيق AddMonths(1) على 28 فبراير يعطي 28 مارس، ومنذ تلك اللحظة يستمر الانزياح إلى الأبد: ما كان يُقصد به «نهاية الشهر» يتحول بصمت وبشكل دائم إلى «اليوم 28». يتألف الحل من جزأين: بالنسبة للحسابات المتكررة، احسب دائماً انطلاقاً من نقطة مرجعية ثابتة (تاريخ العقد، أو تعريف «نهاية كل شهر») بدل الإضافة إلى النتيجة السابقة، واشتقّ «نهاية الشهر» من DateTime.DaysInMonth بدل قيمة تاريخ حرفية.
الملف الشخصي للمؤلف
صفحة الملف الشخصي لمؤلف المقالة.
غو كومورا
مؤسّس شركة كومورا سوفت ذ.م.م.
يركّز على تطوير برامج ويندوز، والاستشارات التقنية، والتحقيق في الأخطاء، ويتميّز في المشاريع التي تبقى فيها الأصول القديمة ناشطة، وفي تشخيص الأعطال التي يصعب تحديد سببها.