DateFormatter
に関しては古くからあり、おおよそのプロジェクトで使われているイメージですが、DateComponentsFormatter
やRelativeDateTimeFormatter
(iOS13+なので最近)については体感としてあまり使われておらず、特に歴史のあるプロジェクトでは独自のformatterで実装しているケースが多いので、書いてみました。
全てのケースで使えるわけではないですが、特に多言語対応しているアプリでは、大きく実装量が減って楽にはなるので、選択肢の一つとしてあり得ると思います。
標準のFormatterを使う意義
独自のformatterを利用するのに対して、標準のformatterを使うことの一番のメリットは、言語ごとに追加で実装することなく、他の言語での表記方法に対応できるところです。
これについては、昨年のWWDCの発表が詳しいですが、こちらではdate関連以外のformatterについても紹介されています。
独自のformatterは、一度書いてしまえば動きが変わることが少ない部分ではあるので、運用面では問題ないと思います。
しかし、なんだかんだテストを書いたり、分岐を多く書かないといけないケースもあるので、使える際には標準のものを使いたいところです。
今回のformatterで言えば、TimeInterval
も引数に取れるので、60で割る処理を書かなくて済むケースもあり得ます。
デメリットとしては、標準で用意された表記方法をしたい場合に小回りが利かないという点があます。 例えばあるケースだけ違う表記をしたいという場合は、大体個別に処理を書く必要があります。
使用例
実際にありそうなケースで、DateComponentsFormatter
やRelativeDateTimeFormatter
を使ってみます。
Case 1: 残り時間のカウントダウン
ECアプリのキャンペーンなどでよくある"残り3日"とか"残り8時間"のような表記です。
こちらはDateComponentsFormatter
のみで実装できます。
let formatter = DateComponentsFormatter() formatter.unitsStyle = .full formatter.allowedUnits = [.day, .hour, .minute] formatter.maximumUnitCount = 1 formatter.includesTimeRemainingPhrase = true
unitsStyle
によって単位の表記の方法が変わります。(hours, hrs, hなど)
https://developer.apple.com/documentation/foundation/datecomponentsformatter/unitsstyle
allowedUnits
で指定した日, 時, 分のみが利用され、maximumUnitCount
が1なので一番大きな単位のみが表示されます。
includesTimeRemainingPhrase
をつけることで"残り"の部分も各言語に合わせてつけてくれます。
print(formatter.string(from: 36)!) // 残り0分, 0 minutes remaining print(formatter.string(from: 192)!) // 残り3分, 3 minutes remaining print(formatter.string(from: 10922)!) // 残り3時間, 3 hours remaining print(formatter.string(from: 90000)!) // 残り1日, 1 day remaining
残り時間によって表記が変わるなど、こだわったviewでなければ、これはそのまま使えそうです。
Case 2: 再生時間の表示
動画アプリや音楽アプリの再生時間などである1:52
のような表記です。
言語によらない表記のためメリットが薄いのと、後述の問題があるので、独自実装でも良いケースではありますが、動画や音楽の再生時間や残り再生時間を表示する際もDateComponentsFormatter
が利用できます。
1:52
のような形式で表示する際はpositional
を利用します。
let formatter = DateComponentsFormatter() formatter.unitsStyle = .positional // defaultでpositional formatter.allowedUnits = [.minute, .second]
01:52
のようにゼロ埋めをする場合は、zeroFormattingBehavior
を利用します。
formatter.zeroFormattingBehavior = .pad
print(formatter.string(from: 36)!) // 00:36 print(formatter.string(from: 192)!) // 03:12 print(formatter.string(from: 10922)!) // 182:02
zeroFormattingBehavior
を設定した際は負数を入れた際に、想定した挙動にならないケースがあるので、YouTubeのような-01:52
のような表示をしたい場合は注意が必要です。
print(formatter.string(from: -36)!) // 00:36 (-00:36にはしてくれない) print(formatter.string(from: -192)!) // -03:12
また、0埋めの0の個数を変えられなかったり、"時間は省いて良いが、分は必ず表示させる"ような挙動を実現できなかったりするので、その場合は素直に独自のformatterを使ったり、複数のformatterを組み合わせるのが良さそうです。
Case 3: 投稿時間の相対表記
Instagramなどの投稿型のアプリでよく見る"n分前"や"n日前"といった表記は、RelativeDateTimeFormatter
で実現できます。
let formatter = RelativeDateTimeFormatter() formatter.dateTimeStyle = .named formatter.unitsStyle = .full
dateTimeStyle
をnamed
にすると、"1 day ago", "1 week ago"の代わりに"yesterday", "last week"などの表記をしてくれます。
正負で未来か過去かを判断してくれます。
print(formatter.localizedString(fromTimeInterval: -3)) // 3 秒前, 3 seconds ago print(formatter.localizedString(fromTimeInterval: -192)) // 3 分前, 3 minutes ago print(formatter.localizedString(fromTimeInterval: -10802)) // 3 時間前, 3 hours ago print(formatter.localizedString(fromTimeInterval: -90000)) // 昨日, yesterday
古すぎる場合に日時をそのまま表示をしたいケースでは、別途DateFormatter
と組み合わせることになります。また、DateComponentsFormatter
と違い、allowedUnits
のようなプロパティがないので、"nヶ月前"だけ"n日前"に直すみたいなことができません。
また、文中で使う場合などは、formattingContext
を設定します。
formatter.formattingContext = .beginningOfSentence
この場合Yesterday
のように先頭を大文字にして返してくれます。
Twitterのように"n分"、"n日"の表記であれば、単に先ほどのDateComponentsFormatter
を使うケースも考えられます。
先ほどのカウントダウンとほぼ同じで、
let formatter = DateComponentsFormatter() formatter.unitsStyle = .abbreviated formatter.allowedUnits = [.day, .hour, .minute, .second] formatter.maximumUnitCount = 1
のような設定にすれば、
print(formatter.string(from: 36)!) // 36秒, 36s print(formatter.string(from: 192)!) // 3分, 3m print(formatter.string(from: 10922)!) // 3時間, 3h print(formatter.string(from: 90000)!) // 1日, 1d print(formatter.string(from: 7257600)!) // 84日, 84d
このような結果が得られます。
RelativeDateTimeFormatter
は未来の日時についても"tomorrow", "in n days"などの表記をしてくれるので、そこまでformatterに任せるかで使い分けができそうです。
余談
最近Public PreviewになったVSCode for the Webを使って書きましたが、markdownのプレビューも使えて、デスクトップ版と変わらない操作感で便利でした。