プロダクト開発部 Globalプロダクトグループの天貝です。
私の所属の正式名称は Globalプロダクトグループ ですが、開発部では グローバルチーム と呼ばれています。海外のユーザからのフィードバックをもとに、海外のユーザに今以上に価値を訴求できるようなプロダクトを目指して開発を行っています。外国籍のエンジニアも所属しており、勉強会やコードレビューも英語で行っています。
今回は、昨年8月にリリースしたタイムゾーン対応を振り返りながら、
- 今後注意すべきこと
- 勉強になった議論・判断
- 苦労したこと
- 気づいたこと・学んだこと
と、タイムゾーン対応から学んだ既存のプロダクトをグローバル化する難しさや今後意識することを書こうと思います。
Sansanが抱えるグローバル化の課題
Sansanのユーザのうち海外で使ってくださっているユーザの割合はまだまだ少ないです。これは海外のユーザにとって違和感のあるUI/UXであることが要因の一つに挙げられます。
Sansanは『まずは』日本国内ユーザ向けに、と作られたプロダクトのため、UXを改善するためには対応しなければならない課題がたくさんあります。これらに向き合い、改善していくのが今のグローバルチームのミッションです。
タイムゾーン対応とは
以前、海外のユーザからこのようなフィードバックがありました。
- 自分が指定した時間にリマインドやメールが送られない
- 画面で表示される時間が現地の時間じゃない
これらの問題はSansan上の日時が日本時間で扱われていることが問題であり、ユーザの環境に合わせて日時を変換する必要があります。この問題を解決するのがタイムゾーン対応プロジェクトです。
Sansanのユーザ情報設定画面で、タイムゾーンが設定できるようになりました。画面やメールに表示される日時やメール送信・リマインド通知時に設定する日時にタイムゾーンが適用されます。
タイムゾーンとは
普段日本で生活をしていてタイムゾーンを意識する機会はほとんどないと思います。まずは、そもそもタイムゾーンってなに?タイムゾーンが設定できるとどんな良いことがあるの?を簡単に説明します。
タイムゾーンとは共通の標準時間を使う地域のことであり、UTC(協定世界時)との時差で表されます。日本の場合は UTC+0900 であり JST と呼ばれます。
また、サマータイムという制度を導入している地域は季節によってUTCとの時差が変わります。時差だけでなくサマータイムの情報を持っているタイムゾーンをサービスに導入することで、その地域に住んでいるユーザごとに正しい時間を提供できるようになります。
タイムゾーン対応を通して振り返る
■ マスタ情報はどのように決めたのか どのように運用するのか
マスタとなる情報を決める上で、UI/UX・実装・メンテナンスなどのそれぞれの面からメリット・デメリットを挙げ、より良い選択をする必要があります。Sansanでは以下のように検討しました。
▽ UIで提供するタイムゾーンのリスト
「(UTC+09:00) 大阪、札幌、東京」のようなWindowsの「日付と時刻」のリストと同じ形式で選択できるようにしました。既存のユーザの体験を担保するようにタイムゾーンの初期値はJSTにしたため、日本にいるユーザは自身で設定する必要はありません。
UIは他のサービスをいくつか参考にして検討しました。下図の右側のように地域をより具体的に明記する形式の方がユーザは選択しやすい可能性も考えられますが、自発的に設定画面からタイムゾーンを変更しようとするユーザは最低限タイムゾーンについての知識を持っていることを期待し左側の形式にしました。リストが比較的長くならずにユーザが選択しやすいだけでなく、後述しますがWindowsOSのタイムゾーン情報の方がSansanの場合は実装する上で都合がいいです。
いくつかのサービスを比較することはUI/UXの勉強になりますが、データや実装をどうするか逆算して比較してみることで、よりよい選択をする材料にもなりました。
▽ タイムゾーンデータの扱い方
タイムゾーンは様々な理由で新設・変更・廃止される可能性があります。そのためマスタデータのメンテナンスも意識しなければなりません。
DBは下図のように扱うことにしました。
「user」はユーザの情報のマスタテーブル、「timezone」はタイムゾーンのデータテーブルです。「windows_timezone_id」はSansanがWindows上で動いているためこのような命名にしており、TimeZoneInfo.Idの値を格納しています。
Sansanにてタイムゾーンを実装する場合のマスタデータ(windows_timezone_id の値)として、TimeZoneInfo.Id と tz database の2種類が候補に挙がりました。後者は NodaTime などの外部ライブラリを使います。.NETのTimeZoneInfoがWindowsOSのレジストリに依存しているため十分対応が可能であり、また外部ライブラリへの依存を増やしたくないことから、TimeZoneInfo.Id の値をマスタデータにすることにしました。
タイムゾーンを制御しているのは timezone.effective_from と timezone.effective_to です。これらはタイムゾーンの開始日と終了日です。effective_to は基本的にnullですが、もし現在存在するタイムゾーンが廃止するとなった場合は日時を設定し、廃止するタイムゾーンに設定しているユーザがいたらサポートからアナウンスをするなどの対応をする必要があります。
▽ タイムゾーンマスタのメンテナンス
Windowsのタイムゾーン情報が更新される場合は Daylight Saving Time & Time Zone*1が更新されるため、グローバルチームのSlackチャンネルでRSSをサブスクライブしています。タイムゾーンの増減・名称の変更がある場合、合わせてDBやUIで使用するリソースを更新します。Sansanでは定期的にWindowsUpdateを行っているため、そのタイミングでサーバーのタイムゾーンが最新にされます。
■ コード内での日時の扱い方はDateTimeOffset
今回のタイムゾーン対応では、既存の実装でDateTime型の日時はDateTimeOffset型に変換しました。C#ではDateTimeOffset型で扱うことで、ユーザのタイムゾーンに合わせた日時をDBに保存することができます。今後C#で日時を扱う場合はDateTimeではなくDateTimeOffsetを用いるようにしました。
また、タイムゾーンデータの扱い方と日時の入出力時の変換方法についてはグローバルチーム内で知っていればいいことではなく、Sansanの開発者全体で認識を合わせておく必要があります。そのため、実装ガイドラインを全体に展開しています。
■ サマータイムの切り替わり時間には注意する
サマータイムは夏を中心とする時期に標準時を1時間進める制度で、冬には戻されます。タイムゾーン対応をした後リリースするまでにテストをしますが、タイムゾーンの切り替わり前後の挙動については特に注意してテストをする必要があります。日時を操作して検証するのは混乱し苦労しました。
サマータイムの切り替わるタイミングについて簡単に説明します。表示上スキップする時間(Invalid Time)や重複する時間(Ambiguous Time)が存在します。例としてサマータイムを導入しているニューヨークについて考えてみます。
ニューヨークのサマータイムの始まりは3月第2日曜日午前2時、終わりは11月第1日曜日午前2時です。2019年はサマータイムの開始日が3月10日なので、2019/3/10 1:59:59 の1秒後は 2019/3/10 3:00:00 に進められます。一方、終了日は11月3日なので、2019/11/3 1:59:59 の1秒後は 2019/11/3 1:00:00 に戻されます。*2普段は東部標準時(EST)と呼ばれ、UST-5:00ですが、サマータイムの期間は東部夏時間(EDT)になり、UST-4:00です。
■ 影響範囲が広いプロジェクトでは対応する単位を意識する
タイムゾーン対応のプロジェクトでは
- ユーザ情報を持つテーブルにカラムを追加しデフォルトのJSTを設定
- 対応が済んで既存の挙動が担保できた機能を順次リリース
- 今スコープの対象機能がすべてリリースされたらユーザ情報設定画面をリリースし、ユーザに機能を開放
のようにリリースしました。2の工程では機能単位でタイムゾーン対応します。この時、JSTのユーザが既存と同じ体験ができることを確認するリグレッションテストと、ユーザのタイムゾーンを変えた状態での様々なテストをしました。
実装をし始めた当初、機能単位ではなくDBの項目単位で対応する方針でした。その項目を使っている箇所を一括で対応した方が対応漏れを防げるだろうと考えていたためです。しかし、リマインド通知日時という項目を例に考えると、リマインド機能だけでなくコンタクト機能にもリマインドを登録できる箇所があります。このような機能を跨いだ対応はリリースだけでなく実装者の分担やテストにおいても複雑になるため、機能単位で対応することにしました。
2と3の工程を分けることで、ユーザに機能を開放する前に検証を十分行ってから着実にリリースできました。今回のタイムゾーン対応のような対応範囲が広いプロジェクトの場合、実装・検証・リリースについて併せて考えることでプロジェクトの進め方の複雑さが変わり、対応漏れやバグを防ぐことにもつながると実感しました。
直面した問題
タイムゾーン対応は順風満帆というわけではありませんでした。要件定義や実装中に直面した問題とその検討結果について書きます。
日時ではなく日付だけの項目
名刺登録日のような時間の情報を持たない項目はタイムゾーンに合わせて変換できませんでした。既存の項目でこのような時間の情報を持たないものは、暗黙的に0時で計算するか、仕様を変えて時間まで登録できるようにすることでしかタイムゾーン変換できません。
また、検索条件として年月日を指定して検索できる項目があります。UIとしては年月日しか入力できませんが、データを日時まで保存している項目についてはユーザのタイムゾーンに合わせた日付で検索ができます。しかし、時間の情報を持たない項目は登録されている日付をそのまま検索します。これは0時とみなして計算していることになるので、ユーザが求めている結果とは違う結果がになる場合があります。
タイムゾーンによっては日付を跨ぐため、日付のデータは0時とみなして計算するのは正しいことではありません。しかし、例えば名刺登録日の仕様を変えて名刺登録日時にする場合、日時までユーザに登録させるのは大幅にUXを損なうと思われます。また過去のデータは時間の情報を持っていないため救えません。既存のUXを変えずに仕様を変えることができる場合は積極的に対応したいですが、この場合は仕方なく既存実装のままにしています。
±xx:45のタイムゾーンが存在する
下の画面のような10分単位や30分単位のリストをプルダウン形式で選択させるUIを日常的に見かけると思います。
しかし、15分・45分単位のタイムゾーンも存在することをご存知でしょうか。登録画面にて日本(UTC+09:00)で 9:00 と保存したものを、例えば出張でカトマンズ(UTC+05:45)に行ってから編集しようとすると画面には保存した値の 5:45 が表示されるべきです。
これは設計の段階でこのようなタイムゾーンが存在することが考えられていれば防げる問題です。時間を選択するリストは15分単位、もしくは自由記入できる方がいいと思います。
発生頻度を考えると現時点でそこまで対応するほどのものでもないだろうという判断のもと、応急処置として0分か30分でなかった場合は丸めた時間を表示するようにしています。あくまで応急処置であり、今後対応が必要です。
まとめ
サービスを新しく作る時や機能を追加するときにグローバルにも意識を向けて設計できることが最も望ましいですが、現実的には難しいです。どうしても目の前にいる主なユーザ層にとってのUXが最優先になってしまうと思います。Sansanも日本のユーザ向けには今まで様々なUI/UXの改善をしてきましたが、いざ海外のユーザの声を聞いてみると思いもよらない要求が出てきます。この要求を掘り下げていくと根底から認識を変える必要があったり、画面を横断して改修が必要になるような影響範囲の広い改修が必要になる場合もあります。
日時のようなデータはサービスの中で日常的に用いられます。このような意識せずとも日常に存在するデータは地域や文化で扱い方が異なることを見落としがちですが、その日常的な体験が損なわれることは大きなインパクトになるので真摯に向き合う必要があると思います。タイムゾーン対応を通してこれらを改めて考えさせられました。
既存プロダクトのグローバル化はとても地味であり、Sansanユーザの大半を占める日本のユーザには影響がないものばかりです。実際にタイムゾーンを日本以外に設定したユーザは現時点ではごく一部でした。しかし、UI/UXがある程度改善された暁にはSansanは世界中の人に使ってもらえるサービスになりますし、いつか日本のユーザ数を超える日が来るかもしれません。そんな野望を抱きながら地道に海外のユーザ向けの改善を続けていきます。