Skip to content

Commit 4710fad

Browse files
committed
Add timeInterval(to:) and dateInterval(to:) API to compute date and/or time differences between date intervals."
1 parent 28a5ce8 commit 4710fad

File tree

2 files changed

+232
-0
lines changed

2 files changed

+232
-0
lines changed

Sources/FoundationEssentials/DateInterval.swift

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,139 @@ public struct DateInterval : Comparable, Hashable, Codable, Sendable {
147147
}
148148
return false
149149
}
150+
151+
/**
152+
Returns the seconds between `self` and `date` or `nil` if there is no difference in time between them.
153+
154+
For example, given this interval and a this date on a timeline:
155+
```
156+
|-----| <-- time interval --> *
157+
```
158+
Returns a postive time interval when `date` is a moment greater than or equal to (after or exactly) the end of `self`.
159+
160+
```
161+
* <-- time interval --> |-----|
162+
```
163+
Returns a negative time interval when `date` is a moment less than or equal to (before or exactly) the start of `self`.
164+
165+
A return value of `0` indicates `date` is equal to either the start or end moments of `self`.
166+
167+
A return value of `nil` indicates the `date` is between the start and end dates (`date` is both greater than the start and less than the end moments of `self`):
168+
```
169+
|--*--|
170+
```
171+
*/
172+
public func timeInterval(to date: Date) -> TimeInterval? {
173+
if date <= start {
174+
return date.timeIntervalSince(start)
175+
} else if end <= date {
176+
return date.timeIntervalSince(end)
177+
} else {
178+
return nil
179+
}
180+
}
181+
182+
/**
183+
Returns the date interval between `self` and `date` or `nil` if there is no difference in time between them.
184+
185+
For example, given this interval and a this date on a timeline:
186+
```
187+
* <-- time interval --> |-----|
188+
```
189+
Returns a value whose start is `date` and whose `duration` is the time between the `date` and the end of `self`.
190+
191+
```
192+
|-----| <-- time interval --> *
193+
```
194+
Returns a value whose start is the end of `self` and whose `duration` is the time between the `date` and the the end of `self`.
195+
196+
A return value with a duration of `0` indicates `date` is equal to the start or end of `self`.
197+
198+
A return value of `nil` indicates there are no moments between `date` and `self` (`date` is both greater than the start and less than the end moments of `self`):
199+
```
200+
|--*--|
201+
```
202+
*/
203+
public func dateInterval(to date: Date) -> DateInterval? {
204+
if date <= start {
205+
return DateInterval(start: date, end: start)
206+
207+
} else if end <= date {
208+
return DateInterval(start: end, end: date)
209+
210+
} else {
211+
return nil
212+
}
213+
}
214+
215+
/**
216+
Returns the seconds between `self` and `dateInterval` or `nil` if there is no difference in time between them.
217+
218+
For example, given these two intervals on a timeline:
219+
```
220+
|-----| <-- time interval --> |-----|
221+
```
222+
Returns a negative time interval when `self` ends before `dateInterval` starts. A postive time interval indicates `self` starts after `dateInterval` ends.
223+
224+
A return value of `0` indicates `self` starts or ends where `dateInterval` ends or starts (in other words, they intersect at their opposing start/end moments):
225+
```
226+
|-----|-----|
227+
```
228+
229+
A return value of `nil` indicates `self` and `dateInterval` do not have any time between them:
230+
```
231+
|--|-----|--|
232+
```
233+
*/
234+
public func timeInterval(to dateInterval: DateInterval) -> TimeInterval? {
235+
if end <= dateInterval.start {
236+
return end.timeIntervalSince(dateInterval.start)
237+
238+
} else if dateInterval.end <= start {
239+
return start.timeIntervalSince(dateInterval.end)
240+
241+
} else {
242+
return nil
243+
}
244+
}
245+
246+
/**
247+
Returns the date interval between `self` and `dateInterval` or `nil` if there is no difference in time between them.
248+
249+
For example, given these two intervals on a timeline:
250+
```
251+
|-----| <-- time interval --> |-----|
252+
```
253+
The latest start date and the earliest end date between `self` and `dateInterval` is determined. Returns a date interval whose start is the earliest end date and whose duration is the difference in time between the latest start and earliest end.
254+
255+
A return value with a duration of `0` indicates `self` and `dateInterval` form an unbroken, continous interval (in other words, they intersect at opposing starts/ends):
256+
```
257+
|-----|-----|
258+
```
259+
260+
A return value of `nil` indicates that no interval exists between `self` and `dateInterval`:
261+
```
262+
|--|-----|--|
263+
```
264+
*/
265+
public func dateInterval(to dateInterval: DateInterval) -> DateInterval? {
266+
let earliestEnd: Date
267+
let duration: TimeInterval
268+
269+
if end <= dateInterval.start {
270+
earliestEnd = end
271+
duration = dateInterval.start.timeIntervalSince(end)
272+
273+
} else if dateInterval.end <= start {
274+
earliestEnd = dateInterval.end
275+
duration = start.timeIntervalSince(dateInterval.end)
276+
277+
} else {
278+
return nil
279+
}
280+
281+
return DateInterval(start: earliestEnd, duration: duration)
282+
}
150283

151284
public func hash(into hasher: inout Hasher) {
152285
hasher.combine(start)

Tests/FoundationEssentialsTests/DateIntervalTests.swift

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,105 @@ final class DateIntervalTests : XCTestCase {
5050
let testInterval3 = DateInterval(start: start, duration: 100.0)
5151
XCTAssertNotEqual(testInterval1, testInterval3)
5252
}
53+
54+
func test_intervalsBetweenDateIntervals() {
55+
// Tests for intervals of zero or more duration between subjects.
56+
// |testInterval1|testInterval2|
57+
let testInterval1 = DateInterval(start: Date(timeIntervalSince1970: 0), end: Date(timeIntervalSinceReferenceDate: 0))
58+
let testInterval2 = DateInterval(start: Date(timeIntervalSinceReferenceDate: 0), end: Date(timeIntervalSinceReferenceDate: 100))
59+
let t1 = testInterval1.timeInterval(to: testInterval2)
60+
XCTAssertEqual(t1, 0)
61+
62+
let t2 = testInterval2.timeInterval(to: testInterval1)
63+
XCTAssertEqual(t2, 0)
64+
65+
let d1 = testInterval1.dateInterval(to: testInterval2)
66+
XCTAssertEqual(d1?.start, testInterval1.end)
67+
XCTAssertEqual(d1?.duration, 0)
68+
69+
let d2 = testInterval2.dateInterval(to: testInterval1)
70+
XCTAssertEqual(d2?.start, testInterval1.end)
71+
XCTAssertEqual(d2?.duration, 0)
72+
73+
XCTAssertEqual(d1, d2)
74+
75+
// |testInterval3|-----|testInterval4|
76+
let testInterval3 = DateInterval(start: Date(timeIntervalSince1970: 0), end: Date(timeIntervalSinceReferenceDate: 0))
77+
let testInterval4 = DateInterval(start: Date(timeIntervalSinceReferenceDate: 1), end: Date(timeIntervalSinceReferenceDate: 100))
78+
let t3 = testInterval3.timeInterval(to: testInterval4)
79+
XCTAssertEqual(t3, -1)
80+
81+
let t4 = testInterval4.timeInterval(to: testInterval3)
82+
XCTAssertEqual(t4, 1)
83+
84+
let d3 = testInterval3.dateInterval(to: testInterval4)
85+
let d4 = testInterval4.dateInterval(to: testInterval3)
86+
XCTAssertEqual(d3?.duration, 1)
87+
XCTAssertEqual(d3?.start, testInterval3.end)
88+
XCTAssertEqual(d4?.duration, 1)
89+
XCTAssertEqual(d4?.start, testInterval3.end)
90+
91+
// Tests for non-existing intervals between subjects.
92+
// |testInterval5|
93+
// |testInterval6|
94+
//
95+
// As a single timeline: |555|565656|666|
96+
let testInterval5 = DateInterval(start: Date(timeIntervalSince1970: 0), end: Date(timeIntervalSinceReferenceDate: 0))
97+
let testInterval6 = DateInterval(start: Date(timeIntervalSinceReferenceDate: -1), end: Date(timeIntervalSinceReferenceDate: 100))
98+
let t5 = testInterval5.timeInterval(to: testInterval6)
99+
XCTAssertEqual(t5, nil)
100+
101+
let t6 = testInterval6.timeInterval(to: testInterval5)
102+
XCTAssertEqual(t6, nil)
103+
104+
let d5 = testInterval5.dateInterval(to: testInterval6)
105+
XCTAssertEqual(d5, nil)
106+
107+
let d6 = testInterval6.dateInterval(to: testInterval5)
108+
XCTAssertEqual(d6, nil)
109+
110+
// |---testInterval7---|
111+
// |testInterval8|
112+
//
113+
// As a single timeline: |777|787878|777|
114+
let testInterval7 = DateInterval(start: Date(timeIntervalSince1970: 0), end: Date(timeIntervalSinceReferenceDate: 0))
115+
let testInterval8 = DateInterval(start: Date(timeIntervalSince1970: 10), end: Date(timeIntervalSince1970: 20))
116+
let t7 = testInterval7.timeInterval(to: testInterval8)
117+
XCTAssertEqual(t7, nil)
118+
119+
let t8 = testInterval8.timeInterval(to: testInterval7)
120+
XCTAssertEqual(t8, nil)
121+
122+
let d7 = testInterval7.dateInterval(to: testInterval8)
123+
XCTAssertEqual(d7, nil)
124+
125+
let d8 = testInterval8.dateInterval(to: testInterval7)
126+
XCTAssertEqual(d8, nil)
127+
128+
// |testInterval9|
129+
// |testInterval10---|
130+
let testInterval9 = DateInterval(start: Date(timeIntervalSince1970: 0), end: Date(timeIntervalSinceReferenceDate: 0))
131+
let testInterval10 = DateInterval(start: Date(timeIntervalSince1970: 0), end: Date(timeIntervalSinceReferenceDate: 100))
132+
let t9 = testInterval9.timeInterval(to: testInterval10)
133+
XCTAssertEqual(t9, nil)
134+
135+
let t10 = testInterval10.timeInterval(to: testInterval9)
136+
XCTAssertEqual(t10, nil)
137+
138+
let d9 = testInterval9.dateInterval(to: testInterval10)
139+
XCTAssertEqual(d9, nil)
140+
141+
let d10 = testInterval10.dateInterval(to: testInterval9)
142+
XCTAssertEqual(d10, nil)
143+
144+
// |testInterval11| on itself
145+
let testInterval11 = DateInterval(start: Date(timeIntervalSince1970: 0), end: Date(timeIntervalSinceReferenceDate: 0))
146+
let t11 = testInterval11.timeInterval(to: testInterval11)
147+
XCTAssertEqual(t11, nil)
148+
149+
let d11 = testInterval11.dateInterval(to: testInterval11)
150+
XCTAssertEqual(d11, nil)
151+
}
53152

54153
func test_hashing() {
55154
// dateWithString("2019-04-04 17:09:23 -0700")

0 commit comments

Comments
 (0)