React + Material UI + Moment.jsでタイムゾーン変換ツールを作った
海外のWebinarを見たりする際などに 9:00 AM PST って日本時間のいつになるの?というのを調べることがあるので、タイムゾーン変換ツールを作ってみました。
GitHub Pagesで公開しています。
ソースはこれです。
良かったら使ってみてください。
できること
- あるタイムゾーンでの日時が、別のタイムゾーンで何日の何時になるのかを調べることができる
- タイムゾーンは地域( Asia/Tokyo や America/Los_Angeles など)、もしくは略名( JST や UTC 、PST など)で入力
- 指定したタイムゾーンのUTCオフセットを表示
- 地域指定の場合は、夏時間かどうかを判定
- 時刻表機は24時間/12時間を切り替え可能
- 初期タイムゾーンは、ひとつはUTC、もうひとつはブラウザの言語情報をもとに現在のタイムゾーンを判定した値を使用
いちおうスマートフォンでも使えるようにと作っていますが、Material UIの日付/時刻ピッカーがスマートフォンに最適化されていないため、特にiPhone 4/4sサイズだと厳しいかもしれません。
現在の制限
- 略名についてはmoment-timezoneのデータを元にしているが、一部含まれていないものもある
- 同じ略称となるものが複数存在するものがあるようなので、そういったものは除外している
- 夏時間の判定は、略名の場合はしていない
- 略名の場合、その略名自体に夏時間かどうかを含んでいる( PST に対する PDT のように)ので、とりあえず出していない(いずれ出したい)
きっかけ
ググればそういったサイトはたくさん出てくるのですが、計算にサーバーサイドを使っていたり、情報が多すぎて逆にわかりにくくなっていたりという印象がありました。
そこで、JavaScriptで時間を取り扱うライブラリとして有名なMoment.jsを使って、ついでに最近触っているReactとMaterial UIを使えば、通信なしでサクッと変換するツールが作れるのでは、と思いついたのがきっかけです。
そんな感じで、わりと軽い気持ちで作り始めてみたのですが、JavaScriptでタイムゾーンを扱うというのが、実は非常に面倒くさいことに途中で気づき、色々とハマっていました。
ふりかえり
以下、勉強になったこと。
JavaScriptでタイムゾーンを取り扱うためのいろいろ
よく理解していなかったのですが、タイムゾーンというのは継続して情報はメンテされ続けていて、年に数回程度変更されたりしているようです。
西暦何年かによって、UTCオフセットが変わっていることもあるため、例えば遠い過去の日付についての時差を厳密に計算する場合などは、その辺りも考慮する必要があるようですね。
この辺りについては、以下のスライドが詳しかったです。
Moment Timezone - タイムゾーンを扱うJavaScriptライブラリ
Moment.js とは別に Moment Timezone というライブラリがあり、タイムゾーンに関する操作はこちらを使うようです。
探してみたところ、他にもタイムゾーンを扱うライブラリはいくつか見つかったのですが、Moment.jsとの和性の点で、これを採用することにしました。
バージョンが 2015d
などとなっているとおり、新しいタイムゾーンデータにも追随してくれるようなので、その点でも安心かなと思っています。
JavaScriptでユーザーのタイムゾーンを取得する
変換前のタイムゾーンの初期値として、当然ユーザーのタイムゾーンを取得して使おうと考えたのですが、どうやらこれはブラウザからは取得できないようです。
詳しくは以下のエントリで説明されています。
JavaScript でタイムゾーン名を取得する方法 - Qiita
で、これも上記エントリで紹介されていた、 jsTimezoneDetect というライブラリが、ある程度推測してくれるということで、これを使うことにしました。
また直接関係ないのですが、タイムゾーンという国際的なものを扱うので、どうせならいずれアプリとしても国際化対応したいなーと考えたのですが、どうやらユーザーの言語設定もブラウザからは取れないようでした。
そのため、以下のエントリで紹介されている方法で、言語情報を取得することにしています。(まだ国際化は実装できていませんが)
ブラウザの言語をJavascriptから調べる。 – @masuidrive blog
プログラム内部でのタイムゾーンの取扱
先のスライドにもあるとおり、JavaScriptの Date
は、ローカルタイムとUTCの2つのタイムゾーンしか扱えません。
そのため、それ以外のタイムゾーンの時刻を取り扱う場合は、表示や入力の際にローカルタイムと多のタイムゾーンの差を考慮しながら計算する必要があります。
例えば日本(JST)がローカルタイムとなる場合、 America/Los_Angeles の 2015/8/7 9:00 は、 Asia/Tokyo (JST) では 2015/8/8 1:00 になり、これは、 new Date().getTime()
で取得できる 1970/1/1 00:00:00 UTC からのミリ秒数としては同じ値になります。
ですが、上記の日時を表す Date
を使って画面表示した場合、日本ではJSTで解釈されるので、 2015/8/8 1:00 が表示されてしまいます。
そこで、表示上 America/Los_Angeles の 2015/8/7 9:00 にするためには new Date('2015/08/07 09:00')
な Date
インスタンスを渡せば良いわけですが、このインスタンスが持つUTCオフセットはあくまで Asia/Tokyo の +9:00 というチグハグな状態のため、あとからこのインスタンスを再利用して計算等する場合は注意が必要です。
同様に、ユーザーが入力した日時をJST以外のタイムゾーンとして扱う場合も、入力されたタイミングの Date
はローカルタイム(JST)で解釈されるため、そのまま使ってしまうと、本来のタイムゾーンでの値と違う日時として計算してしまうため、注意が必要です。
Reactの理解不足
JavaScriptで複数のタイムゾーンを扱うことへの対処として、当初はタイムゾーン変換後の Date
というのをコンポーネントに持たせてしまい、これが実装中に混乱を引き起こしていました。
また、作っている途中でなぜか、コンポーネントの props
に渡す値が Render
の都度変わるのは今後ダメになる?という大きな勘違いをしてしまい、 state
を乱発してしまったのも要因のひとつです。
何かおかしいと思い、心を落ち着けるために、いよいよ「 入門 React
」を買って、ざっと眺めていたところ、おもいっきり「加工した値を state
で持つのはアンチパターン」と書かれていました。
そこから実装を見なおして、改めて state
ではなく props
を活用するようにしたところ、コードもすっきりできた気がします。
今後の予定
最低限、自分が使いたい機能はできたので、ひとまず公開してしまっています。
まだまだ使いにくいところがあるので、暇を見て改善していこうと思います。
- タイムゾーン入力方法の変更
- オートコンプリート、もしくはドロップダウンリストによる選択
- 略名時の夏時間表記
- 国際化
- スマートフォン対応
- 日付ピッカー/時刻ピッカーがスマートフォンに対応できていない
- 本家でも検討されているようなので、状況を見つつ、独自対応も検討
- 日付ピッカー/時刻ピッカーがスマートフォンに対応できていない
WEB+DB PRESS総集編[Vol.1~84] (WEB+DB PRESS plus)
- 作者: WEB+DB PRESS編集部,田中慎司,池邉智洋,桑野章弘,石本光司,城戸忠之
- 出版社/メーカー: 技術評論社
- 発売日: 2015/08/11
- メディア: 大型本
- この商品を含むブログを見る