Moment: لا تؤدي إضافة التواريخ والطرح منها إلى الاحتفاظ بالساعات عندما يكون للمنطقة الزمنية قواعد منتصف الليل بالتوقيت الصيفي

تم إنشاؤها على ٢٢ أغسطس ٢٠١٨  ·  8تعليقات  ·  مصدر: moment/moment

وصف المشكلة:

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

مع وجود تاريخ على 8/11/2018 00:00:00 في المنطقة الزمنية لأمريكا / سانتياغو ، تؤدي إضافة يوم واحد إلى زيادة الوقت إلى 8/11/2018 23:00:00 بدلاً من 8/12/2018 00:00:00 .

توضح المستندات أنه يجب الاحتفاظ بالساعات عند التبديل عبر التوقيت الصيفي _ أثناء استخدام الأيام_ ، ولكن لا يبدو أن هذا صحيح هنا.

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

// This code is from moment.js docs https://momentjs.com/docs/#/manipulating/add/
var m = moment(new Date(2011, 2, 12, 5, 0, 0)); // the day before DST in the US
m.hours(); // 5
m.add(1, 'days').hours(); // 5

خطوات التكاثر

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

fmt = d => d.format() + ' ' + d.tz()

x = moment.tz(new Date('08/11/2018 00:00:00'), 'America/Santiago');
fmt(x);                       // "2018-08-11T00:00:00-04:00 America/Santiago"
fmt(x.clone().add(1, 'day')); // "2018-08-11T23:00:00-04:00 America/Santiago" - offset unchanged, added 23 hours not 1 day
fmt(x.clone().add(2, 'day')); // "2018-08-13T00:00:00-03:00 America/Santiago" - original hour preserved now

هنا ، يمكنك أن ترى أن إضافة 24 ساعة قد أدت إلى تغييرها بمقدار 25 ساعة وتم تغيير إزاحة المنطقة الزمنية.

fmt = d => d.format() + ' ' + d.tz()

x = moment.tz(new Date('08/11/2018 00:00:00'), 'America/Santiago');
fmt(x);                          // "2018-08-11T00:00:00-04:00 America/Santiago"
fmt(x.clone().add(24, 'hours')); // "2018-08-12T01:00:00-03:00 America/Santiago" - offset changed, added 25 hours
fmt(x.clone().add(48, 'hours')); // "2018-08-13T01:00:00-03:00 America/Santiago"

بيئة:

  • الإصدار 68.0.3440.84 (الإصدار الرسمي) (64 بت)
  • Mac OSX El Capitan 10.11.6 (15G31)

معلومات أخرى قد تكون مفيدة:

  • المنطقة الزمنية للجهاز: المنطقة الزمنية الشرقية للولايات المتحدة ، التوقيت الصيفي
  • تم تشغيل رمز الوقت والتاريخ: 3:00 مساءً في 22 أغسطس 2018
  • أعدت ظهور هذه المشكلة في Chrome Dev Tools على صفحة الويب Moment.js

إخراج تاريخ JS

(تاريخ جديد ()). toString ()

  • الأربعاء 22 آب (أغسطس) 2018 15:13:40 GMT-0400 (التوقيت الصيفي الشرقي)

(تاريخ جديد ()). toLocaleString ()

  • 22/8/2018 ، 3:13:40 مساءً

(تاريخ جديد ()). getTimezoneOffset ()

  • 240

الملاح. userAgent

  • Mozilla / 5.0 (Macintosh ؛ Intel Mac OS X 10_11_6) AppleWebKit / 537.36 (KHTML ، مثل Gecko) Chrome / 68.0.3440.84 Safari / 537.36

لحظة

  • 2.22.2
Bug DST Pending Next Release Up-For-Grabs

التعليق الأكثر فائدة

يبدو أن السلوك هنا قد تغير بين المنطقة الزمنية اللحظية v0.5.4 و v0.5.26.
في الإصدار القديم ، كانت إضافة يوم واحد إلى "2018-08-11" تمنحك 11 مساءً في نفس اليوم
في الإصدار الجديد ، يمنحك "2018-08-12" في منتصف الليل. لكن هذه المرة غير موجودة بالفعل ، وإضافة دقيقة واحدة يعود إلى ساعة - ولكن كان من المفترض أن يضيف تغيير التوقيت الصيفي هذا ساعة واحدة.

الإصدار القديم ، يعمل كما هو موضح في هذه المشكلة:
https://jsfiddle.net/eqyvuxht/1/

نسخة جديدة ، تغيرت في السلوك ، ولكن ما زلت على خطأ على ما أعتقد؟
https://jsfiddle.net/0h6atn4b/4/

هنا حل بديل:

function switchZone(m, zone) {
  let arr = [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second()];
  if(zone) {
    return moment.tz(arr, zone);
  }
  return moment(arr);
}

function safeAddDays(m, days) {
   let oldZone = m.tz();
   let utc = switchZone(m, 'UTC');
   utc.add(days, 'days');
   return switchZone(utc, oldZone);
}

ال 8 كومينتر

حاولت إعادة إنتاج هذا في المنطقة الزمنية لأوروبا / روما ، ولم أتمكن من:

أوروبا / روما - https://www.timeanddate.com/time/change/italy/rome

28 أكتوبر 2018 - انتهاء التوقيت الصيفي
عندما يكون التوقيت الصيفي المحلي على وشك الوصول
الأحد ، 28 أكتوبر ، 2018 ، الساعة 3:00:00 صباحًا ، يتم إرجاع الساعات للوراء لمدة ساعة واحدة إلى
الأحد ، 28 أكتوبر ، 2018 ، 2:00:00 صباحًا بالتوقيت القياسي المحلي بدلاً من ذلك.

لا يوجد خطأ في هذا الرمز.

d => d.format() + ' ' + d.tz()

x = moment.tz(new Date('10/27/2018 00:00:00'), 'Europe/Rome');
fmt(x)                       // "2018-10-27T06:00:00+02:00 Europe/Rome"
fmt(x.clone().add(1, 'day')) // "2018-10-28T06:00:00+01:00 Europe/Rome" - tz offset changed, hour preserved as expected
fmt(x.clone().add(2, 'day')) // "2018-10-29T06:00:00+01:00 Europe/Rome"

يمكنك أن ترى أن TZ يتغير ، لكن الساعات تظل ثابتة.

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

يبدو أن حالة الحافة هذه قد تكون خاصة بالمناطق الزمنية التي يكون فيها قطع التوقيت الصيفي في منتصف الليل ، لأنني لم أتمكن من التكاثر مع منطقة زمنية حيث يكون القطع في الساعة 3 صباحًا ، مع تاريخ x في الساعة 3 صباحًا.

أوروبا / روما - https://www.timeanddate.com/time/change/italy/rome

28 أكتوبر 2018 - انتهاء التوقيت الصيفي
عندما يكون التوقيت الصيفي المحلي على وشك الوصول
الأحد ، 28 أكتوبر ، 2018 ، الساعة 3:00:00 صباحًا ، يتم إرجاع الساعات للوراء لمدة ساعة واحدة إلى
الأحد ، 28 أكتوبر ، 2018 ، 2:00:00 صباحًا بالتوقيت القياسي المحلي بدلاً من ذلك.

لا يوجد خطأ في هذا الرمز.

fmt = d => d.format() + ' ' + d.tz()

x = moment.tz(new Date('October 27, 2018 03:00:00'), 'Europe/Rome');
fmt(x);                       // "2018-10-27T09:00:00+02:00 Europe/Rome"
fmt(x.clone().add(1, 'days')) // "2018-10-28T09:00:00+01:00 Europe/Rome" - tz offset changed, hour preserved as expected
fmt(x.clone().add(2, 'days')) // "2018-10-29T09:00:00+01:00 Europe/Rome"

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

أمريكا / بونتا_أريناس - https://www.timeanddate.com/time/zone/chile/punta-arenas
في عام 2016 ، تم قطع هذه المنطقة الزمنية أيضًا عند منتصف الليل.

يبدو أن هذا الرمز يحتوي على خطأ. انظر التعليق بعد كل سطر

fmt = d => d.format() + ' ' + d.tz()

x = moment.tz(new Date('08/13/2016 00:00:00'), 'America/Punta_Arenas')
fmt(x);                        // "2016-08-13T00:00:00-04:00 America/Punta_Arenas"
fmt(x.clone().add(1, 'days')); // "2016-08-13T23:00:00-04:00 America/Punta_Arenas" - 23 hours added, not 1 day, no tz offset change
fmt(x.clone().add(2, 'days')); // "2016-08-15T00:00:00-03:00 America/Punta_Arenas"

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

عند طرح التواريخ بالضبط في وردية التوقيت الصيفي ، لا يتم الاحتفاظ بالساعة في ذلك اليوم فقط ، ولكن يتم تغيير التاريخ.

fmt = d => d.format() + ' ' + d.tz()

x = moment.tz(new Date('08/13/2018 23:00:00'), 'America/Santiago');
fmt(x);                             // "2018-08-14T00:00:00-03:00 America/Santiago"
fmt(x.clone().subtract(1, 'days')); // "2018-08-13T00:00:00-03:00 America/Santiago"
fmt(x.clone().subtract(2, 'days')); // "2018-08-12T01:00:00-03:00 America/Santiago" - hour not preserved, but date changed
fmt(x.clone().subtract(3, 'days')); // "2018-08-11T00:00:00-04:00 America/Santiago" - original hour preserved now

تلقينا تقريرًا آخر عن هذه المشكلة ، من مستخدم منطقة زمنية له America/Asuncion .

تبدو الوظيفة التي تنتج الخطأ في تطبيقنا كما يلي:

function generateDayRange(start, end) {
    const days = [];
    let current = start.clone();

    while (current <= end) {
        days.push(current.clone());
        current = current.add(1, 'days');
    }

    return days;
}

عندما نستخدم هذه الوظيفة ، فإننا نمر في تاريخ بدء في بداية اليوم ، وتاريخ انتهاء حيث لا يكون الوقت مهمًا:

generateDayRange(startDate.clone().startOf('week'), startDate.clone().endOf('week'));
generateDayRange(getAppDate(startDate), getAppDate(endDate));
generateDayRange(startRange, startRange.clone().add(27, 'days'));

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

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

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

المشكلة ذات الصلة رقم 4785 بها روابط إلى رمز:
مثال لحظة https://runkit.com/embed/1r62d83amq7x
ومع ذلك ، تتعامل Luxon مع هذا الأمر بشكل صحيح
https://runkit.com/embed/t49achvensqf

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

يبدو أن السلوك هنا قد تغير بين المنطقة الزمنية اللحظية v0.5.4 و v0.5.26.
في الإصدار القديم ، كانت إضافة يوم واحد إلى "2018-08-11" تمنحك 11 مساءً في نفس اليوم
في الإصدار الجديد ، يمنحك "2018-08-12" في منتصف الليل. لكن هذه المرة غير موجودة بالفعل ، وإضافة دقيقة واحدة يعود إلى ساعة - ولكن كان من المفترض أن يضيف تغيير التوقيت الصيفي هذا ساعة واحدة.

الإصدار القديم ، يعمل كما هو موضح في هذه المشكلة:
https://jsfiddle.net/eqyvuxht/1/

نسخة جديدة ، تغيرت في السلوك ، ولكن ما زلت على خطأ على ما أعتقد؟
https://jsfiddle.net/0h6atn4b/4/

هنا حل بديل:

function switchZone(m, zone) {
  let arr = [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second()];
  if(zone) {
    return moment.tz(arr, zone);
  }
  return moment(arr);
}

function safeAddDays(m, days) {
   let oldZone = m.tz();
   let utc = switchZone(m, 'UTC');
   utc.add(days, 'days');
   return switchZone(utc, oldZone);
}

نحن نواجه نفس المشكلة هذا الشهر (سبتمبر 2019) حيث يرى مستخدمو سانتياغو الذين يستخدمون منتقي التاريخ لدينا تقويمًا بتواريخ تبدأ من 1 ، 2 ، 3 ، 4 ، 5 ، 6 ، 7 ، 7 ، 7 ، 7 ، شهر سبتمبر ... تحت الغطاء ، نستخدم أيضًا .add(1, 'days') . سيحدث مرة أخرى العام المقبل في سبتمبر 2020 إذا لم يكن لدينا إصلاح لذلك بحلول ذلك الوقت.

يبدو أن الحل البديل mblandfo يؤدي الحيلة لنا أيضًا. شكر!

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات