Skip to content

Commit 9b085d6

Browse files
committed
Add timeIntervalSince(_:) and dateIntervalSince(_:) API to compute date and/or time differences between date intervals.
1 parent 28a5ce8 commit 9b085d6

File tree

2 files changed

+267
-0
lines changed

2 files changed

+267
-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 this date on a timeline:
155+
```
156+
|-----| <-- time interval --> *
157+
```
158+
Returns a negative time interval when `date` is a moment greater than or equal to the end of `self` because the receiver specifies a range of times earlier than `date`.
159+
160+
```
161+
* <-- time interval --> |-----|
162+
```
163+
Returns a positive time interval when `date` is a moment less than or equal to (before) the start of `self` because the receiver specifies a range of times later than `date`.
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 timeIntervalSince(_ date: Date) -> TimeInterval? {
173+
if end <= date {
174+
return end.timeIntervalSince(date)
175+
} else if date <= start {
176+
return start.timeIntervalSince(date)
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 this date on a timeline:
186+
```
187+
* <-- duration --> |-----|
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+
|-----| <-- duration --> *
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 dateIntervalSince(_ 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 timeIntervalSince(_ 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+
|-----| <-- duration --> |-----|
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 dateIntervalSince(_ 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: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,140 @@ final class DateIntervalTests : XCTestCase {
5050
let testInterval3 = DateInterval(start: start, duration: 100.0)
5151
XCTAssertNotEqual(testInterval1, testInterval3)
5252
}
53+
54+
func test_intervalsBetweenDateIntervalAndDate() {
55+
let earlier = Date(timeIntervalSince1970: 0)
56+
let middle = Date(timeIntervalSince1970: 5)
57+
let later = Date(timeIntervalSince1970: 10)
58+
59+
let start = Date(timeIntervalSince1970: 1)
60+
let duration: TimeInterval = 8
61+
let end = start.addingTimeInterval(duration) // 9
62+
let testInterval1 = DateInterval(start: start, duration: duration)
63+
64+
// * --- |testInterval1|
65+
let t1 = testInterval1.timeIntervalSince(earlier)
66+
let d1 = testInterval1.dateIntervalSince(earlier)
67+
XCTAssertEqual(t1, 1)
68+
XCTAssertEqual(d1, DateInterval(start: earlier, end: start))
69+
70+
// |testInterval1| --- *
71+
let t2 = testInterval1.timeIntervalSince(later)
72+
let d2 = testInterval1.dateIntervalSince(later)
73+
XCTAssertEqual(t2, -1)
74+
XCTAssertEqual(d2, DateInterval(start: end, end: later))
75+
76+
// | testInterval1 * |
77+
let t3 = testInterval1.timeIntervalSince(middle)
78+
let d3 = testInterval1.dateIntervalSince(middle)
79+
XCTAssertEqual(t3, nil)
80+
XCTAssertEqual(d3, nil)
81+
82+
// equal to start/end
83+
XCTAssertEqual(testInterval1.timeIntervalSince(start), 0)
84+
XCTAssertEqual(testInterval1.dateIntervalSince(start), DateInterval(start: start, duration: 0))
85+
XCTAssertEqual(testInterval1.timeIntervalSince(end), 0)
86+
XCTAssertEqual(testInterval1.dateIntervalSince(end), DateInterval(start: end, duration: 0))
87+
}
88+
89+
func test_intervalsBetweenDateIntervals() {
90+
// Tests for intervals of zero or more duration between subjects.
91+
// |testInterval1|testInterval2|
92+
let testInterval1 = DateInterval(start: Date(timeIntervalSince1970: 0), end: Date(timeIntervalSinceReferenceDate: 0))
93+
let testInterval2 = DateInterval(start: Date(timeIntervalSinceReferenceDate: 0), end: Date(timeIntervalSinceReferenceDate: 100))
94+
let t1 = testInterval1.timeIntervalSince(testInterval2)
95+
XCTAssertEqual(t1, 0)
96+
97+
let t2 = testInterval2.timeIntervalSince(testInterval1)
98+
XCTAssertEqual(t2, 0)
99+
100+
let d1 = testInterval1.dateIntervalSince(testInterval2)
101+
XCTAssertEqual(d1?.start, testInterval1.end)
102+
XCTAssertEqual(d1?.duration, 0)
103+
104+
let d2 = testInterval2.dateIntervalSince(testInterval1)
105+
XCTAssertEqual(d2?.start, testInterval1.end)
106+
XCTAssertEqual(d2?.duration, 0)
107+
108+
XCTAssertEqual(d1, d2)
109+
110+
// |testInterval3|-----|testInterval4|
111+
let testInterval3 = DateInterval(start: Date(timeIntervalSince1970: 0), end: Date(timeIntervalSinceReferenceDate: 0))
112+
let testInterval4 = DateInterval(start: Date(timeIntervalSinceReferenceDate: 1), end: Date(timeIntervalSinceReferenceDate: 100))
113+
let t3 = testInterval3.timeIntervalSince(testInterval4)
114+
XCTAssertEqual(t3, -1)
115+
116+
let t4 = testInterval4.timeIntervalSince(testInterval3)
117+
XCTAssertEqual(t4, 1)
118+
119+
let d3 = testInterval3.dateIntervalSince(testInterval4)
120+
let d4 = testInterval4.dateIntervalSince(testInterval3)
121+
XCTAssertEqual(d3?.duration, 1)
122+
XCTAssertEqual(d3?.start, testInterval3.end)
123+
XCTAssertEqual(d4?.duration, 1)
124+
XCTAssertEqual(d4?.start, testInterval3.end)
125+
126+
// Tests for non-existing intervals between subjects.
127+
// |testInterval5|
128+
// |testInterval6|
129+
//
130+
// As a single timeline: |555|565656|666|
131+
let testInterval5 = DateInterval(start: Date(timeIntervalSince1970: 0), end: Date(timeIntervalSinceReferenceDate: 0))
132+
let testInterval6 = DateInterval(start: Date(timeIntervalSinceReferenceDate: -1), end: Date(timeIntervalSinceReferenceDate: 100))
133+
let t5 = testInterval5.timeIntervalSince(testInterval6)
134+
XCTAssertEqual(t5, nil)
135+
136+
let t6 = testInterval6.timeIntervalSince(testInterval5)
137+
XCTAssertEqual(t6, nil)
138+
139+
let d5 = testInterval5.dateIntervalSince(testInterval6)
140+
XCTAssertEqual(d5, nil)
141+
142+
let d6 = testInterval6.dateIntervalSince(testInterval5)
143+
XCTAssertEqual(d6, nil)
144+
145+
// |---testInterval7---|
146+
// |testInterval8|
147+
//
148+
// As a single timeline: |777|787878|777|
149+
let testInterval7 = DateInterval(start: Date(timeIntervalSince1970: 0), end: Date(timeIntervalSinceReferenceDate: 0))
150+
let testInterval8 = DateInterval(start: Date(timeIntervalSince1970: 10), end: Date(timeIntervalSince1970: 20))
151+
let t7 = testInterval7.timeIntervalSince(testInterval8)
152+
XCTAssertEqual(t7, nil)
153+
154+
let t8 = testInterval8.timeIntervalSince(testInterval7)
155+
XCTAssertEqual(t8, nil)
156+
157+
let d7 = testInterval7.dateIntervalSince(testInterval8)
158+
XCTAssertEqual(d7, nil)
159+
160+
let d8 = testInterval8.dateIntervalSince(testInterval7)
161+
XCTAssertEqual(d8, nil)
162+
163+
// |testInterval9|
164+
// |testInterval10---|
165+
let testInterval9 = DateInterval(start: Date(timeIntervalSince1970: 0), end: Date(timeIntervalSinceReferenceDate: 0))
166+
let testInterval10 = DateInterval(start: Date(timeIntervalSince1970: 0), end: Date(timeIntervalSinceReferenceDate: 100))
167+
let t9 = testInterval9.timeIntervalSince(testInterval10)
168+
XCTAssertEqual(t9, nil)
169+
170+
let t10 = testInterval10.timeIntervalSince(testInterval9)
171+
XCTAssertEqual(t10, nil)
172+
173+
let d9 = testInterval9.dateIntervalSince(testInterval10)
174+
XCTAssertEqual(d9, nil)
175+
176+
let d10 = testInterval10.dateIntervalSince(testInterval9)
177+
XCTAssertEqual(d10, nil)
178+
179+
// |testInterval11| on itself
180+
let testInterval11 = DateInterval(start: Date(timeIntervalSince1970: 0), end: Date(timeIntervalSinceReferenceDate: 0))
181+
let t11 = testInterval11.timeIntervalSince(testInterval11)
182+
XCTAssertEqual(t11, nil)
183+
184+
let d11 = testInterval11.dateIntervalSince(testInterval11)
185+
XCTAssertEqual(d11, nil)
186+
}
53187

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

0 commit comments

Comments
 (0)