ISO8601DateFormatter：iOS（Swift）で日付・時刻を ISO 8601 形式にして扱う

iOS（Swift） で日付・時刻を ISO 8601 形式で扱う際に `ISO8601DateFormatter` を使ったのでまとめました。

# ISO 8601 とは

[ウィキペディア：ISO 8601](https://ja.wikipedia.org/wiki/ISO_8601)

> ISO 8601 は日付・時刻の表記に関する国際規格。
>
> | 基本形式 | 20180904T161400+0900 |
> |:--|:--|
> | 拡張形式 | 2018-09-04T16:14:00+09:00 |

# `ISO8601DateFormatter` とは

https://developer.apple.com/documentation/foundation/iso8601dateformatter

- 日付・時刻を ISO 8601 形式で扱う（`DateFormatter` クラスの代わりに利用[^1]）
- iOS 10 以降（一部 iOS 11 以降）で利用可能
- デフォルトでは RFC 3339 形式（`"yyyy-MM-dd'T'HH:mm:ssXXXXX"`）

[^1]: https://developer.apple.com/documentation/foundation/dateformatter

# 基本的な使い方

```swift
import Foundation
```

### `Date` から ISO 8601 形式へ変換

```swift
let formatter = ISO8601DateFormatter()
formatter.string(from: date)
// 例: "2018-09-18T02:00:00Z"
```

### ISO 8601 形式から `Date` へ変換

```swift
let formatter = ISO8601DateFormatter()
formatter.date(from: "2018-09-18T02:00:00Z")
```

# :warning: 注意事項

プロパティを再設定すると `CFDateFormatterRef` が再生成されてコストが非常に高くなる可能性があります。

> Please note that there can be a significant performance cost when resetting these properties.  Resetting each property can result in regenerating the entire CFDateFormatterRef, which can be very expensive.

# オプション

https://developer.apple.com/documentation/foundation/iso8601dateformatter/options

デフォルト設定は次のとおりです。（それぞれの説明は [後述](https://qiita.com/jpmartha/items/4edf5ca2e40f3a4c18ce#%E3%81%8A%E3%81%BE%E3%81%91%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3%E4%B8%80%E8%A6%A7)）

- `.withInternetDateTime`
- `.withDashSeparatorInDate`
- `.withColonSeparatorInTime`
- `.withColonSeparatorInTimeZone`


`ISO8601DateFormatter.Options` は `OptionSet` なので次のように追加や削除ができます。

### オプションを追加

```swift
let formatter = ISO8601DateFormatter()
formatter.formatOptions.insert(.withFractionalSeconds)
formatter.string(from: date)
// 例: "2018-09-18T02:00:00.000Z"
```

### オプションを削除

```swift
let formatter = ISO8601DateFormatter()
formatter.formatOptions.remove(.withDashSeparatorInDate)
formatter.string(from: date)
// 例: "20180918T02:00:00Z"
```

# タイムゾーン

https://developer.apple.com/documentation/foundation/iso8601dateformatter/1643185-timezone

デフォルトは GMT[^2] です。

[^2]: [ウィキペディア：グリニッジ標準時](https://ja.wikipedia.org/wiki/%E3%82%B0%E3%83%AA%E3%83%8B%E3%83%83%E3%82%B8%E6%A8%99%E6%BA%96%E6%99%82)

### タイムゾーンを変更

```swift
let formatter = ISO8601DateFormatter()
formatter.timeZone = TimeZone(identifier: "Asia/Tokyo")!
formatter.string(from: date)
// 例: "2018-09-18T11:00:00+09:00"
```

# おまけ：オプション一覧

ISO8601DateFormatter.Options | 例 | 説明
---|---|---
`withYear` | `"2018"` | 年 / ①`withWeekOfYear` 指定 → `YYYY`[^3]　②それ以外 → `yyyy`
`withMonth` | `"09"` | 月 / `MM`
`withWeekOfYear` | `"2018W38"` | 頭文字`W` + 週番号`w`
`withDay` | `"18"` | 日 / ①`withMonth` 指定 → `dd` ②`withWeekOfYear` 指定 → `ee` ③それ以外 → `DDD`
`withTime` | `T02:00:00` | 時刻 / `HH:mm:ss` 
`withTimeZone` | `Z`, `+09:00` | タイムゾーン / `ZZZZZ`
`withSpaceBetweenDateAndTime` | `"2018-09-18 02:00:00Z"` | 日付と時刻の間 `T`の代わりにスペース
`withDashSeparatorInDate` | `"20180918T02:00:00Z"`（削除した場合） | 日付の区切り文字 `-`
`withColonSeparatorInTime` | `"2018-09-18T020000Z"`（削除した場合） | 時刻の区切り文字 `:`
`withColonSeparatorInTimeZone` | `"2018-09-18T11:00:00+0900"`（削除した場合） | タイムゾーンの区切り文字 `:`
`withFullDate` | `"2018-09-18"` | 年月日 / `withYear`, `withMonth`, `withDay`指定と同じ
`withFullTime` | `"02:00:00Z"` | 時・分・秒
`withInternetDateTime` | `"2018-09-18T02:00:00Z"`　| RFC 3339 形式 / `withFullDate`, `withFullTime`, `withDashSeparatorInDate`, `withColonSeparatorInTime`, `withColonSeparatorInTimeZone` 指定と同じ
`withFractionalSeconds` | `"2018-09-18T02:00:00.000Z"` | 秒の小数部（iOS 11 以降）

[^3]: http://www.unicode.org/reports/tr35/tr35-25.html#Date_Field_Symbol_Table

# 参考記事

- [Developers.IO: [iOS 10] ISO8601DateFormatter について](https://dev.classmethod.jp/smartphone/iphone/about-ios-iso8601dateformatter/)
- [Qiita: iOSでISO8601を扱う](https://qiita.com/m__ike_/items/81d84465bb4b9c470131)