Edited at
SwiftDay 22

2018年12月31日問題

More than 1 year has passed since last update.

2014年ごろに一度話題になったやつですが、最近また立て続けにその話題を耳にしたのと丁度年の瀬ということで今年も掘り返してみました。

DateFormatterのdateFormatをYYYY-MM-ddとしていると、2017年12月31日に今日の日付が"2018-12-31"と出力されます。

これ自体はSwift関係なくISOの仕様です。先々週Rebuild.fmを聞いていたところ、以前TwitterのAndroidアプリがこの設定ミスによって5時間使えなくなっていた話をしていました。仕様なので当然ですが2017年も健在です。

let datecomponens = DateComponents(year: 2017, month: 12, day: 31)

let calendar = Calendar.current
let date = calendar.date(from: datecomponens)

let f = DateFormatter()
f.locale = Locale(identifier: "en_US_POSIX")
f.dateFormat = "YYYY-MM-dd"

f.string(from: date!) // "2018-12-31"

とかなんとか試していたら、最近こちらのブログでも取り上げられていました。

理由としては、「YYYYは何年の週であるかを表す(Week of Year)」という仕様があり、日曜始まりのLocaleでは2017年12月31日は2018年の最初の週にあたるため、2018と表示されてしまいます。正しく日付を扱うには小文字のyyyyを使う必要があります。

Appleのドキュメントに注意が記載されています。

It uses yyyy to specify the year component. A common mistake is to use YYYY. 

yyyy specifies the calendar year whereas YYYY specifies the year (of “Week of Year”), used in the ISO year-week calendar.
In most cases, yyyy and YYYY yield the same number, however they may be different.
Typically you should use the calendar year.

- Data Formatting Guide - Apple Developers

ということは大丈夫な年もあるということなのですが、実は去年の2016年は正しく出力されます。

let datecomponens = DateComponents(year: 2016, month: 12, day: 31)

let calendar = Calendar.current
let date = calendar.date(from: datecomponens)

let f = DateFormatter()
f.locale = Locale(identifier: "en_US_POSIX")
f.dateFormat = "YYYY-MM-dd"

f.string(from: date!) // "2016-12-31"

というわけで、去年このフォーマットにして正しく動いていた方は注意が必要かもしれません。

そして以前話題になった時に詳細をしっかり書いている記事があったので、実はこの記事はいらなかったのかもしれない。

年末でご多忙のことの存じます。皆様どうぞお体の方もご自愛くださいませ。


参考資料