見出し画像

Now in REALITY Tech #96 iOSの日付フォーマットを正しく理解して、各地域で正しく日付を伝えよう🗓️

質問です。日付の文脈で「7/8」を見たら、いつのことだと思いますか?

日本やアメリカでは7月8日になりますが、イギリスやタイでは8月7日になります。

日付のフォーマットは地域によって違います。そして、ローカライズにおいては避けては通れないトピックです。(ちなみに、各地域がどういうフォーマットを使っているかはこちらのWikiから参照できます。)

ということで、iOSでの日付フォーマットの謎を解明して、世界中のユーザー誰にも正しい日付が伝わるようにしていきましょう!

自己紹介が遅れました。タイ出身のiOSエンジニアの @chuymaster です。最近はローカライズ周りの改善をやっていて、ローカライズおじさんになりつつあります😂

Best Practice

前提として、Swiftの DateFormatter では規定の dateStyle が定義されていて、利用が推奨されています。推奨されたスタイルを使う場合、自動でフォーマットが適用されるので、心配が不要になります。

private func makeDateFormatter(locale: Locale) -> DateFormatter {
    let formatter = DateFormatter()
    formatter.locale = locale
    formatter.dateStyle = .medium //  .full, .long, .medium, .short, .none から指定できる
    return formatter
}

上記のコードの DateFormatter を使った、様々な地域・言語での表示例がこちらです。

dateStyle = .long
「月」がフルで表示される
dateStyle = .medium
「月」が省略で表示される
dateStyle = .short
数字のみになる

カンマの表示や、韓国の「.」表示も変換してくれて優秀ですね。ちなみにタイは基本的に仏暦を使っているので、自動で2024年が2567年に変換され、省略時に67になります。

日付フォーマットをカスタマイズして表示したい場合

実際のアプリケーションでは、規定のフォーマットだけでは都合が悪いので、カスタムフォーマットで表示する必要があります。REALITYでは下記が主な要件です。

  • 直近のイベントのことなので、年表示は不要で、月日だけを表示したい

  • より覚えやすいように、曜日を出したい

同年の開催予定のイベントで、年の表示はしなくても良い
日常的に使うチャットでは曜日感覚が大事

その時に使う関数が setLocalizedDateFormatFromTemplate(_:) です。テンプレートに使える文字列は「Unicode Technical Standard」に登場する「Date Format Pattern」になります。

表示したい日付のコンポーネントを渡せば、ユーザーの言語と地域にあった、年月日の翻訳、順番、区切りをつけて表示してくれます。

// テンプレートの準備
enum Template: String, CaseIterable {
    case Md
    case yMd
    case MMMd
    case yMMMd
    case EMMMd
    case EyMMMd
}

// DateFormatterの作成
private func makeDateFormatter(locale: Locale, template: Template) -> DateFormatter {
    let formatter = DateFormatter()
    formatter.locale = locale
    formatter.setLocalizedDateFormatFromTemplate(template.rawValue)
    return formatter
}

こちらが上記のコードの DateFormatter を使った表示例です。左側が関数に渡した日付フォーマットパターンで、右側が実際に表示された文字列です。

en_US = 英語/アメリカ地域
en_GB = 英語/イギリス地域

上記を見て分かるように、同じ Md テンプレートでも、アメリカは 7/8 で、イギリスは 08/07 で表示されます。テンプレート内の日付フォーマットパターンの順番は関係なく、例えば dM で渡しても正しく変換されるし、 EyMMMd と MMMdEy は同じ表示になります。

ko_KR = 韓国語/韓国地域
「.」が使われている
th_TH = タイ語/タイ地域
年は仏暦で表示される

どのフォーマットを使うかは要件次第です。なお、REALITYでは、まだ移行中の段階ですが、「月」の表示については「M」ではなく「MMM」を採用します。上記の例で分かるように、「M」だと「月」が数字だけになります。d/Mの地域のユーザーであっても、REALITYは日本のアプリだということで、脳内でM/dに変換して「日」と捉えてしまう可能性があります。その点、「MMM」だと月の単語が表示されて間違えようがないので、曖昧さ回避のために採用しました。

言語と地域と日付フォーマットの関係

iOSではアプリ言語と端末の地域を別々で設定できるため、同じ地域でも違う言語を使うユーザーがいます。私もその一人で、日本に住んでいるので地域は日本ですが、基本的にアプリは英語で使っています。

では、言語と地域が違う場合の表示を見てみましょう。例として、月日の順番が日本と違うインドネシアのインドネシア語を取り上げます。

ja_JP = 日本語/日本地域
id_JP = インドネシア語/日本地域

インドネシア地域は「8/7(日/月)」が一般的な表記です。上記の「インドネシア語/日本地域」の例では:

  • Md の場合、表示が「7/8(月/日)」です。日本地域の表示になります。

  • MMMd の場合、表示が「8 Jul(日/月)」です。地域が日本にも関わらず、インドネシア地域のフォーマットでの表示になります。

おそらく、インドネシア語の文字列が入った場合、地域基準ではなく、言語基準でのフォーマットになったのでしょう。確かに自分の言語が入っているのに、月日の表示が逆になるのは不自然ですね。

言語と地域を組み合わせたときの挙動は私はまだまだ全部把握できていませんが、この事例からでも、やはり「M」ではなく「MMM」でフォーマットした方が、月と日が正しく伝わると言えるでしょう。

おまけ:和暦とタイ仏暦のフォーマット

ところで、iOSではカレンダーを和暦とタイ仏暦に変更できることをご存知でしょうか?「設定>言語と地域>暦法」から変更ができます。

暦法の変更

この場合、地域ID(localeIdentifier)に @calendar=japanese / @calendar=buddhist が付きますが、テンプレートを使って日付をフォーマットすれば、特に気にすることなく、自動で暦を表示してくれます。

ja_JP@calendar=japanese = 日本語/日本地域/和暦
ja_JP@calendar=buddhist = 日本語/日本地域/タイ仏暦

年の表示が少し長くなるので、たまには適用してみて、UI崩れがないかどうかを確認するといいかもしれません。

まとめ

「Unicode Technical Standard」の「Date Format Pattern」による日付フォーマットは、iOSでもAndroidでも使用できます。表示はOSによって少し違う場合がありますが、OSに任せるのがローカライズ対応における王道だと思います。また、カスタムなフォーマットを使用する場合、「月」に関しては「M」よりも「MMM」の方が正確に日付を伝えることができると考えます。ぜひ参考にしてください。

最後に、何があろうと、決して「yyyy年MM月dd日」の固定フォーマットを日付表示に適用してはいけません!!!