partial range で recurrence を検索する
Search for recurrence in partial ranges
このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら↗。
01 何が問題だったのか
SF-0009 で導入された Calendar.RecurrenceRule には、繰り返しイベントの発生日を列挙するための recurrences(of:in:) メソッドがあります。第 2 引数に Range<Date> を渡すことで、その範囲内の発生日だけを取り出せます。
let birthday = Date(timeIntervalSince1970: 813283200.0) // 1995-10-10T00:00:00-0000
let rangeStart = Date(timeIntervalSince1970: 946684800.0) // 2000-01-01T00:00:00-0000
let rangeEnd = Date(timeIntervalSince1970: 1293840000.0) // 2011-01-01T00:00:00-0000
let recurrence = Calendar.RecurrenceRule(calendar: .current, frequency: .yearly)
for date in recurrence.recurrences(of: birthday, in: rangeStart..<rangeEnd) {
// 2000 年から 2010 年までの birthday の発生日
}
しかし、上端あるいは下端だけを指定する partial range(rangeStart... のような形)には対応していませんでした。たとえば「2000 年以降のすべての発生日」を取り出すには、範囲を指定せずに列挙して条件で弾くか、
for date in recurrence.recurrences(of: birthday) where date >= rangeStart {
// 2000 年以降のすべての発生日
}
Date.distantPast や Date.distantFuture を上下端に充てて全範囲扱いにする必要がありました。
for date in recurrence.recurrences(of: birthday, in: rangeStart..<Date.distantFuture) {
// 2000 年以降のすべての発生日
}
これらの書き方は冗長なだけでなく、必要のない範囲についても発生日を計算してしまうため、性能面でも無駄が生じていました。
02 どのように解決されるのか
Calendar.RecurrenceRule の recurrences(of:in:) に、partial range と ClosedRange<Date> を受け取るオーバーロードが追加されます。FoundationPreview 6.3 以降で利用できます。
extension Calendar.RecurrenceRule {
@available(FoundationPreview 6.3, *)
public func recurrences(of start: Date,
in range: PartialRangeThrough<Date>
) -> some (Sequence<Date> & Sendable)
@available(FoundationPreview 6.3, *)
public func recurrences(of start: Date,
in range: PartialRangeTo<Date>
) -> some (Sequence<Date> & Sendable)
@available(FoundationPreview 6.3, *)
public func recurrences(of start: Date,
in range: PartialRangeFrom<Date>
) -> some (Sequence<Date> & Sendable)
@available(FoundationPreview 6.3, *)
public func recurrences(of start: Date,
in range: ClosedRange<Date>
) -> some (Sequence<Date> & Sendable)
}
これにより、「ある日付以降のすべての発生日」を素直に書けるようになります。
for date in recurrence.recurrences(of: birthday, in: rangeStart...) {
// 2000 年以降のすべての発生日
}
...rangeEnd(PartialRangeThrough<Date>)や ..<rangeEnd(PartialRangeTo<Date>)、rangeStart...rangeEnd(ClosedRange<Date>)も同様に渡せます。Date.distantPast や Date.distantFuture を端に詰めたり、列挙後にフィルタしたりする必要はなくなります。
これは単に書き方が短くなるだけでなく、性能面でも有利です。範囲外の発生日を計算してから捨てるのではなく、指定された範囲に必要な分だけを生成するため、不要な計算を省けます。
API としては既存の recurrences(of:in:) にオーバーロードを追加するだけなので、既存コードへの影響はありません。