Skip to content

Commit 6577f3a

Browse files
authored
fix Calendar.RecurrenceRule (#1284)
1 parent ea9e9e2 commit 6577f3a

File tree

2 files changed

+24
-0
lines changed

2 files changed

+24
-0
lines changed

Sources/FoundationEssentials/Calendar/Calendar_Recurrence.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ extension Calendar {
9393
/// value is used as a lower bound for ``nextBaseRecurrenceDate()``.
9494
let rangeLowerBound: Date?
9595

96+
/// The start date's nanoseconds component
97+
let startDateNanoseconds: TimeInterval
98+
9699
/// How many occurrences have been found so far
97100
var resultsFound = 0
98101

@@ -232,6 +235,8 @@ extension Calendar {
232235
}
233236
var componentsForEnumerating = recurrence.calendar._dateComponents(components, from: start)
234237

238+
startDateNanoseconds = start.timeIntervalSince1970.remainder(dividingBy: 1)
239+
235240
let expansionChangesDay = dayOfYearAction == .expand || dayOfMonthAction == .expand || weekAction == .expand || weekdayAction == .expand
236241
let expansionChangesMonth = dayOfYearAction == .expand || monthAction == .expand || weekAction == .expand
237242

@@ -422,6 +427,13 @@ extension Calendar {
422427
recurrence._limitTimeComponent(.second, dates: &dates, anchor: anchor)
423428
}
424429

430+
if startDateNanoseconds > 0 {
431+
// `_dates(startingAfter:)` above returns whole-second dates,
432+
// so we need to restore the nanoseconds value present in the original start date.
433+
for idx in dates.indices {
434+
dates[idx] += startDateNanoseconds
435+
}
436+
}
425437
dates = dates.filter { $0 >= self.start }
426438

427439
if let limit = recurrence.end.date {

Tests/FoundationEssentialsTests/GregorianCalendarRecurrenceRuleTests.swift

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -803,4 +803,16 @@ final class GregorianCalendarRecurrenceRuleTests: XCTestCase {
803803
]
804804
XCTAssertEqual(results, expectedResults)
805805
}
806+
807+
func testDailyRecurrenceRuleWithNonzeroNanosecondComponent() {
808+
let start = Date(timeIntervalSince1970: 1746627600.5) // 2025-05-07T07:20:00.500-07:00
809+
let rule = Calendar.RecurrenceRule.daily(calendar: gregorian, end: .afterOccurrences(2))
810+
let results = Array(rule.recurrences(of: start))
811+
812+
let expectedResults: [Date] = [
813+
start,
814+
Date(timeIntervalSince1970: 1746714000.5), // 2025-05-08T07:20:00.500-07:00
815+
]
816+
XCTAssertEqual(results, expectedResults)
817+
}
806818
}

0 commit comments

Comments
 (0)