概要
SwiftでDateFormatterに年末の日付を渡したらそんな挙動するの!?ということがあったので記録を残しておきます。
詳細
早速ですが、以下の二つのformatDateTime関数に2022-12-31 00:00:00 +0000
を渡した際の返り値が違うことをご存知でしょうか?
func formatDateTime(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "YYYY-MM-dd"
return formatter.string(from: date)
}
func formatDateTime2(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter.string(from: date)
}
上の関数は2023-12-31
が返され、下の関数は2022-12-31
が返されます。
それぞれ、dateFormat
が
YYYY-MM-dd
であるか、
yyyy-MM-dd
であるかの違いが存在します。
一見同じようなフォーマットに見えますが、明確に振る舞いが異なります。
どういった違いがあるかというと、
-
YYYY
は週の年(Week of Year)を指し、与えられた日付が属する年を返します。 -
yyyy
は暦年を指し、与えられた日付が属する暦年を返します。
週の年とは、ISO 8601の規格で定義されている概念です。
この規格では、
最初の木曜日を含む週が、その年の第1週である。
週の最初の日は、月曜日である[14]。
となっており、その週が属する年を「週の年」と定義しています。
この規格を踏まえた上で具体例を見てみましょう。
例えば、2024年12月31日は2025年1月の週になっています。
Playgroundで境界値となる値に変換をかける以下のようなコードを実行してみます。
import UIKit
func formatDateTime(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "YYYY-MM-dd"
return formatter.string(from: date)
}
func formatDateTime2(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter.string(from: date)
}
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
if let date = dateFormatter.date(from: "2024-12-29 00:00:00") {
print(formatDateTime(date)) // "2024-12-29\n"
print(formatDateTime2(date)) // "2024-12-29\n"
}
if let date = dateFormatter.date(from: "2024-12-30 00:00:00") {
print(formatDateTime(date)) // "2025-12-30\n"
print(formatDateTime2(date)) // "2024-12-30\n"
}
if let date = dateFormatter.date(from: "2024-12-31 00:00:00") {
print(formatDateTime(date)) // "2025-12-31\n"
print(formatDateTime2(date)) // "2024-12-31\n"
}
週の始まりが月曜日、最初の木曜日を含む週が第1週である、と定義されている通り、
"2024-12-29 00:00:00"
をそれぞれの関数に渡した時は二つとも同様の結果を返しましたが、
"2024-12-30 00:00:00"
をそれぞれの関数に渡した時には異なる結果を返しました。
このようなケースではyyyy
を使わないとデータの不整合が生じてしまう可能性があると言えます。
仮にY
(週の年)の状態でリリースし、データが入力された後にこの仕様に気付いたとしましょう。
その場合、2025年12月31日
と入力されているデータは2024年12月31日
と入力したかったのか、それとも2024年12月31日
と入力したかったのかというのは入力者本人しか分からないため、データを修正することが困難、といった状況になりかねません。
ちなみに、どちらのフォーマットを使用しても大丈夫なケースもあります。
例えば、2017年12月31日
を見てみると、こちらは12月の最終週の中に31日まで収まっています。
2024年のケースと同様に以下のようなコードを実行し、結果を見てみます。
import UIKit
func formatDateTime(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "YYYY-MM-dd"
return formatter.string(from: date)
}
func formatDateTime2(_ date: Date) -> String {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd"
return formatter.string(from: date)
}
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
if let date = dateFormatter.date(from: "2017-12-28 00:00:00") {
print(formatDateTime(date)) // "2017-12-28\n"
print(formatDateTime2(date)) // "2017-12-28\n"
}
if let date = dateFormatter.date(from: "2017-12-29 00:00:00") {
print(formatDateTime(date)) // "2017-12-29\n"
print(formatDateTime2(date)) // "2017-12-29\n"
}
if let date = dateFormatter.date(from: "2017-12-30 00:00:00") {
print(formatDateTime(date)) // "2017-12-30\n"
print(formatDateTime2(date)) // "2017-12-30\n"
}
if let date = dateFormatter.date(from: "2017-12-31 00:00:00") {
print(formatDateTime(date)) // "2017-12-31\n"
print(formatDateTime2(date)) // "2017-12-31\n"
}
このような場合は仮にY
(週の年)をフォーマットとして使用しても、yyyy
(暦年)を使用してもきっちりと2017年12月31日
が返されます。
ただ、どのような場合でも対応できることを考えると、特殊な事情がない限りはyyyy
(暦年)を使用することが賢明でしょう。
一通り調べた後にAppleの公式ドキュメントを読んでいたらきっちり書かれていました。
Data Formatting Guide
原理をそこまで知らずに使っていたので勉強になりました。