HTML5アプリケーションでの、サーバとクライアントの時差について

JavaScript

ユーザーとの対話をクライアントサイドのJavaScriptで行いつつ、サーバとの通信はAPIをXHRで呼び出すことで行うような(Ajaxと呼ばれた)モダンな Webアプリケーションを開発する際には、日本のサーバで日本人に対して日本語でサービスを提供するのであれど時差の問題について一考されたい。

古典的な Webアプリケーションでは、ユーザーが10時と入力すればユーザーの意図通りサーバに10時という時刻が確実に伝わり、そうデータベースに記録されたし、データベースに10時と記録されていればユーザーにも10時と表示されるのが当たり前だった。つまり全ての処理をサーバ本位で行っていたため時差が問題になることはなかった。

古典的な Webアプリケーション

ところがブラウザ側で動作する JavaScriptがユーザーとの対話を行う場合はどうだろう。サーバのタイムゾーンと、クライアントたるWebブラウザのタイムゾーンは違う可能性がある。以前はサーバ本位で処理が行われていたため考えなくても良かったことだが、Webブラウザの地位が拡大し処理の一部がクライアント側で行われるようになったことで、クライアントとサーバの間に存在する地理的な距離が時差となって現れ問題になる。

要するに時差を考慮しないAjaxアプリケーションでは、ユーザーが(フォームなどで)入力した日付時刻の情報がサーバには時差分ズレて伝わる、またはその逆でサーバのデータベースに格納されている日付時刻情報がユーザーの画面ではズレて表示されてしまうということだ。実際にはズレているわけではなくUTC(世界標準時)に直せば同じ時刻を示しているわけだが、かならずしもそれはユーザーの意図する結果を反映するわけではない。

日本国内向けのサービスであれば国内に時差などないのでほとんどの場合この問題は起こらないが、日本人全てが日本にいるというわけでもない。例えばシンガポール在住の日本人一家が週末日本に帰省する予定を立てていて、「日本時間の」18時のつもりでサイトからレストランの予約をしたところ日本にあるレストラン側のデータベース上には19時と登録されてしまう、といったことになるかもしれない。

もっと具体的に説明する。ユーザーがシンガポールに居れば、ブラウザ上のJavaScriptもシンガポール時間でDateオブジェクトの処理をする。ところがユーザーは日本のレストランのサイトを日本語で閲覧しているのだから、当然予約の日時も日本時間のつもりで入力している。そして予約データが日本のサーバに送信される際には(一旦UTCを経由して)日本時間に変換される。これがサーバとクライアントの間で時差がある時にユーザーの意図と違う時刻がサーバに記録されてしまうからくりだ。

日本国内のユーザーと対話する場合

時差のある場所に居るユーザーと対話する場合

相手の居場所がシンガポールなら1時間のズレで済むが、もっと遠いところにも日本人は住んでいるし、進む国際化で海外に住む日本人の数は将来増えはすれど減ることはないだろうから、マイナーケースとしてただ放っておくというのも気が引ける話である。また、「日本人ならどこの国に居てもタイムゾーンを日本に設定していろ」っていうのも横暴な話だ。

これについては Ajaxで初めて出現した問題というわけでもなく、Apache(旧Adobe) FlexのようなRIAプラットフォームでも頭の痛い問題だった(ActionScriptのDate型も JavaScriptのDate型と同じ)。しかしこれについての議論を日本語でそれほど見かけない所をみると、国内で時差のあるアメリカ等と違って日本では問題の存在自体にほとんど気付かれていないのではないかと思う。

解決方法

この問題は、JSONのシリアライザ・デシリアライザが日付時刻型(クライアント側では JavaScriptの Date、サーバ側ではその言語の日付時刻型)を正しく(彼我の時差を考慮して)処理してしまうために起こるので、通信のさいに日付時刻型を使わずよりナイーブに文字列や数字の羅列(”2014-03-17 01:23:45” や [2014,3,17,1,23,45] のような)で時間を表現すれば解決する。するのだが、例えば AngularJSと UI Bootstrapで日付と時刻の入力をする で紹介している Datepickerのような部品は入出力に Dateオブジェクトを使うし、サーバ側でもその言語のネイティブな Date型を使ってロジックを記述する方が簡潔かつ演算も容易でバグも少なく出来るため Date型を使うこと自体をやめるのは賢明とは言いがたい。とはいえ通信するたびにいちいち文字列や数値の配列との相互変換を記述するというのは避けられるなら避けたいので透過的な変換を模索することになる。それは、クライアント側・サーバ側の JSONシリアライザ・デシリアライザがそれぞれどういうもので、どうカスタマイズ出来るかという個別の話になるだろう。(どんな場合でもこうすればいい、という銀の弾丸は多分今のところない)

下記、わかっていること

JSONには日付時刻型のリテラルは存在しないが、大抵のシリアライザ・デシリアライザは整数(エポックミリ秒)表現やISO 8601のような文字列表現で代用する。シリアライズ・デシリアライズ時には多くの場合UTCとローカル時刻の変換処理が挟まるため、これが(正しい処理であるにも関わらず)問題となる。

これに対して取り得る一つの方針は、サーバ・クライアントの両端点において日付時刻の情報を一貫して「ローカル時刻として」シリアライズ・デシリアライズするように措置するというのがある。通信プロトコルのセオリーに反するが、なんであれ一度UTCを介すようなことをすれば元々ユーザーが意図したのが何日何時だったかという情報はその時点で失われてしまうため仕方ないし、これについてそもそも論をしても建設的な結論に辿り着きそうもない。

JavaScriptのDateオブジェクトはコンストラクタに "yyyy/MM/dd HH:mm:ss" 形式の文字列を与えてやればそれをローカル時刻として解釈しインスタンス化してくれる。

AngularJSは Dateオブジェクトをシリアライズする際必ず UTCに変換してしまう。この挙動をカスタマイズする方法はまだつかめていないので、私は仕方なく $filter("date")(date, "yyyy/MM/dd HH:mm:ss") を使ってDateオブジェクトをローカル時刻を示す文字列に都度変換している。

サーバーサイドのJSONパーサーとして Jacksonを使っている場合、java.util.Date系のシリアライズ・デシリアライズにデフォルトで用いられるDateFormatは必ずUTCとして日付時刻文字列を解釈/出力してしまう。仕方ないので ObjectMapper.setDateFormat(new SimpleDateFormat("yyyy/MM/dd HH:mm:ss")) として DateFormatオブジェクトを 上書きしてやることでローカル時刻扱いで日付時刻文字列を入出力してくれるようになる。

同じカテゴリの記事

Angular.JSでselect要素にoptionをぶら下げる色々な方法 2014年3月29日
AngularJSで、時間のかかる通信の間にモーダルを使ってプログレスバーを表示する 2014年3月18日
AngularJSの $resourceを使って application/x-www-form-urlencoded 形式のリクエストを POSTする 2014年3月15日
AngularJSと UI Bootstrapで日付と時刻の入力をする 2014年3月14日

お勧めカテゴリ

英語でアニメ観ようず
なじみ深い日本製アニメの英語版DVDで、字幕と音声から英語を学びましょうという趣旨のシリーズ記事です。
ScalaのようでJavaだけど少しScalaなJSON API
Scalaと Spring Frameworkを使って REST的なJSON APIを実装してみましょう。
ドクジリアン柔術少女 すから☆ぱいそん
代表 嶋田大貴のブログです。写真は神仏に見せ金をはたらく罰当たりの図