【Swift】DateComponentsFormatterで「時間の量」を出力する

過去3回の投稿では[拙作カレンダーアプリ](https://firstseed.io/jp/)で利用しているコードを含め、日付に関する操作や出力を説明してきました。

第一回：[Calendarに日付操作のメソッドを追加してみた](https://qiita.com/tamadeveloper/items/8e4f68d586cc5b0f9a5a)
第二回：[DateFormatterで日付や時刻を出力する](https://qiita.com/tamadeveloper/items/b5eff5b43599bba3058a)
第三回：[DateIntervalFormatterで日付や時刻の範囲を出力する](https://qiita.com/tamadeveloper/items/2a0fcef55ee6c3178664)

今回は`DateComponentsFormatter`について説明します。

# `DateComponentsFormatter`とは

このクラスはいわば「時間の量」を表す文字列を出力できます。例えば「2年5か月12日」、「2時間10分28秒」などです。またそれらを縮めて「約2年5か月」「約2時間10分」のような出力もできます。さらに「残り10日」や「残り12分」のような出力もできるようになっています。

# `TimeInterval`を出力する

以下のコードで、310秒を「分」と「秒」で出力できます。

```swift
let time:TimeInterval = 310
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .brief
formatter.allowedUnits = [.minute, .second]
print(formatter.string(from: time))

// 出力：5min 10sec（英語の場合）
// 出力：5分10秒（日本語の場合）
```

後述の`UnitsStyle`を変えることで、例えば英語の場合には「5 minutes, 10 seconds」や「5m 10s」などと表示させることができます。

`includesApproximationPhrase`を使うと「約」、`includesTimeRemainingPhrase`を使うと「残り」を出力できます。

```swift
let time:TimeInterval = 350
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .brief
formatter.includesApproximationPhrase = true
formatter.allowedUnits = [.minute]
print(formatter.string(from: time))

// 出力：About 5min
// 出力：約 5分
```

上記のコードでは350秒が「約 5分」と表示されるので、秒の部分は切り捨てられていることが分かります。

```swift
let time:TimeInterval = 310
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .brief
formatter.includesTimeRemainingPhrase = true
formatter.allowedUnits = [.minute, .second]
print(formatter.string(from: time))

// 出力：5min 10sec remaining
// 出力：残り5分10秒
```

上記はタイマーアプリやダウンロードの残り時間などを表示する場合に使えそうです。

# `DateComponents`を出力する

```swift
var components = DateComponents()
components.month = 2
components.day = 10 
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.allowedUnits = [.month, .day]
print(formatter.string(from: components))

// 出力：2 months, 10 days
// 出力：2か月 10日
```

`DateComponents`の一部を出力できます。`DateFormatter`だと「2月10日」と表示されますが、`DateComponentsFormatter`だと「2か月 10日」となります。

# 2つの`Date`から出力する

```swift
let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.allowedUnits = [.year, .month, .day]
let date1 = Date()
let date2 = Calendar.current.move(date1, byDays:400)
print(formatter.string(from: date1, to: date2))

// 出力：1 year, 1 month, 3 days
// 出力：1年 1か月 3日
```

2つの`Date`の時間差を出力できます。`DateIntervalFormatter`だと「2018年7月14日～2019年8月18日」のような表示になりますが、`DateComponentsFormatter`だと「1年 1か月 3日」となります。

# `UnitsStyle`について

`UnitsStyle`を変えることによって、日にちや時間の出力を変えることができます。

日にちの場合：

| UnitsStyle | 英語 | 日本語 |
|:-----------|:------------------:|:------------------:|
| .spellOut | one year, one month, three days | 一年 一か月 三日 |
| .full     | 1 year, 1 month, 3 days | 1年 1か月 3日 |
| .short    | 1 yr, 1 mth, 3 days | 1年 1か月 3日 |
| .brief     | 1yr 1mth 3days | 1年1か月3日 |
| .abbreviated  | 1y 1mo 3d | 1y1m3d |

時間の場合：

| UnitsStyle | 英語 | 日本語 |
|:-----------|:------------------:|:------------------:|
| .spellOut | one hour, twenty-two minutes, thirty seconds | 一時間 二十二分 三十秒 |
| .full     | 1 hour, 22 minutes, 30 seconds | 1時間 22分 30秒 |
| .short    | 1 hr, 22 min, 30 sec | 1時間 22分 30秒 |
| .brief     | 1hr 22min 30sec | 1時間22分30秒 |
| .abbreviated  | 1h 22m 30s | 1h22m30s |
| .positional   | 1:22:30 | 1:22:30 |

# ローカライズについて

`DateComponentsFormatter`クラスは`DateFormatter`や`DateIntervalFormatter`と違って`locale`プロパティがなく、出力する言語をコードで自由に変えることができません。

`DateComponentsFormatter`クラスはアプリ側が対応している言語で出力される仕様になっています。例えばアプリが日本語にローカライズされており、なおかつシステム言語が日本語に設定されている場合は、日本語で出力されます。アプリが日本語にローカライズされていない場合は、システム言語が日本語だとしても出力は英語となります。