Androidアプリ・iOSアプリ開発における日付出力の落とし穴

Androidアプリ開発におけるKotlin(Java)、iOSアプリ開発におけるSwift(Objective-C)共に同様の落とし穴があるため、メモ

# 結論
日付を文字で出力するときの指定で、**年**のフォーマット指定に**大文字**の`Y`を利用すると、年末の一部の日付で意図した年より1年進んでしまう場合があるため、基本的には**小文字**の`y`を利用すべき

# 詳細
小文字`y`は1月1日から新しい年が始まる考え方に基づいて年を算出するものであり、我々が普通利用する方法と一致する
一方で、大文字`Y`は1週間を日曜始まり・土曜終わりとした上で、1月1日が含まれる週を新しい年の第1週とする考えに基づいて年を算出するものである

## 各年末パターンでの違いの例
1月1日が日曜日の場合は、`y`と`Y`で差は出ない

```shell:2016年12月〜2017年1月
Sun Mon Tue Wed Thu Fri Sat
 25  26  27  28  29  30  31
---------------------------- (大文字Yでの年の区切り)
  1   2   3   4   5   6   7
```
この場合、`y`と`Y`いずれにおいても、12月31日までは2016年が出力され、1月1日からは2017年が出力される

一方で、以下のように1月1日が月曜日であった場合は差がでる

```shell:2017年12月〜2018年1月
Sun Mon Tue Wed Thu Fri Sat
 24  25  26  27  28  29  30
---------------------------- (大文字Yでの年の区切り)
 31   1   2   3   4   5   6
```
12月31日について、`y`では2017年が出力されるが、`Y`では**2018年**が出力される

また、以下のように1月1日が土曜日であった場合はもっとも差が大きくなる

```shell:2010年12月〜2011年1月
Sun Mon Tue Wed Thu Fri Sat
 19  20  21  22  23  24  25
---------------------------- (大文字Yでの年の区切り)
 26  27  28  29  30  31   1
  2   3   4   5   6   7   8
```
12月26日から31日について、`Y`では全て**2011年**が出力されることになる

## 各言語での挙動確認
Kotlin・Swiftにて2010年の年末の日付を出力したとき、`y`と`Y`における違いを比較
どちらの言語も、`Y`を指定すると「1月1日が含まれる週がその年の第1週とする」考えに基づいて年が表示されている

```kotlin:Kotlin
val inputDateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.US)
val lowerYDateFormat = SimpleDateFormat("yyyy-MM-dd (EEE)", Locale.US)
val upperYDateFormat = SimpleDateFormat("YYYY-MM-dd (EEE)", Locale.US)

val fetchedStrings = listOf(
        "2010-12-25",
        "2010-12-26",
        "2010-12-27",
        "2010-12-28",
        "2010-12-29",
        "2010-12-30",
        "2010-12-31",
        "2011-01-01",
        "2011-01-02",
        "2011-01-03",
        "2011-01-04",
        "2011-01-05",
        "2011-01-06",
        "2011-01-07"
)

for (fetchedString in fetchedStrings) {
    val parsedDate = inputDateFormat.parse(fetchedString)
    val lowerYDateString = lowerYDateFormat.format(parsedDate)
    val upperYDateString = upperYDateFormat.format(parsedDate)

    println("$lowerYDateString -> $upperYDateString")
}
```

```shell:Kotlinの実行結果
2010-12-25 (Sat) -> 2010-12-25 (Sat)
2010-12-26 (Sun) -> 2011-12-26 (Sun)
2010-12-27 (Mon) -> 2011-12-27 (Mon)
2010-12-28 (Tue) -> 2011-12-28 (Tue)
2010-12-29 (Wed) -> 2011-12-29 (Wed)
2010-12-30 (Thu) -> 2011-12-30 (Thu)
2010-12-31 (Fri) -> 2011-12-31 (Fri)
2011-01-01 (Sat) -> 2011-01-01 (Sat)
2011-01-02 (Sun) -> 2011-01-02 (Sun)
2011-01-03 (Mon) -> 2011-01-03 (Mon)
2011-01-04 (Tue) -> 2011-01-04 (Tue)
2011-01-05 (Wed) -> 2011-01-05 (Wed)
2011-01-06 (Thu) -> 2011-01-06 (Thu)
2011-01-07 (Fri) -> 2011-01-07 (Fri)
```

```swift:Swift
let inputDateFormatter = DateFormatter()
inputDateFormatter.dateFormat = "yyyy-MM-dd"
inputDateFormatter.locale = Locale(identifier: "ja_JP")

let lowerYDateFormatter = DateFormatter()
lowerYDateFormatter.dateFormat = "yyyy-MM-dd (EEE)"
lowerYDateFormatter.locale = Locale(identifier: "ja_JP")

let upperYDateFormatter = DateFormatter()
upperYDateFormatter.dateFormat = "YYYY-MM-dd (EEE)"
upperYDateFormatter.locale = Locale(identifier: "ja_JP")

let fetchedStrings = [
    "2010-12-25",
    "2010-12-26",
    "2010-12-27",
    "2010-12-28",
    "2010-12-29",
    "2010-12-30",
    "2010-12-31",
    "2011-01-01",
    "2011-01-02",
    "2011-01-03",
    "2011-01-04",
    "2011-01-05",
    "2011-01-06",
    "2011-01-07",
]

for fetchedString in fetchedStrings {
    let parsedDate = inputDateFormatter.date(from: fetchedString)!
    let lowerYDateString = lowerYDateFormatter.string(from: parsedDate)
    let upperYDateString = upperYDateFormatter.string(from: parsedDate)

    print("\(lowerYDateString) -> \(upperYDateString)")
}
```

```shell:Swiftの実行結果
2010-12-25 (Sat) -> 2010-12-25 (Sat)
2010-12-26 (Sun) -> 2011-12-26 (Sun)
2010-12-27 (Mon) -> 2011-12-27 (Mon)
2010-12-28 (Tue) -> 2011-12-28 (Tue)
2010-12-29 (Wed) -> 2011-12-29 (Wed)
2010-12-30 (Thu) -> 2011-12-30 (Thu)
2010-12-31 (Fri) -> 2011-12-31 (Fri)
2011-01-01 (Sat) -> 2011-01-01 (Sat)
2011-01-02 (Sun) -> 2011-01-02 (Sun)
2011-01-03 (Mon) -> 2011-01-03 (Mon)
2011-01-04 (Tue) -> 2011-01-04 (Tue)
2011-01-05 (Wed) -> 2011-01-05 (Wed)
2011-01-06 (Thu) -> 2011-01-06 (Thu)
2011-01-07 (Fri) -> 2011-01-07 (Fri)
```

# 参考
- https://ja.wikipedia.org/wiki/ISO_8601#.E5.B9.B4.E3.81.A8.E9.80.B1.E3.81.A8.E6.9B.9C.E6.97.A5
- http://d.hatena.ne.jp/nakamura001/20150102/1420213850