2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SwiftのDateFormatterに年末の値を渡す時には気を付けよう!

Last updated at Posted at 2024-02-27

概要

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で境界値となる値に変換をかける以下のようなコードを実行してみます。

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年のケースと同様に以下のようなコードを実行し、結果を見てみます。

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: "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

原理をそこまで知らずに使っていたので勉強になりました。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?