Files
Scarriffle dccf8671b2 Add 'CalendarrKit/' from commit '9ea1a8705d608b1aa476c0b35685c57466bb0c50'
git-subtree-dir: CalendarrKit
git-subtree-mainline: 8bc32faa7f
git-subtree-split: 9ea1a8705d
2026-10-19 05:34:15 +02:00

68 lines
3.3 KiB
Swift

import Foundation
/// How much of the calendar travels to the watch.
///
/// Deliberately narrower than `SnapshotCoverage`: the payload crosses
/// WatchConnectivity on every publish, and a watch complication never needs six
/// weeks of history to answer "what is next".
public enum WatchCoverage {
/// Days before today. One is enough for "still running" all-day events.
public static let daysBehind = 1
/// Days after today.
public static let daysAhead = 14
/// Hard cap on events sent, applied after sorting by start date.
public static let maxEvents = 120
}
extension CalendarrSnapshot {
/// A narrower copy of this snapshot, suitable for sending to the watch.
///
/// The coverage window shrinks together with the events, and never widens
/// past what the writer already claimed. If `WatchCoverage.maxEvents`
/// truncates the list, `coverageEnd` is pulled back to the first event we
/// had to drop: everything before that point is provably complete, and
/// beyond it the watch must not pretend to know anything.
public func trimmedForWatch(now: Date = Date(),
calendar: Calendar = Calendar(identifier: .gregorian)) -> CalendarrSnapshot {
let dayStart = calendar.startOfDay(for: now)
let windowStart = max(coverageStart,
calendar.date(byAdding: .day, value: -WatchCoverage.daysBehind, to: dayStart) ?? dayStart)
let windowEnd = min(coverageEnd,
calendar.date(byAdding: .day, value: WatchCoverage.daysAhead, to: dayStart) ?? dayStart)
guard windowEnd > windowStart else {
return Self.rebuilt(from: self, start: windowStart, end: windowStart, events: [])
}
let inWindow = events
.filter { $0.start < windowEnd && $0.end > windowStart }
.sorted { $0.start < $1.start }
guard inWindow.count > WatchCoverage.maxEvents else {
return Self.rebuilt(from: self, start: windowStart, end: windowEnd, events: inWindow)
}
let kept = Array(inWindow.prefix(WatchCoverage.maxEvents))
// The first dropped event marks the edge of what we can prove. Its start
// becomes the new end of coverage; the last kept event is dropped too
// when it starts at that same instant, which keeps the invariant
// "every event we carry starts before coverageEnd" exactly true.
let provenEnd = max(windowStart, min(windowEnd, inWindow[WatchCoverage.maxEvents].start))
return Self.rebuilt(from: self, start: windowStart, end: provenEnd,
events: kept.filter { $0.start < provenEnd && $0.end > windowStart })
}
private static func rebuilt(from source: CalendarrSnapshot,
start: Date, end: Date,
events: [SnapshotEvent]) -> CalendarrSnapshot {
CalendarrSnapshot(writtenAt: source.writtenAt,
coverageStart: start,
coverageEnd: end,
isLoggedIn: source.isLoggedIn,
writerVersion: source.writerVersion,
events: events,
theme: source.theme,
language: source.language)
}
}