Androidで日付表記をお手軽に国際化する

こんにちは、LINEでAndroid clientを開発しているShojiです。何故か一部からはビルド王子と呼ばれています。この記事はLINE Advent Calendar2016の5日目の記事です。

Androidアプリの日付表記の国際化

せっかく頑張ってコードを書いて、テストしたAndroidアプリなら海外含めて沢山の人に使って欲しいですよね?

LINEは海外でも使われているのでUIテキストの翻訳をするのは勿論ですが、アプリの国際化はUIの翻訳に限りません。特にLINEはアプリの性格上日付表示がUI上に多く、このフォーマットを各言語文化にあった形で表示する必要があります。

同じ英語でも、皆さんも中学校の英語の授業できっと習ったように、

  • イギリス式: Fri, 18 Nov 2016
  • アメリカ式: Fri, Nov 18, 2016

と月日の順序が違ったりします。 アメリカ英語とイギリス英語ぐらいなら各言語用のフォーマットを用意して、こんな風にRクラス経由で

new SimpleDateFormat(getString(R.string.ui_date_format));

対応することも出来なくは無いですが、対応言語が増えると果たして出力されたテキストがネイティヴにとって自然かの検証が微妙な感じです。海外製アプリだと有名どころでも日本語設定で「11月 16, 2016」と表示されてしまったり、それがデザインが良い感じ風なAndroid WearのWatch faceだったりするともう残念感がハンパないです。せっかくMoto 360 2nd gen買ったのに・・・!

android.text.format.DateUtils

そこでandroid.text.format.DateUtilsの出番です。

https://developer.android.com/reference/android/text/format/DateUtils.html

DateUtils.formatDateTime(context, System.currentTimeMillis(), FORMAT_SHOW_YEAR | FORMAT_SHOW_DATE | FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL) 

で、Android端末の言語設定を切り替えると

  • イギリス英語: Fri, 18 Nov 2016
  • アメリカ英語: Fri, Nov 18, 2016
  • 日本語: 2016年11月18日(金)

と良い感じに出力してくれます。「2016/11/18」のような表記が欲しい場合は、

DateUtils.formatDateTime(context, System.currentTimeMillis(), FORMAT_SHOW_YEAR | FORMAT_SHOW_DATE | FORMAT_NUMERIC_DATE)

FORMAT_NUMERIC_DATEを指定すれば

  • イギリス英語: 18/11/2016
  • アメリカ英語: 11/18/2016
  • 日本語: 2016/11/18

と、こちらもちゃんとイギリス英語とアメリカ英語の慣習の違いを反映してくれます。

ただし、Android APIなのでOS versionよって微妙に動作が異なる事があり、例えば、

DateUtils.formatDateTime(context, System.currentTimeMillis(), FORMAT_SHOW_YEAR | FORMAT_SHOW_DATE | FORMAT_SHOW_WEEKDAY | FORMAT_ABBREV_ALL)

で、

  • Android 6.0: 2016年11月18日(金)
  • Android 4.1: 2016/11/18 (金)
DateUtils.formatDateTime(context, System.currentTimeMillis(), FORMAT_SHOW_YEAR | FORMAT_SHOW_DATE | FORMAT_SHOW_WEEKDAY)

で、

  • Android 6.0: 2016年11月18日金曜日
  • Android 4.1: 2016年11月18日 (金)

と挙動にぶれがあります(エミュレータにて動作確認)。小さいTextViewに表示する際にはOS versionによって見切れてしまっていないかなどの検証をしましょう。

DateUtilsにはformatDateTime以外にも「5分前」や「3日前」などを各言語で出力する、SNSのFeed向けなgetRelativeTimeSpanStringなどもあります。

DateUtils.getRelativeTimeSpanString(THREE_DAYS_AGO_IN_MILLISECONDS)

で出力すると、

  • 日本語: 3 日前
  • 英語: 3 days ago
  • フランス語: Il y a 3 jours

と日本人プログラマがつい忘れがちな複数形も自動的に処理できます。

さらに挙げれば、日本語では「2016年に」も、「金曜日に」も、「9時に」も、同じ「に」ですが、英語ではそれぞれ”in 2016″、”on Friday”、”at 9″と前置詞が異なります。(中学校でやりましたね!)

DateUtils.getRelativeTimeSpanString(context, timeInMillis, true /* withPreposition */)

withPrepositiontrueにすると適切な前置詞付きで出力してくれます。

  • 日本語: 11月18日
  • 英語: on Nov 18
  • フランス語: le 18 nov.

写真データの注釈としてや、過去のイベントを表示するUIなどで便利だと思います。

いずれのAPIもOS version間での挙動差分や、ビジネス上アプリで必要な言語をAPIが対応してくれるかの検証は必要ですが、デザイン的な制約が少ない箇所であればお手軽な日付表示の国際化としてオススメです。

Android 7.0 ICU4Jとの関係性

そんなDateUtilsですが、Android 7.0でのICU4Jの導入と共に下記クラスがICU4JでのDateUtilsに対応するクラスとして案内されています。

https://developer.android.com/guide/topics/resources/icu4j-framework.html#migration

  • android.icu.text.DateFormat
  • android.icu.text.RelativeDateTimeFormatter

日付表示の国際化のためにAndroidのコミュニティではDateUtilsを、ICU4Jのコミュニティでは上記クラスをそれぞれ育ててきたのが、今回のAndroid 7.0でどちらのクラスも利用できるようになったと理解しています。

ICU4JのDateFormatでは例えば、

DateFormat.getDateInstance(DateFormat.RELATIVE_MEDIUM)

でのアルファベット言語での「今日」や「昨日」といった出力が、標準では小文字から始まった単語で出力されるので、文中に埋め込むテキストに利用したりとAndroidのDateUtilsに比べて細かい制御が可能になっています。(setContext(DisplayContext.CAPITALIZATION_FOR_STANDALONE)を利用すれば単独表示用の”Yesterday”で出力できます。)

  • android.text.format.DateUtils.getRelativeTimeSpanString: Yesterday
  • android.icu.text.DateFormat.getPatternInstance: yesterday

Android 7.0以降でより細かい日付操作がしたいというのであればICU4Jを利用できます。

日付表記・時間表記は各国や各言語の文化的なバラエティが多く、プログラマやデザイナが把握して多言語対応な自然な実装をするのは難しいので、API等をうまく使っていきたいですね。

ICU4Jについては前日のMasakuniさんの記事も是非ご参考下さい。

明日は川田さんによる「新卒で LINE Shop チームに入って Elasticserach をいじった話」についての記事です。お楽しみに!

Related Post