Skip to content

Commit 8455ab0

Browse files
authored
Calendar.RecurrenceRule: Only match weekday components when filtering by weekday (#1020)
When filtering by weekdays, we match the dates against a set of date components. These components happened to contain the hour, minute and second components from the anchor date, so filtering would undo previous expansions by hour, minute and second.
1 parent 78fa839 commit 8455ab0

File tree

2 files changed

+31
-9
lines changed

2 files changed

+31
-9
lines changed

Sources/FoundationEssentials/Calendar/Calendar_Recurrence.swift

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -614,8 +614,7 @@ extension Calendar {
614614
/// weekdays of intereset, or to filter a list of dates
615615
func _weekdayComponents(for weekdays: [Calendar.RecurrenceRule.Weekday],
616616
in parent: Calendar.Component,
617-
anchor: Date,
618-
anchorComponents: DateComponents? = nil) -> [DateComponents]? {
617+
anchor: Date) -> [DateComponents]? {
619618
/// Map of weekdays to which occurences of the weekday we are interested
620619
/// in. `1` is the first such weekday in the interval, `-1` is the last.
621620
/// An empty array indicates that any weekday is valid
@@ -645,9 +644,6 @@ extension Calendar {
645644
} else {
646645
.weekOfYear
647646
}
648-
/// The components we return for matching and enumeration
649-
let componentSet: Calendar.ComponentSet = [.weekday, .hour, .minute, .second]
650-
651647

652648
guard
653649
let interval = dateInterval(of: parent, for: anchor)
@@ -656,7 +652,6 @@ extension Calendar {
656652
lazy var weekRange = range(of: weekComponent, in: parent, for: anchor)!
657653

658654
var result: [DateComponents] = []
659-
let anchorComponents = anchorComponents ?? _dateComponents(componentSet, from: anchor)
660655

661656
lazy var firstWeekday = component(.weekday, from: interval.start)
662657
// The end of the interval would always be midnight on the day after, so
@@ -667,15 +662,15 @@ extension Calendar {
667662
for (weekday, occurences) in map {
668663
let weekdayIdx = weekday.icuIndex
669664
if occurences == [] {
670-
var components = anchorComponents
665+
var components = DateComponents()
671666
components.setValue(nil, for: weekComponent)
672667
components.weekday = weekdayIdx
673668
result.append(components)
674669
} else {
675670
lazy var firstWeek = weekRange.lowerBound + (weekdayIdx < firstWeekday ? 1 : 0)
676671
lazy var lastWeek = weekRange.upperBound - (weekdayIdx > lastWeekday ? 1 : 0)
677672
for occurence in occurences {
678-
var components = anchorComponents
673+
var components = DateComponents()
679674
if occurence > 0 {
680675
components.setValue(firstWeek - 1 + occurence, for: weekComponent)
681676
} else {
@@ -815,7 +810,7 @@ extension Calendar {
815810
if let weekdays = combinationComponents.weekdays {
816811
dates = try dates.flatMap { date, comps in
817812
let parentComponent: Calendar.Component = .month
818-
let weekdayComponents = _weekdayComponents(for: weekdays, in: parentComponent, anchor: date, anchorComponents: comps)
813+
let weekdayComponents = _weekdayComponents(for: weekdays, in: parentComponent, anchor: date)
819814
let dates = try weekdayComponents!.map { comps in
820815
var date = date
821816
if let result = try dateAfterMatchingWeekOfYear(startingAt: date, components: comps, direction: .forward) {

Tests/FoundationEssentialsTests/GregorianCalendarRecurrenceRuleTests.swift

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -739,4 +739,31 @@ final class GregorianCalendarRecurrenceRuleTests: XCTestCase {
739739
XCTAssertEqual(dates.next(), Date(timeIntervalSince1970: 1767484800.0)) // 2026-01-04T00:00:00-0000
740740
XCTAssertEqual(dates.next(), Date(timeIntervalSince1970: 1799020800.0)) // 2027-01-04T00:00:00-0000
741741
}
742+
743+
func testWeekdayFilter() {
744+
var alarms = Calendar.RecurrenceRule(calendar: gregorian, frequency: .daily)
745+
alarms.weekdays = [.every(.monday), .every(.tuesday), .every(.wednesday), .every(.thursday), .every(.friday)]
746+
alarms.hours = [7, 8]
747+
alarms.minutes = [0, 15, 30, 45]
748+
749+
let start = Date(timeIntervalSince1970: 1695304800.0) // 2023-09-21T14:00:00-0000
750+
var results = alarms.recurrences(of: start).makeIterator()
751+
752+
XCTAssertEqual(results.next(), Date(timeIntervalSince1970: 1695366000.0)) // 2023-09-22T07:00:00-0000
753+
XCTAssertEqual(results.next(), Date(timeIntervalSince1970: 1695366900.0)) // 2023-09-22T07:15:00-0000
754+
XCTAssertEqual(results.next(), Date(timeIntervalSince1970: 1695367800.0)) // 2023-09-22T07:30:00-0000
755+
XCTAssertEqual(results.next(), Date(timeIntervalSince1970: 1695368700.0)) // 2023-09-22T07:45:00-0000
756+
XCTAssertEqual(results.next(), Date(timeIntervalSince1970: 1695369600.0)) // 2023-09-22T08:00:00-0000
757+
XCTAssertEqual(results.next(), Date(timeIntervalSince1970: 1695370500.0)) // 2023-09-22T08:15:00-0000
758+
XCTAssertEqual(results.next(), Date(timeIntervalSince1970: 1695371400.0)) // 2023-09-22T08:30:00-0000
759+
XCTAssertEqual(results.next(), Date(timeIntervalSince1970: 1695372300.0)) // 2023-09-22T08:45:00-0000
760+
XCTAssertEqual(results.next(), Date(timeIntervalSince1970: 1695625200.0)) // 2023-09-25T07:00:00-0000
761+
XCTAssertEqual(results.next(), Date(timeIntervalSince1970: 1695626100.0)) // 2023-09-25T07:15:00-0000
762+
XCTAssertEqual(results.next(), Date(timeIntervalSince1970: 1695627000.0)) // 2023-09-25T07:30:00-0000
763+
XCTAssertEqual(results.next(), Date(timeIntervalSince1970: 1695627900.0)) // 2023-09-25T07:45:00-0000
764+
XCTAssertEqual(results.next(), Date(timeIntervalSince1970: 1695628800.0)) // 2023-09-25T08:00:00-0000
765+
XCTAssertEqual(results.next(), Date(timeIntervalSince1970: 1695629700.0)) // 2023-09-25T08:15:00-0000
766+
XCTAssertEqual(results.next(), Date(timeIntervalSince1970: 1695630600.0)) // 2023-09-25T08:30:00-0000
767+
XCTAssertEqual(results.next(), Date(timeIntervalSince1970: 1695631500.0)) // 2023-09-25T08:45:00-0000
768+
}
742769
}

0 commit comments

Comments
 (0)