@@ -9,10 +9,10 @@ public struct Cycle: Codable, CustomStringConvertible, Sendable, Identifiable, E
99 /// The earliest reference cycle (not necessarily the earliest cycle for
1010 /// which data is available, but the earliest representable date for a
1111 /// cycle).
12- public static let datum = Self ( year: 2020 , month: 12 , day: 3 )
12+ static let datum = Self ( year: 2020 , month: 12 , day: 3 )
1313
14- /// `true` if this cycle's effectivity period includes the current date .
15- public static var current : Self { effectiveCycle ( for: Date ( ) ) ! }
14+ /// The currently effective cycle.
15+ public static var effective : Self { effectiveCycle ( for: Date ( ) ) ! }
1616
1717 private static var calendar : Calendar {
1818 var calendar = Calendar ( identifier: . gregorian)
@@ -21,7 +21,7 @@ public struct Cycle: Codable, CustomStringConvertible, Sendable, Identifiable, E
2121 }
2222
2323 /// The length of time a cycle is effective for.
24- public static let cycleDuration : DateComponents = . init( day: 28 )
24+ static let cycleDuration : DateComponents = . init( day: 28 )
2525 private static let negativeCycleDuration : DateComponents = . init( day: - 28 )
2626
2727 /// The year of the first date of the cycle.
@@ -43,30 +43,32 @@ public struct Cycle: Codable, CustomStringConvertible, Sendable, Identifiable, E
4343 return dc
4444 }
4545
46- /// A `Date` object representing the first effective day of this cycle.
47- public var date : Date ? { Self . calendar. date ( from: dateComponents) }
46+ /// The effective date of this cycle (midnight UTC when cycle becomes effective) .
47+ public var effectiveDate : Date ? { Self . calendar. date ( from: dateComponents) }
4848
49- /// The last date when this cycle is effective.
50- public var endDate : Date ? {
51- guard let date else { return nil }
52- return Self . calendar. date ( byAdding: Self . cycleDuration, to: date)
49+ /// The expiration date of this cycle (midnight UTC when cycle expires).
50+ ///
51+ /// This is the exact moment the cycle expires, which is also the effective date of the next cycle.
52+ public var expirationDate : Date ? {
53+ guard let effectiveDate else { return nil }
54+ return Self . calendar. date ( byAdding: Self . cycleDuration, to: effectiveDate)
5355 }
5456
5557 /// The range of times when this cycle is effective.
58+ ///
59+ /// The range starts at `effectiveDate` and ends at `expirationDate` (exclusive).
5660 public var dateRange : DateInterval ? {
57- guard let date else { return nil }
58- guard let endDate else { return nil }
59-
60- return DateInterval ( start: date, end: endDate)
61+ guard let effectiveDate, let expirationDate else { return nil }
62+ return DateInterval ( start: effectiveDate, end: expirationDate)
6163 }
6264
6365 /// Whether or not this cycle is currently effective.
6466 public var isEffective : Bool { contains ( Date ( ) ) }
6567
6668 /// The next active cycle following this one.
6769 public var next : Self ? {
68- guard let endDate else { return nil }
69- let components = Self . calendar. dateComponents ( [ . year, . month, . day] , from: endDate )
70+ guard let expirationDate else { return nil }
71+ let components = Self . calendar. dateComponents ( [ . year, . month, . day] , from: expirationDate )
7072 guard let year = components. year,
7173 let month = components. month,
7274 let day = components. day
@@ -76,8 +78,9 @@ public struct Cycle: Codable, CustomStringConvertible, Sendable, Identifiable, E
7678
7779 /// The previously active cycle before this one.
7880 public var previous : Self ? {
79- guard let date else { return nil }
80- guard let prevDate = Self . calendar. date ( byAdding: Self . negativeCycleDuration, to: date) else {
81+ guard let effectiveDate else { return nil }
82+ guard let prevDate = Self . calendar. date ( byAdding: Self . negativeCycleDuration, to: effectiveDate)
83+ else {
8184 return nil
8285 }
8386 let components = Self . calendar. dateComponents ( [ . year, . month, . day] , from: prevDate)
@@ -116,7 +119,7 @@ public struct Cycle: Codable, CustomStringConvertible, Sendable, Identifiable, E
116119 ``datum`` date.
117120 */
118121 public static func effectiveCycle( for date: Date ) -> Self ? {
119- guard var cycle = datum. date else { return nil }
122+ guard var cycle = datum. effectiveDate else { return nil }
120123 guard date >= cycle else { return nil }
121124
122125 var lastCycle = cycle
@@ -157,3 +160,23 @@ public struct Cycle: Codable, CustomStringConvertible, Sendable, Identifiable, E
157160 case year, month, day
158161 }
159162}
163+
164+ extension Cycle : Comparable {
165+ public static func < ( lhs: Cycle , rhs: Cycle ) -> Bool {
166+ if lhs. year != rhs. year { return lhs. year < rhs. year }
167+ if lhs. month != rhs. month { return lhs. month < rhs. month }
168+ return lhs. day < rhs. day
169+ }
170+ }
171+
172+ // MARK: - Factory Methods
173+
174+ extension Cycle {
175+ /// Returns the cycle that contains the given date.
176+ ///
177+ /// - Parameter date: The date to find the cycle for.
178+ /// - Returns: The cycle containing the date, or `nil` if the date is before the datum.
179+ public static func cycle( for date: Date ) -> Self ? {
180+ effectiveCycle ( for: date)
181+ }
182+ }
0 commit comments