Moment: タイムゾーンに深夜DSTルールがある場合、日付の加算と減算は時間を保持しません

作成日 2018年08月22日  ·  8コメント  ·  ソース: moment/moment

この件についての説明:

(モーメント日付の配列)に日付範囲を作成しようとしています。各日付は前の1日後です。 これはすべてのユーザーで期待どおりに機能しますが、UIに重複した日付が表示されているアメリカ/サンティアゴタイムゾーンのユーザーのバグレポートを受け取りました。 私が気付いたのは、特定の日付に1日を追加すると、実際には23時間しか追加されないということです。

上の日付で8/11/2018 00:00:00アメリカ/サンティアゴタイムゾーンでは、1日に時間が増え加える8/11/2018 23:00:00の代わりに8/12/2018 00:00:00

ドキュメントでは、_日を使用しながら_ DST間を移動するときに時間を保持する必要があると説明されていますが、ここではそうではないようです。

夏時間を超える時間を追加する場合は、留意すべき特別な考慮事項もあります。 年、月、週、または日を追加する場合、元の時間は常に追加された時間と一致します。

// 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

再現する手順

次のコードでは、使用されているタイムゾーンのDSTルールは深夜です。 このルールが適用される前日の真夜中の日付があります。この日付に1日を追加し、翌日の真夜中/ 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)

役立つ可能性のあるその他の情報:

  • マシンタイムゾーン:米国東部タイムゾーン、夏時間
  • 日時コードが実行されました:2018年8月22日の午後3時
  • この問題は、Moment.jsWebページのChromeDevToolsで再現しました。

JS日付出力

(新しいDate())。toString()

  • 2018年8月22日水曜日15:13:40GMT-0400(東部夏時間)

(新しいDate())。toLocaleString()

  • 2018年8月22日、午後3時13分40秒

(新しいDate())。getTimezoneOffset()

  • 240

navigator.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

moment.version

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

最も参考になるコメント

ここでの動作は、moment-timezonev0.5.4とv0.5.26の間で変更されたように見えます。
古いバージョンでは、「2018-08-11」に1日を追加すると、同じ日の午後11時になります。
新しいバージョンでは、深夜に「2018-08-12」が表示されます。 ただし、この時間は実際には存在せず、1分を追加すると1時間戻りますが、このDSTの変更は1時間を追加することになっています。

古いバージョンは、この問題で説明されているように動作します。
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

2018年10月28日-夏時間が終了します
現地の夏時間が近づいているとき
2018年10月28日日曜日、午前3:00:00に時計が1時間遅れて
2018年10月28日日曜日、代わりに現地標準時の午前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が変化していることがわかりますが、時間は一定のままです。

再現手順に基づくと、追加された日付がDSTカットオフに完全に到達すると、コードに不一致があるように見えます。 サンティアゴのタイムゾーンでは、DSTの規則では、深夜に時計が1時間戻ります。

このエッジケースは、カットオフが午前3時で、日付が午前3時のxタイムゾーンでは再現できなかったため、DSTカットオフが深夜のタイムゾーンに特に当てはまるようです。

ヨーロッパ/ローマ-https://www.timeanddate.com/time/change/italy/rome

2018年10月28日-夏時間が終了します
現地の夏時間が近づいているとき
2018年10月28日日曜日、午前3:00:00に時計が1時間遅れて
2018年10月28日日曜日、代わりに現地標準時の午前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"

このバグは、DSTカットオフが深夜にあるタイムゾーンにのみ存在し、日付が正確にカットオフに到達するようにする日付に時間を追加していることを確認できました。

アメリカ/プンタアレナス-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)を差し引くと、同様の問題のように見えることがわかりました。

日付を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;
}

この関数を使用するときは、1日の始まりである開始日と、時間がそれほど重要ではない終了日を渡します。

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

問題は、結果のモーメント日付の配列に重複する日が含まれていることです。これは、アプリケーションのUIに反映されます(カレンダーに重複する日があります)。

私のより大きな懸念は、ライブラリが期待どおりに動作しないため、データの損失の認識や不正な要求など、見過ごされてしまう微妙な問題が発生する可能性があることです。

好奇心旺盛な読者がこの問題を解決するために突き刺すことができるように、私はコードペンか何かで一連のテストを取得するために働きます。

関連する問題#4785には、コードへのリンクがあります。
瞬間の例https://runkit.com/embed/1r62d83amq7x
それでもルクソンはこれを正しく処理します
https://runkit.com/embed/t49achvensqf

最近DSTが変更されたことがわかっているので、現在developあるコードと照合する必要があります(ただし、リリースされていません)。 または、次のリリースまで待って、状況が同じかどうかを確認することもできます。

ここでの動作は、moment-timezonev0.5.4とv0.5.26の間で変更されたように見えます。
古いバージョンでは、「2018-08-11」に1日を追加すると、同じ日の午後11時になります。
新しいバージョンでは、深夜に「2018-08-12」が表示されます。 ただし、この時間は実際には存在せず、1分を追加すると1時間戻りますが、このDSTの変更は1時間を追加することになっています。

古いバージョンは、この問題で説明されているように動作します。
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年9月)に同じ問題が発生し、日付ピッカーを使用しているSantiagoユーザーに、日付が1、2、3、4、5、6、7、7、7、7のカレンダーが表示されます。 9月...内部では、 .add(1, 'days')も使用しています。 それまでに修正がなければ、来年の2020年9月に再び発生します。

@mblandfoの回避策は、私たちにとってもうまくいくようです。 ありがとう!

このページは役に立ちましたか?
0 / 5 - 0 評価