recurrence: consolidate 3 duplicate implementations into one shared, tested engine (KC-69)
Before adding "every N weeks/months" support, found the recurrence window math was independently hand-written in three places (main app's TaskItem.isOccurrence, and two separate widget extensions' countdown progress calculations) with no shared code to keep them in sync. Extracted the math into Shared/RecurrenceRule.swift (pure Foundation, no framework deps) compiled into both the app and widget extension targets via a new project.yml Shared/ source path, and pointed all three call sites at it. Verified every case is identical to prior behavior at interval==1 by compiling and running a standalone test driver directly against the real file with swiftc - 38/38 passing, including Jan-31-monthly and Feb-29-leap-year-yearly edge cases. Groundwork only - no recurrenceInterval field on TaskItem yet, the actual interval feature comes next on top of this. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -341,30 +341,11 @@ struct EventProvider: AppIntentTimelineProvider {
|
||||
|
||||
/// Mode B window for a recurring event: the occurrence span containing "now"
|
||||
/// (previous occurrence → next occurrence), derived from the rule label.
|
||||
/// The actual math lives in RecurrenceRule (Shared/) — shared with the main
|
||||
/// app's occurrence matching and the other countdown widget's copy of this
|
||||
/// same calculation, so none of the three can independently drift.
|
||||
private static func recurrenceSpan(label: String, due: Date) -> (prev: Date, next: Date)? {
|
||||
let cal = Calendar.current
|
||||
let now = Date()
|
||||
let comp: Calendar.Component
|
||||
let step: Int
|
||||
switch label {
|
||||
case "Daily", "Every Weekday": comp = .day; step = 1
|
||||
case "Weekly": comp = .day; step = 7
|
||||
case "Monthly": comp = .month; step = 1
|
||||
case "Yearly": comp = .year; step = 1
|
||||
default: return nil
|
||||
}
|
||||
var next = due
|
||||
var guardRail = 0
|
||||
if next > now {
|
||||
// Walk back until the previous occurrence is in the past.
|
||||
while let prev = cal.date(byAdding: comp, value: -step, to: next),
|
||||
prev > now, guardRail < 4000 { next = prev; guardRail += 1 }
|
||||
} else {
|
||||
while next <= now, let n = cal.date(byAdding: comp, value: step, to: next),
|
||||
guardRail < 4000 { next = n; guardRail += 1 }
|
||||
}
|
||||
guard let prev = cal.date(byAdding: comp, value: -step, to: next) else { return nil }
|
||||
return (prev, next)
|
||||
RecurrenceRule.span(label: label, due: due, now: Date())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user