はじめに
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)
以上です。
参考文献