4
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

iOS強化月間 - iOSアプリ開発の知見を共有しよう -

DateFormatterの和暦バグの解決法を考える

Posted at

はじめに

DateFormatterとは、SwiftでDate型の日付を文字列に変換するときに使用するオブジェクトです。
実はこのDateFormatter、使い方を誤ると思わぬバグを生み出してしまいます
※ 現在時刻は2023/9/30 12:34だとして説明します。

  let date = Date()    // 現在時刻
  let dateFormatter = DateFormatter()
  dateFormatter.dateFormat = "yyyy/M/d HH:mm"
  let r = dateFormatter.string(from: date)
  print(r)    // 2023/9/30 12:34

上記のコード、一見問題なさそうに見えますよね……?
しかし、実行する端末の設定によっては、0005/9/30 12:34と表示されてしまうバグを含んでいるのです。 「設定」アプリを開いて「一般>言語と地域>暦法」を「和暦」に設定した端末でこのコードを実行すると、そのバグを観測できます。
暦法の設定には以下の3種類があり、

  • 西暦(グレゴリオ暦)
  • 和暦
  • タイ仏歴

タイ仏歴に設定した場合も、2566/9/30 12:34という間違った表示になります。
「西暦2023年 = 令和5年 = 仏歴2566年」がこれらの数字の正体です。yyyyに必ずしも西暦が入るとは限らないということですね。

では、yyyyを間違いなく西暦にするには? 実機で色々と試して調べてみました。

解決法1 - Localeを設定する方法

DateFormatterは、何も設定しなければ端末の設定が反映されてしまいますが、Localeを設定することで表示形式を指定することができます。

  let date = Date()    // 現在時刻
  let dateFormatter = DateFormatter()
+ dateFormatter.locale = Locale(identifier: "ja_JP")
  dateFormatter.dateFormat = "yyyy/M/d HH:mm"
  let r = dateFormatter.string(from: date)
  print(r)    // 2023/9/30 12:34

Localeは、言語や地域に関する情報を持っている構造体で、ja_JPは「年は西暦で表現する」という情報を持っています。これを設定することで、和暦設定の端末でも西暦で表示することができます。

解決法2 - Calendarを設定する方法

さらに調べると、次のような方法も見つかりました。

  let date = Date()    // 現在時刻
  let dateFormatter = DateFormatter()
+ dateFormatter.calendar = Calendar(identifier: .gregorian)
  dateFormatter.dateFormat = "yyyy/M/d HH:mm"
  let r = dateFormatter.string(from: date)
  print(r)    // 2023/9/30 12:34

Calendar構造体は、暦法に関する情報を持っていて、.gregorianで西暦、.japaneseで和暦、.buddhistで仏歴の情報を持たせることができます。
これを設定することでも、和暦設定の端末でも西暦で表示することができます。

比較検討

何も設定してないDateFormatterと、Localeと、Calendarを比較するためのコードを書きました。

  for calendar in [DateFormatter().calendar!,
                   Locale(identifier: "ja_JP").calendar,
                   Calendar(identifier: .gregorian)] {
      print("identifier: \(calendar.identifier), lacale: \(calendar.locale!), timeZone: \(calendar.timeZone)")
  }

「一般>日付と時刻>時間帯」を「ニューヨーク、米国」に、暦法を「和暦」に設定した端末で、実行してみました。

無設定 Locale Calendar
identifier japanese gregorian gregorian
locale ja_JP@calendar=japanese (current) ja_JP (fixed) (fixed)
timeZone America/New_York (fixed (equal to current)) America/New_York (fixed (equal to current)) America/New_York (fixed (equal to current))

Localeをja_JPに設定したらタイムゾーンが強制的に東京になったりするかなと思ってニューヨークに設定してみましたが、そういうこともなさそうなので、これを見る限りLocaleとCalendarの間に特に差はなさそうです。
コードを見て意図を理解しやすいという点で言えば、Calendarを使うのが無難そうです。

結論

DateFormatterを使用する時は、和暦バグ防止のために、以下の設定をつけよう!

dateFormatter.calendar = Calendar(identifier: .gregorian)

以上です。

参考文献

4
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
7

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?