مع إزاحة التاريخ صفر. العمل الصحيح مع التاريخ والوقت

مع إزاحة التاريخ صفر. العمل الصحيح مع التاريخ والوقت

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

TestDateAndTime = testDateAndTime.DateTime.Date;

دعونا نقسمها:

  • لقد بدأت بقيمة DateTimeOffset 2008-05-01T08:06:32+01:00
  • قمت بعد ذلك بالاتصال بـ .DateTime، مما أدى إلى ظهور القيمة DateTime 2008-05-01T08:06:32 مع DateTimeKind.Unspecified.
  • قمت بعد ذلك بالاتصال بـ .Date، مما أدى إلى ظهور قيمة DateTime بقيمة 2008-05-01T00:00:00 مع DateTimeKind.Unspecified.
  • تقوم بتعيين النتيجة إلى testDateAndTime، وهو من النوع DateTimeOffset. يؤدي هذا إلى تحويل ضمني من DateTime إلى DateTimeOffset - ، الذي ينطبق محليوحدة زمنية. في حالتك، يبدو أن إزاحة هذه القيمة في منطقتك الزمنية المحلية هي -04:00، وبالتالي فإن القيمة الناتجة هي DateTimeOffset 2008-05-01T00:00:00-04:00 كما وصفت.

أنت قلت:

الهدف النهائي هو ببساطة الحصول على تاريخ بدون أي إزاحة زمنية أو منطقة زمنية.

حسنا، هناك حالياًليس نوع بيانات C# أصلي، وهو مجرد تاريخ بدون وقت، ويوجد نوع تاريخ خالص فيه وقت النظامالحزمة في corefxlab، ولكنها ليست جاهزة تمامًا لتطبيق إنتاج نموذجي حتى الآن. يوجد LocalDate في مكتبة Noda Time يمكنك استخدامه اليوم، ولكن لا يزال يتعين عليك التحويل مرة أخرى إلى نوع أصلي قبل الحفظ في قاعدة البيانات. لذا، في هذه الأثناء، أفضل ما يمكنك فعله هو:

  • تغيير الخاص بك خادم قاعدة البياناتلاستخدام نوع التاريخ في الحقل.
  • في التعليمات البرمجية .NET الخاصة بك، استخدم DateTime مع الوقت 00:00:00 وDateTimeKind.Unspecified. عليك أن تتذكر تجاهل الجزء الزمني (نظرًا لوجود تواريخ بالفعل بدون منتصف الليل المحلي في مناطق زمنية معينة).
  • قم بتغيير خاصية الاختبار لتصبح DateTime بدلاً من DateTimeOffset.

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

أريد التاريخ الحاليمع إزاحة صفر.

اذا أنت اريد حقاإنه مثل DateTimeOffset، يمكنك القيام بما يلي:

TestDateAndTime = new DateTimeOffset(testDateAndTime.Date, TimeSpan.Zero);

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

فيما يتعلق بالسؤال الإضافي المطروح على منتداك. يؤدي تحديد DateTimeKind.Utc إلى تغيير سلوك التمثيل الضمني. بدلاً من استخدام المنطقة الزمنية المحلية، يتم استخدام التوقيت العالمي المنسق (UTC)، الذي تكون إزاحته صفرًا دائمًا. والنتيجة هي نفس وجهة النظر الأكثر وضوحًا التي قدمتها أعلاه. ما زلت أوصي بعدم القيام بذلك لنفس الأسباب.

فكر في مثال يبدأ بـ 2016-12-31T22:00:00-04:00. وفقًا لنهجك، يجب عليك تخزين 2016-12-31T00:00:00+00:00 في قاعدة البيانات. ومع ذلك، فهذه نقطتان مختلفتان في الوقت المناسب. الأول، الذي تم تطبيعه إلى UTC، سيكون 2017-01-01T02:00:00+00:00، والثاني، الذي تم تحويله إلى منطقة زمنية أخرى، سيكون 2016-12-30T20:00:00-04:00. يرجى ملاحظة التغيير في تواريخ التحويل. ربما لا يكون هذا هو السلوك الذي تريده في طلبك.

تواجه جميع المشاريع تقريبًا مشكلات ناتجة عن المعالجة غير الصحيحة وتخزين التاريخ والوقت. حتى لو تم استخدام المشروع في نفس المنطقة الزمنية، فلا يزال بإمكانك الحصول على مفاجآت غير سارة بعد التبديل إلى التوقيت الشتوي/الصيفي. في الوقت نفسه، قليل من الناس يحيرهم تنفيذ الآلية الصحيحة منذ البداية، لأنه يبدو أنه لا يمكن أن تكون هناك مشاكل في هذا، لأن كل شيء تافه. لسوء الحظ، يظهر الواقع لاحقا أن الأمر ليس كذلك.

منطقيا، يمكن تمييز الأنواع التالية من القيم المتعلقة بالتاريخ والوقت:


دعونا نفكر في كل نقطة على حدة، دون أن ننسى ذلك.

التاريخ و الوقت

لنفترض أن المختبر الذي جمع المادة للتحليل يقع في المنطقة الزمنية +2، والفرع المركزي، الذي يراقب الانتهاء من التحليلات في الوقت المناسب، يقع في المنطقة الزمنية +1. تم تسجيل الأوقات الواردة في المثال عندما تم جمع المادة بواسطة المختبر الأول. السؤال الذي يطرح نفسه - ما هو الرقم الزمني الذي يجب أن يراه المكتب المركزي؟ ومن الواضح أن البرمجيات المكتب المركزييجب أن يظهروا في 15 كانون الثاني (يناير) 2014 عند الساعة 12:17:15 - أي أقل بساعة، نظرًا لأن الحدث وقع في تلك اللحظة بالذات وفقًا لمراقبتهم.

دعونا نفكر في إحدى سلاسل الإجراءات المحتملة التي يتم من خلالها تمرير البيانات من العميل إلى الخادم والعودة، مما يسمح لك دائمًا بعرض التاريخ/الوقت بشكل صحيح وفقًا للمنطقة الزمنية الحالية للعميل:

  1. يتم إنشاء القيمة على العميل، على سبيل المثال، 2 مارس 2016 15 :13:36، العميل موجود في المنطقة الزمنية +2.
  2. يتم تحويل القيمة إلى تمثيل سلسلة للإرسال إلى الخادم - "2016-03-02T 15 :13:36+02:00”.
  3. يتم إرسال البيانات المتسلسلة إلى الخادم.
  4. يقوم الخادم بإلغاء تسلسل الوقت في كائن التاريخ/الوقت، وإحضاره إلى منطقته الزمنية الحالية. على سبيل المثال، إذا كان الخادم يعمل عند +1، فسيحتوي الكائن على 2 مارس 2016 14 :13:36.
  5. يقوم الخادم بحفظ البيانات في قاعدة البيانات، لكنه لا يحتوي على أي معلومات حول المنطقة الزمنية - فأنواع التاريخ/الوقت الأكثر استخدامًا لا تعرف شيئًا عنها. وبالتالي، سيتم حفظ 2 مارس 2016 في قاعدة البيانات 14 :13:36 في منطقة زمنية "غير معروفة".
  6. يقرأ الخادم البيانات من قاعدة البيانات وينشئ كائنًا مطابقًا بقيمة 2 مارس 2016 14 :13:36. وبما أن الخادم يعمل في المنطقة الزمنية +1، فسيتم تفسير هذه القيمة أيضًا في نفس المنطقة الزمنية.
  7. يتم تحويل القيمة إلى تمثيل سلسلة لإرسالها إلى العميل - "2016-03-02T 14 :13:36+01:00”.
  8. يتم إرسال البيانات المتسلسلة إلى العميل.
  9. يقوم العميل بإلغاء تسلسل القيمة المستلمة في كائن التاريخ/الوقت، وإرسالها إلى منطقته الزمنية الحالية. على سبيل المثال، إذا كانت القيمة -5، فيجب أن تكون القيمة المعروضة هي 2 مارس 2016 09 :13:36.
يبدو أن كل شيء على ما يرام، ولكن دعونا نفكر في ما يمكن أن يحدث من خطأ في هذه العملية. في الواقع، يمكن أن تحدث المشاكل هنا في كل خطوة تقريبًا.
  • يمكن إنشاء الوقت على العميل بدون منطقة زمنية على الإطلاق - على سبيل المثال، نوع DateTime في .NET مع DateTimeKind.Unspecified.
  • قد يستخدم محرك التسلسل تنسيقًا لا يتضمن إزاحة المنطقة الزمنية.
  • عند إلغاء التسلسل إلى كائن، يمكن تجاهل إزاحة المنطقة الزمنية، خاصة في برامج إلغاء التسلسل "المحلية الصنع" - سواء على الخادم أو العميل.
  • عند القراءة من قاعدة بيانات، يمكن إنشاء كائن تاريخ/وقت بدون منطقة زمنية على الإطلاق - على سبيل المثال، نوع DateTime في .NET مع DateTimeKind.Unspecified. علاوة على ذلك، مع DateTime في .NET، هذا ما يحدث عمليًا إذا لم تحدد بوضوح DateTimeKind آخر مباشرة بعد التدقيق اللغوي.
  • إذا كانت خوادم التطبيقات التي تعمل مع قاعدة بيانات مشتركة موجودة في مناطق زمنية مختلفة، فسيكون هناك ارتباك خطير في إزاحات الوقت. ستكون قيمة التاريخ/الوقت المكتوبة إلى قاعدة البيانات بواسطة الخادم A ويقرأها الخادم B مختلفة بشكل ملحوظ عن نفس القيمة الأصلية المكتوبة بواسطة الخادم B ويقرأها الخادم A.
  • سيؤدي نقل خوادم التطبيقات من منطقة إلى أخرى إلى تفسير غير صحيح لقيم التاريخ/الوقت المخزنة بالفعل.
لكن العيب الأكثر خطورة في السلسلة الموضحة أعلاه هو استخدام المنطقة الزمنية المحلية على الخادم. إذا لم يكن هناك تغيير إلى التوقيت الصيفي/الشتوي، فلا مشاكل إضافيةلن يكون. ولكن خلاف ذلك، يمكنك الحصول على الكثير من المفاجآت غير السارة.

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

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

دعونا نلقي نظرة على ما تعطيه لنا هذه القاعدة:

  • عند إرسال البيانات إلى الخادم، يجب على العميل تمرير إزاحة المنطقة الزمنية حتى يتمكن الخادم من تحويل الوقت بشكل صحيح إلى UTC. هناك خيار بديل وهو إجبار العميل على إجراء هذا التحويل، لكن الخيار الأول أكثر مرونة. عند استلام البيانات مرة أخرى من الخادم، سيقوم العميل بتحويل التاريخ والوقت إلى منطقته الزمنية المحلية، مع العلم أنه سيتلقى الوقت بالتوقيت العالمي المنسق (UTC) على أي حال.
  • لا توجد انتقالات بين التوقيت الصيفي والشتوي في التوقيت العالمي المنسق، وبالتالي فإن المشاكل المرتبطة بهذا لن تكون ذات صلة.
  • على الخادم، عند القراءة من قاعدة البيانات، لا تحتاج إلى تحويل قيم الوقت؛ كل ما تحتاجه هو الإشارة بوضوح إلى أنه يتوافق مع التوقيت العالمي المنسق (UTC). في .NET، على سبيل المثال، يمكن تحقيق ذلك عن طريق تعيين DateTimeKind على كائن الوقت إلى DateTimeKind.Utc.
  • إن الاختلاف في المناطق الزمنية بين الخوادم التي تعمل مع قاعدة بيانات مشتركة، وكذلك نقل الخوادم من منطقة إلى أخرى، لن يؤثر بأي شكل من الأشكال على صحة البيانات المستلمة.
لتنفيذ مثل هذه القاعدة يكفي الاهتمام بثلاثة أشياء:
  1. قم بإجراء آلية التسلسل وإلغاء التسلسل بحيث تتم ترجمة قيم التاريخ/الوقت بشكل صحيح من UTC إلى المنطقة الزمنية المحلية والعودة.
  2. تأكد من أن أداة إلغاء التسلسل من جانب الخادم تقوم بإنشاء كائنات التاريخ/الوقت بالتوقيت العالمي المنسق (UTC).
  3. تأكد من أنه عند القراءة من قاعدة البيانات، يتم إنشاء كائنات التاريخ/الوقت بالتوقيت العالمي المنسق (UTC). يتم توفير هذا العنصر أحيانًا بدون تغييرات في التعليمات البرمجية - فقط يتم ضبط المنطقة الزمنية للنظام على جميع الخوادم على UTC.
الاعتبارات والتوصيات المذكورة أعلاه تعمل بشكل رائع عندما يتم الجمع بين الشرطين:
  • لا تتطلب متطلبات النظام عرض التوقيت المحلي و/أو إزاحة المنطقة الزمنية تمامًا كما تم تخزينها. على سبيل المثال، يجب أن تطبع تذاكر الطيران أوقات المغادرة والوصول في المنطقة الزمنية المقابلة لموقع المطار. أو إذا أرسل الخادم فواتير الطباعة التي تم إنشاؤها في دول مختلفة، يجب أن ينتهي كل منها بالتوقيت المحلي، ولا يتم تحويله إلى المنطقة الزمنية للخادم.
  • جميع قيم التاريخ والوقت في النظام "مطلقة" - أي. وصف نقطة زمنية في المستقبل أو الماضي لها قيمة واحدة بالتوقيت العالمي المنسق (UTC). على سبيل المثال، "تم إطلاق مركبة الإطلاق في الساعة 23:00 بتوقيت كييف"، أو "سيعقد الاجتماع من الساعة 13:30 إلى الساعة 14:30 بتوقيت مينسك". ستكون أرقام هذه الأحداث مختلفة باختلاف المناطق الزمنية، ولكنها ستصف نفس النقطة الزمنية. ولكن قد يحدث أن متطلبات برمجةتشير ضمنًا إلى التوقيت المحلي "النسبي" في بعض الحالات. على سبيل المثال، "سيتم بث هذا البرنامج التلفزيوني من الساعة 9:00 إلى الساعة 10:00 صباحًا في كل دولة توجد بها قناة تلفزيونية تابعة." لقد اتضح أن بث البرنامج ليس حدثًا واحدًا، بل عدة أحداث، ومن المحتمل أن تحدث جميعها في فترات زمنية مختلفة على نطاق "مطلق".
بالنسبة للحالات التي يتم فيها انتهاك الشرط الأول، يمكن حل المشكلة باستخدام أنواع البيانات التي تحتوي على المنطقة الزمنية - سواء على الخادم أو في قاعدة البيانات. فيما يلي قائمة صغيرة من الأمثلة لمنصات مختلفة وأنظمة إدارة قواعد البيانات.
.شبكة DateTimeOffset
جافا org.joda.time.DateTime، java.time.ZonedDateTime
مرض التصلب العصبي المتعدد SQL datetimeoffset
أوراكل، بوستجرس الطابع الزمني مع المنطقة الزمنية
ماي إس كيو إل

انتهاك الشرط الثاني هو حالة أكثر تعقيدا. إذا كان من الضروري تخزين هذا الوقت "النسبي" للعرض فقط، ولا توجد مهمة لتحديد اللحظة "المطلقة" في الوقت الذي وقع فيه الحدث أو سيحدث في منطقة زمنية معينة، فيكفي ببساطة تعطيل تحويل الوقت. على سبيل المثال، قام المستخدم بالدخول إلى بداية البرنامج لجميع فروع شركة التلفزيون بتاريخ 25 مارس 2016 الساعة 9:00، وسيتم بثه وتخزينه وعرضه بهذا الشكل. ولكن قد يحدث أن يقوم أحد المجدولين بتنفيذ إجراءات خاصة تلقائيًا قبل ساعة من بدء كل برنامج (إرسال إشعارات أو التحقق من وجود بعض البيانات في قاعدة بيانات شركة التلفزيون). إن تنفيذ مثل هذا المجدول بشكل موثوق ليس مهمة تافهة. لنفترض أن المجدول يعرف المنطقة الزمنية التي يوجد بها كل فرع. وإحدى الدول التي يوجد بها فرع تقرر تغيير المنطقة الزمنية بعد مرور بعض الوقت. هذه الحالة ليست نادرة كما قد تبدو - خلال هذا العام وفي العامين السابقين أحصيت أكثر من 10 أحداث مماثلة (http://www.timeanddate.com/news/time/). اتضح أنه يجب على المستخدمين تحديث روابط المنطقة الزمنية، أو يجب على المخطط أن يأخذ هذه المعلومات تلقائيًا من مصادر عالمية مثل واجهة برمجة تطبيقات المنطقة الزمنية لخرائط Google. أنا لا أتعهد بتقديم حل عالمي لمثل هذه الحالات، سأشير ببساطة إلى أن مثل هذه المواقف تتطلب دراسة جادة.

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

تاريخ بدون وقت

دعنا نقول مع العرض الصحيحوتم ترتيب التواريخ والأوقات، مع مراعاة المنطقة الزمنية للعميل. دعنا ننتقل إلى التواريخ بدون وقت والمثال المعطى لهذه الحالة في البداية - "يدخل العقد الجديد حيز التنفيذ في 2 فبراير 2016". ماذا سيحدث إذا تم استخدام نفس الأنواع ونفس الآلية لمثل هذه القيم مثل التواريخ والأوقات "العادية"؟

لا تحتوي كافة الأنظمة الأساسية واللغات وأنظمة إدارة قواعد البيانات على أنواع التاريخ فقط. على سبيل المثال، في .NET يوجد فقط نوع DateTime، ولا يوجد نوع منفصل "التاريخ فقط". حتى لو تم تحديد تاريخ فقط عند إنشاء مثل هذا الكائن، فإن الوقت لا يزال موجودًا وهو يساوي 00:00:00. إذا قمنا بنقل القيمة "2 فبراير 2016 00:00:00" من منطقة بإزاحة +2 إلى +1، نحصل على "1 فبراير 2016 23:00:00". بالنسبة للمثال أعلاه، سيكون هذا معادلاً للعقد الجديد الذي يبدأ في 2 فبراير في منطقة زمنية واحدة، وفي 1 فبراير في المنطقة الزمنية الأخرى. من الناحية القانونية، هذا أمر سخيف، وبالطبع لا ينبغي أن يكون كذلك. قاعدة عامةبالنسبة للتواريخ "الخالصة"، فالأمر بسيط للغاية - لا ينبغي تحويل هذه القيم في أي خطوة من خطوات الحفظ والقراءة.

هناك عدة طرق لتجنب تحويل التواريخ:

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

الفاصل الزمني

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

لكن حساب الفاصل الزمني يمكن أن يكون له مخاطر. لنفترض أن لدينا بعض نماذج كود C# التي تحسب الفاصل الزمني بين حدثين:

DateTime start = DateTime.Now; //... DateTime end = DateTime.Now; ساعات مزدوجة = (النهاية - البداية).TotalHours؛
للوهلة الأولى، لا توجد مشاكل هنا، ولكن الأمر ليس كذلك. أولاً، قد تكون هناك مشاكل في اختبار الوحدة لمثل هذا الكود، لكننا سنتحدث عن هذا بعد قليل. ثانيًا، لنتخيل أن اللحظة الأولية للتوقيت وقعت بالتوقيت الشتوي، واللحظة الأخيرة وقعت بالتوقيت الصيفي (على سبيل المثال، هذه هي الطريقة التي يتم بها قياس عدد ساعات العمل، ويكون للعمال نوبة ليلية).

لنفترض أن الكود يعمل في منطقة زمنية يحدث فيها التوقيت الصيفي لعام 2016 ليلة 27 مارس، ونحاكي الموقف الموضح أعلاه:

DateTime start = DateTime.Parse("2016-03-26T20:00:15+02"); DateTime end = DateTime.Parse("2016-03-27T05:00:15+03"); ساعات مزدوجة = (النهاية - البداية).TotalHours؛
سيؤدي هذا الرمز إلى 9 ساعات، على الرغم من مرور 8 ساعات بين هذه اللحظات. يمكنك التحقق من ذلك بسهولة عن طريق تغيير الكود مثل هذا:

DateTime start = DateTime.Parse("2016-03-26T20:00:15+02").ToUniversalTime(); DateTime end = DateTime.Parse("2016-03-27T05:00:15+03").ToUniversalTime(); ساعات مزدوجة = (النهاية - البداية).TotalHours؛
ومن هنا الاستنتاج - أي عمليات حسابيةتحتاج إلى التعامل مع التاريخ والوقت باستخدام قيم UTC أو الأنواع التي تخزن معلومات المنطقة الزمنية. ثم قم بالتحويل مرة أخرى إلى المستوى المحلي إذا لزم الأمر. من وجهة النظر هذه، يمكن تصحيح المثال الأصلي بسهولة عن طريق تغيير DateTime.Now إلى DateTime.UtcNow.

لا يعتمد هذا الفارق الدقيق على منصة أو لغة معينة. إليك رمز مشابه في Java به نفس المشكلة:

LocalDateTime start = LocalDateTime.now(); //... LocalDateTime end = LocalDateTime.now(); ساعات طويلة = ChronoUnit.HOURS.between(start, end);
ويمكن أيضًا إصلاح هذه المشكلة بسهولة - على سبيل المثال، باستخدام ZonedDateTime بدلاً من LocalDateTime.

الجدول الزمني للأحداث المجدولة

تعد جدولة الأحداث المجدولة حالة أكثر تعقيدًا. لا يوجد نوع عالمي يسمح لك بتخزين الجداول في المكتبات القياسية. ولكن مثل هذه المهمة لا تنشأ نادرا جدا، لذلك حلول جاهزةيمكن العثور عليها دون مشاكل. ومن الأمثلة الجيدة على ذلك تنسيق جدولة cron، والذي يتم استخدامه بشكل أو بآخر بواسطة حلول أخرى، مثل Quartz: http://quartz-scheduler.org/api/2.2.0/org/quartz/CronExpression.html. وهو يغطي جميع احتياجات الجدولة تقريبًا، بما في ذلك خيارات مثل "الجمعة الثانية من الشهر".

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

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

أولاً، فيما يتعلق باستخدام أعضاء الفئة الثابتة للحصول على الوقت الحالي - DateTime.UtcNow، ZonedDateTime.now()، إلخ. كما قيل، فإن استخدامها مباشرة في التعليمات البرمجية يمكن أن يؤدي إلى تعقيد اختبار الوحدة بشكل خطير، لأنه بدون أطر عمل خاصة لن يكون من الممكن استبدال الوقت الحالي. لذلك، إذا كنت تخطط لكتابة اختبارات الوحدة، فيجب عليك التأكد من إمكانية استبدال تنفيذ هذه الأساليب. هناك طريقتان على الأقل لحل هذه المشكلة:

  • قم بتوفير واجهة IDateTimeProvider بأسلوب واحد يقوم بإرجاع الوقت الحالي. ثم أضف تبعية إلى هذه الواجهة في جميع وحدات التعليمات البرمجية حيث تحتاج إلى الحصول على الوقت الحالي. أثناء التنفيذ العادي للبرنامج، سيتم إدخال التنفيذ "الافتراضي" في كل هذه الأماكن، مما يُرجع الوقت الحالي الحقيقي، وفي اختبارات الوحدة - أي تنفيذ ضروري آخر. هذه الطريقة هي الأكثر مرونة من وجهة نظر الاختبار.
  • أنشئ فصلًا ثابتًا خاصًا بك باستخدام طريقة للحصول على الوقت الحالي والقدرة على تثبيت أي تطبيق لهذه الطريقة من الخارج. على سبيل المثال، في حالة كود C#، يمكن لهذه الفئة الكشف عن خاصية UtcNow وأسلوب SetImplementation(Func) ضمنا). إن استخدام خاصية أو طريقة ثابتة للحصول على الوقت الحالي يلغي الحاجة إلى تحديد التبعية بشكل صريح على واجهة إضافية في كل مكان، ولكن من وجهة نظر مبادئ OOP، فهذا ليس حلاً مثاليًا. ومع ذلك، إذا كان الخيار السابق غير مناسب لسبب ما، فيمكنك استخدام هذا الخيار.
هناك مشكلة إضافية يجب معالجتها عند الترحيل إلى تطبيق موفر الوقت الحالي الخاص بك وهي التأكد من عدم استمرار أي شخص في استخدام الفئات القياسية "بالطريقة القديمة". من السهل حل هذه المهمة في معظم أنظمة مراقبة جودة التعليمات البرمجية. في الأساس، يتعلق الأمر بالبحث عن سلسلة فرعية "غير مرغوب فيها" في جميع الملفات باستثناء الملف الذي تم الإعلان عن التنفيذ "الافتراضي فيه".

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

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

العلامات: إضافة العلامات

DateTimeOffset testDateAndTime = new DateTimeOffset(2008, 5, 1, 8, 6, 32, new TimeSpan(1, 0, 0)); // الوقت والتاريخ النظيف testDateAndTime = testDateAndTime.DateTime.Date; var dateTableEntry = db.DatesTable.First(dt => dt.Id == someTestId); dateTableEntry.test= testDateAndTime; db.SaveChangesAsync();

النتيجة في قاعدة البيانات: 2008-05-01 00:00:00.0000000 -04:00

كيفية تمكين -4:00 إلى +00:00 (من الكود قبل الحفظ)؟

لقد حاولت:

مهمة عامة SetTimeZoneOffsetToZero(DateTimeOffset dateTimeOffSetObj) ( TimeSpan ZeroOffsetTimeSpan = new TimeSpan(0, 0, 0, 0, 0); إرجاع dateTimeOffSetObj.ToOffset(zeroOffsetTimeSpan); )

لا يفعل أي شيء.

الهدف النهائي هو ببساطة الحصول على تاريخ بدون أي إزاحة زمنية أو منطقة زمنية. لا أريد تحويل الوقت إلى منطقة زمنية أخرى (أي 00:00:00.0000000 ولا أريد طرح 4 ساعات من الوقت 00:00:00.0000000 وإزاحة 00:00:00.0000000 الوقت المحدد بمقدار +00 :00 ، أريد فقط ضبط الإزاحة على +00:00). أريد التاريخ الحالي مع تعويض صفر.

يحرر:

إليك ما يمكنك تقديمه في مكان آخر:

DateTimeOffset testDateAndTime = new DateTimeOffset(2008, 5, 1, 8, 6, 32, new TimeSpan(1, 0, 0)); testDateAndTime = testDateAndTime.DateTime.Date; // صفر جزء من الوقت testDateAndTime = DateTime.SpecifyKind(testDateAndTime.Date, DateTimeKind.Utc); // جزء الإزاحة "صفر خارج".

كنت متأكدًا من أن SpecifyKind سيحدد dateTimeOffset الخاص بي، مثل تغيير إزاحة الوقت والمنطقة الزمنية، ولكن عند اختباره، يبدو أنه يغير إزاحة المنطقة الزمنية فقط، وهو ما أريده. هل هناك مشكلة مع هذا؟

1 إجابة

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

TestDateAndTime = testDateAndTime.DateTime.Date;

دعونا نقسمها:

  • لقد بدأت مع DateTimeOffset 2008-05-01T08:06:32+01:00
  • قمت بعد ذلك بالاتصال بـ .DateTime، مما أدى إلى الحصول على قيمة DateTime هي 2008-05-01T08:06:32 مع DateTimeKind.Unspecified.
  • قمت بعد ذلك بالاتصال بـ .Date، مما أدى إلى ظهور قيمة DateTime بقيمة 2008-05-01T00:00:00 مع DateTimeKind.Unspecified.
  • تقوم بإرجاع النتيجة في testDateAndTime، وهو من النوع DateTimeOffset. يؤدي هذا إلى تحويل ضمني من DateTime إلى DateTimeOffset الذي ينطبق على المنطقة الزمنية المحلية. في حالتك، يبدو أن إزاحة هذه القيمة في منطقتك الزمنية المحلية هي -04:00، وبالتالي فإن القيمة الناتجة هي DateTimeOffset 2008-05-01T00:00:00-04:00 كما وصفت،

أنت قلت:

الهدف النهائي هو ببساطة الحصول على تاريخ بدون أي إزاحة زمنية أو منطقة زمنية.

حسنًا، لا يوجد حاليًا أي نوع بيانات أصلي في C# وهو مجرد تاريخ بدون وقت. يوجد نوع تاريخ خالص في حزمة System.Time في corefxlab، ولكنه ليس جاهزًا تمامًا لتطبيق إنتاج نموذجي. يوجد LocalDate في مكتبة وقت Noda، والذي يمكنك استخدامه اليوم، ولكن لا يزال يتعين عليك التحويل مرة أخرى إلى نوع أصلي قبل تخزينه في قاعدة البيانات. لذا، في هذه الأثناء، أفضل ما يمكنك فعله هو:

  • قم بتغيير SQL Server الخاص بك لاستخدام نوع التاريخ في هذا الحقل.
  • في التعليمات البرمجية .NET الخاصة بك، استخدم DateTime مع الوقت 00:00:00 وDateTimeKind.Unspecified. عليك أن تتذكر تجاهل الجزء الزمني (نظرًا لوجود تواريخ بالفعل بدون منتصف الليل المحلي في مناطق زمنية معينة).
  • قم بتغيير تنبيه الاختبار ليكون DateTime بدلاً من DateTimeOffset.

بشكل عام، في حين أن DateTimeOffset مناسب لـ كمية كبيرةالسيناريوهات (مثل أحداث الطابع الزمني)، فهي غير مناسبة لقيم التاريخ فقط.

أريد التاريخ الحالي مع تعويض صفر.

إذا كنت تريد ذلك حقًا مثل DateTimeOffset فستفعل:

TestDateAndTime = new DateTimeOffset(testDateAndTime.Date, TimeSpan.Zero);

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

فيما يتعلق بالسؤال الإضافي الذي تم طرحه في تعديلك - يؤدي تحديد DateTimeKind.Utc إلى تغيير سلوك الإرسال الضمني. بدلاً من استخدام المنطقة الزمنية المحلية، يتم استخدام التوقيت العالمي المنسق (UTC)، الذي تكون إزاحته صفرًا دائمًا. والنتيجة هي نفس وجهة النظر الأكثر وضوحًا التي قدمتها أعلاه. ما زلت أوصي بعدم القيام بذلك لنفس الأسباب.

دعونا نلقي نظرة على مثال البدء من 2016-12-31T22:00:00-04:00. وفقًا لنهجك، يجب عليك تخزين 2016-12-31T00:00:00+00:00 في قاعدة البيانات. ومع ذلك، فهذه نقطتان مختلفتان في الوقت المناسب. الأول، الذي تم تطبيعه إلى UTC، سيكون 2017-01-01T02:00:00+00:00، والثاني، الذي تم تحويله إلى منطقة زمنية أخرى، سيكون 2016-12-30T20:00:00-04:00. يرجى ملاحظة التغيير في تواريخ التحويل. ربما لا يكون هذا هو السلوك الذي تريده في طلبك.