[Swift] DateComponentsFormatterやRelativeDateTimeFormatterを使って日付の差分を表示する

## はじめに
先日[[Swift]日付を取得し差分を出す方法](https://qiita.com/yuujioka/items/87437716d23e700eb4d9)と言う記事を書いたが、いやいやもっと標準ライブラリ使って簡単に取得できるよ！って事を知れたので試してみました。

## 環境

・ iOS 13 以上

## DateComponentsFormatter

A formatter that creates string representations of quantities of time.
→時間の量の文字列表現を作成するFormatterです。（公式より翻訳）
時間の表現を作成するなら普段用使用するFormatterと同じでは？と言う感じがしますが、とにかく使用してみる。

```swift
 let time: TimeInterval = 300
            let formatter = DateComponentsFormatter()
            formatter.unitsStyle = .full
            formatter.includesApproximationPhrase = true
            formatter.includesTimeRemainingPhrase = true
            formatter.allowedUnits = [.minute]
            let outputString = formatter.string(from: time)!
            print(outputString)
```
![スクリーンショット 2021-06-06 23.10.13.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517666/475129f4-0b39-147d-0e41-8836b31499e6.jpeg)

おお！約って言うのが気になるがどうやらtimeにある秒数から残りを算出してくれている様子。
続いてtime=330にして下記の書き換えてみる。

```swift
formatter.allowedUnits = [.minute]
```
![スクリーンショット 2021-06-06 23.14.11.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517666/1d1fb582-42e4-3dbd-b548-f964d30594d3.jpeg)
分と秒がしっかり取れていることが確認できます。
またincludesApproximationPhraseは結果の語句が不正確な時間値を反映しているかどうかを示すBool値なので
formatter.includesApproximationPhraseをfalseにすると
![スクリーンショット 2021-06-06 23.18.49.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517666/8c1e99ae-3c12-f637-800e-8611ac3ce1e5.jpeg)
となります。(省略可能)
includesTimeRemainingPhraseは出力文字列が残り時間を反映するかどうかを示すBool値なのでfalseにすると”残り”と言う文字が消えます。

```swift
 let time: TimeInterval = 30000000
            let formatter = DateComponentsFormatter()
            formatter.unitsStyle = .full
            formatter.includesApproximationPhrase = false
            formatter.includesTimeRemainingPhrase = false
            formatter.allowedUnits = [.day, .hour, .minute, .second]
            let outputString = formatter.string(from: time)!
            print(outputString)

```
![スクリーンショット 2021-06-06 23.28.21.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517666/07c23596-292f-80c1-497a-f8b066eb027a.jpeg)
allowedUnitsに追加していく事でそれぞれの日時を取得する事ができます。また０の場合は表示されません。(今回は秒が０秒で表示されていない)

## RelativeDateTimeFormatter

A formatter that creates locale-aware string representations of a relative date or time.
→相対日付または時刻のロケール対応文字列表現を作成するFormatterです。(公式より翻訳)
またiOS13より使用可能となっています。

localizedString(for: Date, relativeTo: Date) -> String
を使用する事でフォーマッタのカレンダーを使用して、参照日から指定された日付までの日付間隔をフォーマットしてくれます。

```swift
struct RelativeDateTimeFormatterTraining: View {
    @State var timeText = ""
    var body: some View {
        VStack{
            Button(action: {
                let targetDate = Date(timeIntervalSinceNow: 300)
                let nowDate = Date(timeIntervalSinceNow: 0)
                let formatter = RelativeDateTimeFormatter()
                formatter.unitsStyle = .abbreviated
                formatter.locale = Locale(identifier: "ja-JP")
                timeText = formatter.localizedString(for: targetDate, relativeTo: nowDate)
            }, label: {
                Text("Button")
            })
            Text(timeText)
        }
    }
}
```
![スクリーンショット 2021-06-07 23.12.05.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517666/b33ea30b-94db-ddf1-f32d-79f4ec777c5f.jpeg)

formatter.localizedString(for: targetDate, relativeTo: nowDate)で
forの部分ではフォーマットする間隔の終了日を、relativeToにはフォーマットする間隔の開始日を入れる事で300秒分の差分を計算して自動で5分後と算出してString型で返してくれます。
その他にも
func localizedString(from: DateComponents) -> String
指定された日付コンポーネントで表される相対時間をフォーマットします。
func localizedString(fromTimeInterval: TimeInterval) -> String
フォーマッタのカレンダーを使用して、指定された時間間隔をフォーマットします。
func string(for: Any?) -> String?
現在の日付と時刻に相対的な日付のフォーマットされた文字列を作成します。
があります。

timeIntervalSinceとlocalizedString(fromTimeInterval: TimeInterval) -> Stringを使用する事でTwitterの様な”〜分前”の様な実装を容易に行うことができます。※ RelativeDateTimeFormatterは文法的に正しくない可能性があります。と公式で一文があるように、一番大きな単位以下の単位は基本切り捨てとなりますのでかなり情報落ちするケースも出てきます。
例えば

```swift
 VStack{
            Button(action: {
                let dateFormat = DateFormatter()
                // 日時をString型で定義
                let textLogDate = "2021-01-01 12:00:00"
                let textNowDate = "2021-01-01 12:05:40"
                // dateFormatを初期設定した"2021-01-01 12:00:00"と同じ形式になるように定義
                dateFormat.dateFormat = "yyyy-MM-dd HH:mm:ss"
                // timeZoneを日本に
                dateFormat.timeZone = TimeZone(identifier: "Asia/Tokyo")
                // String型の日時をDate型に変換
                let logDate = dateFormat.date(from: textLogDate) ?? Date()
                let nowDate = dateFormat.date(from: textNowDate) ?? Date()
                // timeIntervalSinceを使用すると差分を取得できる->nowDate-logDate
                let dateSubtraction: TimeInterval = logDate.timeIntervalSince(nowDate)
                let formatter = RelativeDateTimeFormatter()
                formatter.unitsStyle = .abbreviated
                formatter.locale = Locale(identifier: "ja-JP")
                timeText = formatter.localizedString(fromTimeInterval: dateSubtraction)
                print(timeText)
            }, label: {
                Text("Button")
            })
            Text(timeText)
        }

```
とした場合結果は
![スクリーンショット 2021-06-08 0.14.36.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/517666/dd98aebb-cc0a-59cb-882f-08cb61c268e7.jpeg)
となります。正確には5分40秒前ですが、今回の場合は一番大きな単位は分になりますので秒は切り捨てられます。

## まとめ
とにかくTwitterにある様な”〜分前”みたいな実装をする際にはRelativeDateTimeFormatterは本当に便利なものだが正確に取得したい場合はDateComponentsFormatterを使用した方が良さそうだと感じました。しかし標準ライブラリでここまで簡単に差分を取って、String型で返してくれるのは本当に便利なので今後は活用して行きたいです！