Swift Digest

timeZone / locale / firstWeekday / minimumDaysInFirstWeek を受け取る Calendar のイニシャライザ

Calendar initializer with time zone, locale, first weekday, and minimum days in first week

Proposal
SF-0040
Authors
Kiel Gillard
Review Manager
Tina Liu
Status
Accepted

このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら

01 何が問題だったのか

CalendartimeZonelocalefirstWeekdayminimumDaysInFirstWeek をデフォルト以外の値で設定したい場合、これまでは識別子を指定したあとに、プロパティを 1 つずつ書き換える必要がありました。

var calendar = Calendar(identifier: .gregorian)
calendar.timeZone = TimeZone(identifier: "Australia/Sydney")!
calendar.locale = Locale(identifier: "en_AU")
calendar.firstWeekday = 2

この書き方には次のような不便がありました。

  • 本来はイミュータブルな値として使いたい場面でも、var で宣言する必要があり、コードの意図が読み取りにくくなっていました。let で受けられないため、後から書き換えられないという保証もコンパイラから得られません。
  • プロパティを 1 つずつ書き換えるたびに、内部の calendar storage が作り直されます。最終的には timeZonelocalefirstWeekdayminimumDaysInFirstWeek の組み合わせで日付計算が決まるにもかかわらず、途中の状態のたびに無駄な初期化が走ります。
  • 各プロパティを順に設定する過程で、意図した最終的な構成とは異なる、半端な状態の Calendar が一時的に存在することになります。

予約や旅行プラン、シフト管理、プロジェクト管理、金融取引、時計やアラームのように、特定のタイムゾーン・ロケール・週の開始曜日をはっきり指定したい場面はアプリ開発でよくあり、テストでも特定の設定を組み合わせたい場面が多くあります。こうした用途では、デフォルト値そのままで使えることはほとんどないため、上記の不便が常に表に出てきていました。

02 どのように解決されるのか

Calendar に、これらのプロパティをまとめて受け取る新しいイニシャライザを追加します。

extension Calendar {
    public init(
        identifier: Identifier,
        timeZone: TimeZone? = nil,
        locale: Locale? = nil,
        firstWeekday: Int? = nil,
        minimumDaysInFirstWeek: Int? = nil
    )
}

これにより、冒頭の例は 1 つの式で書けるようになり、Calendarlet で定数として宣言できます。

let calendar = Calendar(
    identifier: .gregorian,
    timeZone: TimeZone(identifier: "Australia/Sydney"),
    locale: Locale(identifier: "en_AU"),
    firstWeekday: 2
)

新しいイニシャライザは、Calendar の日付計算に影響するすべての公開プロパティをまとめて受け取れるように設計されています。内部では、これらの値を一括して反映するため、プロパティを 1 つずつ書き換えるときに発生していた calendar storage の作り直しは起きません。途中の半端な状態を経由することもなくなります。

timeZonelocalefirstWeekdayminimumDaysInFirstWeek のいずれかに nil を渡したり、引数自体を省略したりした場合は、既存の Calendar(identifier:) で得られるのと同じデフォルト値が使われます。

既存の Calendar(identifier:) との関係

新しいイニシャライザは追加のみなので、既存の Calendar(identifier:) をそのまま使っているコードには影響しません。新旧のイニシャライザはシグネチャが重なる部分があります。

init(identifier: Identifier) // 既存
init(identifier: Identifier, timeZone: TimeZone? = nil, locale: Locale? = nil, firstWeekday: Int? = nil, minimumDaysInFirstWeek: Int? = nil) // 追加

Calendar(identifier: .gregorian) のように identifier だけを渡す呼び出しでは、コンパイラは引き続き既存のイニシャライザを選びます。新しい挙動が必要なときだけ、追加のパラメータを明示的に指定することになります。