前回の記事に引き続き、Web developer の大庭 (@ohbarye) です。
先日『グローバルサービスでのタイムゾーンとの向き合い方』にてタイムゾーンにまつわる諸問題や解決策を1つの記事にまとめました。
しかし同記事はテクニック集としての側面が多く、Quipper の開発者として実際にどのような問題に直面するのかいまいち伝わりづらかったかもしれません。
そこで今回は Quipper の機能に関連するタイムゾーンを考慮した設計と実装の一部をケーススタディ編と題し、改めてご紹介します。前回の記事と一部重複する内容はありますが具体例を交えた復習として参考になれば幸いです。
目次
- 1 宿題管理機能
- モデルイメージ
- 設計に関する問いかけ
- 解答・解説
- 2 TODO 管理機能
- 画面仕様
- 信頼すべきタイムゾーン
- 3 キャンペーン機能
- おわりに
1. 宿題管理機能
Quipper School およびスタディサプリの宿題管理機能は日々の業務で忙しい先生を助けるコア機能の1つです。
モデルイメージ
まず、開始日と終了日を持った Assignment
(宿題) クラスを用意します。
class CreateAssignments < ActiveRecord::Migration def change create_table :assignments do |t| t.datetime :starts_at t.datetime :ends_at end end end
設計に関する問いかけ
この宿題の開始日と終了日を以下のような UI で設定する場合、アプリケーションは入力された日時をどのタイムゾーンの値として受け取るべきでしょうか?
- 登録する先生のクライアント (マシン or ブラウザ) のタイムゾーン
- DB / Rails のデフォルトタイムゾーン (共通のタイムゾーンとする)
- 登録する先生の所属するクラス or 学校のタイムゾーン
解答・解説
ここで入力している先生が日本の先生だと仮定すると、終了日の 2016/11/11
は「2016/11/11 23:59:59 JST
までに宿題を提出してほしい」と意図しているはずです。
1では先生が出張先や休暇旅行先の異なるタイムゾーンで宿題を作ろうとすると意図しない結果になります。例えばロンドンから登録した時に 2016/11/11 23:59:59 UTC
として扱ってしまうと、これは 2016/11/12 08:59:59 JST
になりますから、宿題の期限が約9時間も長く設定されてしまいます。
2も同じ理由で NG です。先生が意図するタイムゾーンとデフォルトタイムゾーンがたまたま一致しない限りずれが生じます。
というわけで Quipper では3を採用し、学校のタイムゾーンを信頼しています。*1永続化の処理は以下のようになります。
def create_assignment! Time.use_zone(current_user.organization.timezone) do starts_at = Time.zone.parse(start_date).beginning_of_day ends_at = Time.zone.parse(end_date).end_of_day Assignment.create!(starts_at: starts_at, ends_at: ends_at) end end
ちなみに、生徒が提出する際に期限切れかどうかを判定する場合はどうなるでしょうか。
class Assignment def expired? Time.zone.now > self.ends_at end end
Rails アプリケーションと DB それぞれのデフォルトタイムゾーンを統一している場合、両辺の値は同じタイムゾーンでの比較になるのでこれだけで OK です。「締め切り2日前」のような条件であれば Time.zone.now > self.ends_at - 2.day
となります。
こうしたタイムゾーンの扱いを誤り、クライアント側のタイムゾーンや値を信頼するような実装をしてしまうと「提出期限切れなのに提出可能になる」ような"チート行為"が出来てしまいます。これは教育アプリケーションとしては致命的で、先生からの信頼を失うことになります。
2. TODO 管理機能
次に、生徒が自分で学習する日付と講座を設定できる TODO 管理機能を見てみましょう。
画面仕様
以下の画面には 2016/11/05 00:00:00 < now < 2016/11/05 23:59:59
であれば 2016/11/05 に「やるぞ!」と設定した講座が表示されなければいけません。
信頼すべきタイムゾーン
この時に使うべきタイムゾーンはユーザーによって異なります。これは色んな考え方があると思いますが、現在採用している例だとユーザーが持つサブスクリプションに紐付くタイムゾーンで判定をしています。
クライアントからリクエストヘッダーにタイムゾーン情報を詰めて送らせるという案もかつてあったのですが、ブラウザ間の差異に悩まされること、デバッグのし辛さ、チートの可能性が増すといった理由から、極力サーバサイドで判定できるよう設計すべき、という結論になりました。
3. キャンペーン機能
最後に、特定の期間だけバナー広告を表示するキャンペーン機能について。 この機能ではタイムゾーンを入力するインターフェースの問題を扱っています。
画面仕様
キャンペーンの登録は非エンジニアでも行えるよう社内向けアプリケーション内に実装されています。期間や対象などの条件を入力し、 Campaign モデルを作成する処理です。
画面の入力インターフェースは以下のようになっています。
タイムゾーンをユーザーに入力してもらう
先生向けのアプリケーションとの最大の違いは、タイムゾーンをユーザー (操作者) に入力させている点です。
一般の先生・生徒に対しての「タイムゾーンを意識して日時を入力してください」という要請はあまりに酷な UI / UX ですが、 Quipper の各国スタッフにとってみれば作成したいキャンペーンがどの国 (タイムゾーン) のユーザー向けなのかは自明なのでまず悩むことなく登録できます。
こうした画面を用意すれば各国ごとにカスタマイズしたアプリケーションを作る必要はありません。
余談ですが、Quipper のプロダクト開発では「この機能は別のプロダクト、別の国で使えないか?」「別の国から見た時にどう見えるか?」といったグローバルな視座が非常に重要です。そしてこの視点から物事を考えるときにもやはりタイムゾーンの考慮が求められます。
リテラシーへの配慮
キャンペーン管理機能にはやや特殊な例でしたが、利用者のリテラシーを考えた日時・タイムゾーンの入力も考慮することも大事です。
もしリテラシーがそれほど高くないユーザー向けに同じことを聞きたいシーンがあるとしたら、質問を「お住まいはどちらですか?」などにしてプルダウンで選択させるのがよいでしょう。
ちなみに rake time:zones:all
で有効なタイムゾーンを確認できるので選択肢はこの中から選ばせるのが望ましいと思います。
おわりに
2回に渡ってタイムゾーンに関する記事を書きましたがいかがだったでしょうか。
日時やタイムゾーン周りの話は地味ではありますがどのアプリケーションにとってもクリティカルなところだと思います。(たとえば課金処理は、1つ間違えればプロダクトへの信用を失うどころか社会的責任を求められるかもしれない減点方式の世界です)
こうした「地味だけどやらなきゃいけない」問題はどの会社も成長拡大する中でいずれ直面するものなので、そこで折れないために何が必要かを多くの人が考えているはずです。
この問に対する答えの一つが「派手さはなくとも当たり前のことを当たり前にやる」という、Quipper がかねてより大事にしてきているモットーだと個人的には思っています。
★Quipper日本オフィスでは仲間を募集しています。是非お気軽にご応募ください。★