30代からのプログラミング学び直し!

10年エンジニアやってるけどいまだになんもわからん

JavaScriptでnew Date()を使って時間の計算をするときにはタイムゾーンに注意

new Date()の罠

ある残り時間を表示するための以下のコードで、海外のユーザーから「残り時間がずれているよ!」と指摘をいただきました。

NG

let endDate = "2024/03/12 22:00:00";      // この値は実際にはAPIから取得した値なので変更不可
// タイムスタンプに直して、終了時間から現在時間の差分を求める
let diff = new Date(endDate).getTime() - new Date().getTime();
// タイムスタンプの差分から「あと○時間○分」に直す処理は省略

endDateは日本時間だけど、new Date()で取得される現在時刻はユーザーが設定しているタイムゾーンになってしまうので、この処理ではまずいことが分かります。(言い訳ですがこのコードを書いたのは私ではない。)

であれば、日本時間の現在時刻を表示すればいいではないか、と思いDateのリファレンスを読んでみたのですが、どうも一筋縄ではいかなそうでした。Dateはタイムゾーンを自由に設定できるわけではないのですね…(不便)

発想を変えて、どちらも同じタイムゾーンにすればいいだろう!と思ってUTCに変換する処理を入れてみました。

NG

let endDate = "2024/03/12 22:00:00";
let diff = Date.parse(new Date(endDate).toISOString()) - Date.now();

toISOString()UTC時刻に変換してから、Date.parseでタイムスタンプを取得しています。Date.now()は常にUTCのタイムスタンプを返すようです。

が、これでもうまく動作せず。よく考えたら、endDateタイムゾーンの情報がないので、new Date(endDate)の時点で、ユーザーが設定しているタイムゾーンとして解釈されてしまうようです。

であるならば、タイムゾーンを明示的に指定してから初期化しよう、ということで以下の方法で解決しました。 ISO形式(2024-03-12T22:00:00+09:00)で記述するために、dayjsを使いました。

OK

let endDate = "2024/03/12 22:00:00";   // 実際には変更不可の値
let jstDate = dayjs(endDate).format("YYYY-MM-DDTHH:mm:ss+09:00");
let diff = Date.parse(new Date(jstDate).toISOString()) - Date.now();

めでたしめでたし。タイムゾーンは頭が混乱してしまいますね。

とても参考にさせていただきました: JavaScript における Date のタイムゾーンの挙動と対策

ブラウザでタイムゾーンを変更する方法

結論、Chrome DevToolsの「センサー(Sensors)」を使うのが一番手軽で便利でよかったです!こんな便利な機能があったとは!

センサー: デバイス センサーをエミュレートする  |  DevTools  |  Chrome for Developers

Macの システム設定>一般>日付と時刻 で端末の時間を変更するのは、タイムゾーンの問題解決には意味がなかったです。タイムゾーンではなく時間自体がずれてしまうので意図した動作にならず。最初これをやってしまって、無駄に混乱してしまいました……。