@@ -71,27 +71,63 @@ extension Calendar {
7171 let start : Date
7272 /// The recurrenece rule
7373 let recurrence : RecurrenceRule
74- /// Range in which the search should occur. If `nil`, return all results
75- let range : Range < Date > ?
74+ /// The lower end of the search range. If `nil`, the search is unbounded
75+ /// in the past.
76+ let lowerBound : Date ?
77+ /// The upper end of the search range. If `nil`, the search is unbounded
78+ /// in the future. If `inclusive` is true, `bound` is a valid result
79+ let upperBound : ( bound: Date , inclusive: Bool ) ?
7680
7781 init ( start: Date , recurrence: RecurrenceRule , range: Range < Date > ? ) {
7882 self . start = start
7983 self . recurrence = recurrence
80- self . range = range
84+ if let range {
85+ self . lowerBound = range. lowerBound
86+ self . upperBound = ( range. upperBound, false )
87+ } else {
88+ self . lowerBound = nil
89+ self . upperBound = nil
90+ }
91+ }
92+
93+ init ( start: Date , recurrence: RecurrenceRule , range: ClosedRange < Date > ) {
94+ self . start = start
95+ self . recurrence = recurrence
96+ self . lowerBound = range. lowerBound
97+ self . upperBound = ( range. upperBound, true )
98+ }
99+
100+ init ( start: Date , recurrence: RecurrenceRule , range: PartialRangeFrom < Date > ) {
101+ self . start = start
102+ self . recurrence = recurrence
103+ self . lowerBound = range. lowerBound
104+ self . upperBound = nil
105+ }
106+
107+ init ( start: Date , recurrence: RecurrenceRule , range: PartialRangeThrough < Date > ) {
108+ self . start = start
109+ self . recurrence = recurrence
110+ self . lowerBound = nil
111+ self . upperBound = ( range. upperBound, true )
112+ }
113+
114+ init ( start: Date , recurrence: RecurrenceRule , range: PartialRangeUpTo < Date > ) {
115+ self . start = start
116+ self . recurrence = recurrence
117+ self . lowerBound = nil
118+ self . upperBound = ( range. upperBound, false )
81119 }
82120
83121 struct Iterator : Sendable , IteratorProtocol {
84122 /// The starting date for the recurrence
85123 let start : Date
86124 /// The recurrence rule that should be used for enumeration
87125 let recurrence : RecurrenceRule
88- /// The range in which the sequence should produce results
89- let range : Range < Date > ?
90-
91- /// The lower bound of `range`, adjusted so that date expansions may
92- /// still fit in range even if this value is outside the range. This
93- /// value is used as a lower bound for ``nextBaseRecurrenceDate()``.
94- let rangeLowerBound : Date ?
126+
127+ /// The lower bound for iteration results, inclusive
128+ let lowerBound : Date ?
129+ /// The upper bound for iteration results and whether it's inclusive
130+ let upperBound : ( bound: Date , inclusive: Bool ) ?
95131
96132 /// The start date's nanoseconds component
97133 let startDateNanoseconds : TimeInterval
@@ -105,6 +141,10 @@ extension Calendar {
105141 /// date, by the interval specified by the recurrence rule frequency
106142 /// This does not include the start date itself.
107143 var baseRecurrence : Calendar . DatesByMatching . Iterator
144+ /// The lower bound for `baseRecurrence`. Note that this date can be
145+ /// lower than `lowerBound`
146+ let baseRecurrenceLowerBound : Date ?
147+
108148
109149 /// How many elements we have consumed from `baseRecurrence`
110150 var iterations : Int = 0
@@ -123,7 +163,8 @@ extension Calendar {
123163
124164 internal init ( start: Date ,
125165 matching recurrence: RecurrenceRule ,
126- range: Range < Date > ? ) {
166+ lowerBound: Date ? ,
167+ upperBound: ( bound: Date , inclusive: Bool ) ? ) {
127168 // Copy the calendar if it's autoupdating
128169 var recurrence = recurrence
129170 if recurrence. calendar == . autoupdatingCurrent {
@@ -132,7 +173,6 @@ extension Calendar {
132173 self . recurrence = recurrence
133174
134175 self . start = start
135- self . range = range
136176
137177 let frequency = recurrence. frequency
138178
@@ -215,10 +255,12 @@ extension Calendar {
215255 secondAction = . expand
216256 }
217257
218- if let range {
219- rangeLowerBound = recurrence. calendar. dateInterval ( of: frequency. component, for: range. lowerBound) ? . start
258+ self . lowerBound = lowerBound
259+ self . upperBound = upperBound
260+ if let lowerBound {
261+ baseRecurrenceLowerBound = recurrence. calendar. dateInterval ( of: frequency. component, for: lowerBound) ? . start
220262 } else {
221- rangeLowerBound = nil
263+ baseRecurrenceLowerBound = nil
222264 }
223265
224266 // Create date components that enumerate recurrences without any
@@ -330,7 +372,7 @@ extension Calendar {
330372 }
331373 // If a range has been specified, we should skip a few extra
332374 // occurrences until we reach the start date
333- if let rangeLowerBound , nextDate < rangeLowerBound {
375+ if let baseRecurrenceLowerBound , nextDate < baseRecurrenceLowerBound {
334376 continue
335377 }
336378 anchor = nextDate
@@ -476,11 +518,18 @@ extension Calendar {
476518 finished = true
477519 return nil
478520 }
479- if let range = self . range {
480- if date >= range. upperBound {
521+ if let upperBound = self . upperBound {
522+ let outOfRange = switch upperBound. inclusive {
523+ case true : date > upperBound. bound
524+ case false : date >= upperBound. bound
525+ }
526+ if outOfRange {
481527 finished = true
482528 return nil
483- } else if date < range. lowerBound {
529+ }
530+ }
531+ if let lowerBound = self . lowerBound {
532+ if date < lowerBound {
484533 continue
485534 }
486535 }
@@ -503,7 +552,7 @@ extension Calendar {
503552 }
504553
505554 public func makeIterator( ) -> Iterator {
506- return Iterator ( start: start, matching: recurrence, range : range )
555+ return Iterator ( start: start, matching: recurrence, lowerBound : lowerBound , upperBound : upperBound )
507556 }
508557 }
509558}
0 commit comments