Skip to content

Commit

Permalink
Add timeIntervalSince(_:) and dateIntervalSince(_:) API to compute da…
Browse files Browse the repository at this point in the history
…te and/or time differences between date intervals.
  • Loading branch information
kielgillard committed Nov 28, 2023
1 parent 28a5ce8 commit 9b085d6
Show file tree
Hide file tree
Showing 2 changed files with 267 additions and 0 deletions.
133 changes: 133 additions & 0 deletions Sources/FoundationEssentials/DateInterval.swift
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,139 @@ public struct DateInterval : Comparable, Hashable, Codable, Sendable {
}
return false
}

/**
Returns the seconds between `self` and `date` or `nil` if there is no difference in time between them.
For example, given this interval and this date on a timeline:
```
|-----| <-- time interval --> *
```
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`.
```
* <-- time interval --> |-----|
```
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`.
A return value of `0` indicates `date` is equal to either the start or end moments of `self`.
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`):
```
|--*--|
```
*/
public func timeIntervalSince(_ date: Date) -> TimeInterval? {
if end <= date {
return end.timeIntervalSince(date)
} else if date <= start {
return start.timeIntervalSince(date)
} else {
return nil
}
}

/**
Returns the date interval between `self` and `date` or `nil` if there is no difference in time between them.
For example, given this interval and this date on a timeline:
```
* <-- duration --> |-----|
```
Returns a value whose start is `date` and whose `duration` is the time between the `date` and the end of `self`.
```
|-----| <-- duration --> *
```
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`.
A return value with a duration of `0` indicates `date` is equal to the start or end of `self`.
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`):
```
|--*--|
```
*/
public func dateIntervalSince(_ date: Date) -> DateInterval? {
if date <= start {
return DateInterval(start: date, end: start)

} else if end <= date {
return DateInterval(start: end, end: date)

} else {
return nil
}
}

/**
Returns the seconds between `self` and `dateInterval` or `nil` if there is no difference in time between them.
For example, given these two intervals on a timeline:
```
|-----| <-- time interval --> |-----|
```
Returns a negative time interval when `self` ends before `dateInterval` starts. A postive time interval indicates `self` starts after `dateInterval` ends.
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):
```
|-----|-----|
```
A return value of `nil` indicates `self` and `dateInterval` do not have any time between them:
```
|--|-----|--|
```
*/
public func timeIntervalSince(_ dateInterval: DateInterval) -> TimeInterval? {
if end <= dateInterval.start {
return end.timeIntervalSince(dateInterval.start)

} else if dateInterval.end <= start {
return start.timeIntervalSince(dateInterval.end)

} else {
return nil
}
}

/**
Returns the date interval between `self` and `dateInterval` or `nil` if there is no difference in time between them.
For example, given these two intervals on a timeline:
```
|-----| <-- duration --> |-----|
```
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.
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):
```
|-----|-----|
```
A return value of `nil` indicates that no interval exists between `self` and `dateInterval`:
```
|--|-----|--|
```
*/
public func dateIntervalSince(_ dateInterval: DateInterval) -> DateInterval? {
let earliestEnd: Date
let duration: TimeInterval

if end <= dateInterval.start {
earliestEnd = end
duration = dateInterval.start.timeIntervalSince(end)

} else if dateInterval.end <= start {
earliestEnd = dateInterval.end
duration = start.timeIntervalSince(dateInterval.end)

} else {
return nil
}

return DateInterval(start: earliestEnd, duration: duration)
}

public func hash(into hasher: inout Hasher) {
hasher.combine(start)
Expand Down
134 changes: 134 additions & 0 deletions Tests/FoundationEssentialsTests/DateIntervalTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,140 @@ final class DateIntervalTests : XCTestCase {
let testInterval3 = DateInterval(start: start, duration: 100.0)
XCTAssertNotEqual(testInterval1, testInterval3)
}

func test_intervalsBetweenDateIntervalAndDate() {
let earlier = Date(timeIntervalSince1970: 0)
let middle = Date(timeIntervalSince1970: 5)
let later = Date(timeIntervalSince1970: 10)

let start = Date(timeIntervalSince1970: 1)
let duration: TimeInterval = 8
let end = start.addingTimeInterval(duration) // 9
let testInterval1 = DateInterval(start: start, duration: duration)

// * --- |testInterval1|
let t1 = testInterval1.timeIntervalSince(earlier)
let d1 = testInterval1.dateIntervalSince(earlier)
XCTAssertEqual(t1, 1)
XCTAssertEqual(d1, DateInterval(start: earlier, end: start))

// |testInterval1| --- *
let t2 = testInterval1.timeIntervalSince(later)
let d2 = testInterval1.dateIntervalSince(later)
XCTAssertEqual(t2, -1)
XCTAssertEqual(d2, DateInterval(start: end, end: later))

// | testInterval1 * |
let t3 = testInterval1.timeIntervalSince(middle)
let d3 = testInterval1.dateIntervalSince(middle)
XCTAssertEqual(t3, nil)
XCTAssertEqual(d3, nil)

// equal to start/end
XCTAssertEqual(testInterval1.timeIntervalSince(start), 0)
XCTAssertEqual(testInterval1.dateIntervalSince(start), DateInterval(start: start, duration: 0))
XCTAssertEqual(testInterval1.timeIntervalSince(end), 0)
XCTAssertEqual(testInterval1.dateIntervalSince(end), DateInterval(start: end, duration: 0))
}

func test_intervalsBetweenDateIntervals() {
// Tests for intervals of zero or more duration between subjects.
// |testInterval1|testInterval2|
let testInterval1 = DateInterval(start: Date(timeIntervalSince1970: 0), end: Date(timeIntervalSinceReferenceDate: 0))
let testInterval2 = DateInterval(start: Date(timeIntervalSinceReferenceDate: 0), end: Date(timeIntervalSinceReferenceDate: 100))
let t1 = testInterval1.timeIntervalSince(testInterval2)
XCTAssertEqual(t1, 0)

let t2 = testInterval2.timeIntervalSince(testInterval1)
XCTAssertEqual(t2, 0)

let d1 = testInterval1.dateIntervalSince(testInterval2)
XCTAssertEqual(d1?.start, testInterval1.end)
XCTAssertEqual(d1?.duration, 0)

let d2 = testInterval2.dateIntervalSince(testInterval1)
XCTAssertEqual(d2?.start, testInterval1.end)
XCTAssertEqual(d2?.duration, 0)

XCTAssertEqual(d1, d2)

// |testInterval3|-----|testInterval4|
let testInterval3 = DateInterval(start: Date(timeIntervalSince1970: 0), end: Date(timeIntervalSinceReferenceDate: 0))
let testInterval4 = DateInterval(start: Date(timeIntervalSinceReferenceDate: 1), end: Date(timeIntervalSinceReferenceDate: 100))
let t3 = testInterval3.timeIntervalSince(testInterval4)
XCTAssertEqual(t3, -1)

let t4 = testInterval4.timeIntervalSince(testInterval3)
XCTAssertEqual(t4, 1)

let d3 = testInterval3.dateIntervalSince(testInterval4)
let d4 = testInterval4.dateIntervalSince(testInterval3)
XCTAssertEqual(d3?.duration, 1)
XCTAssertEqual(d3?.start, testInterval3.end)
XCTAssertEqual(d4?.duration, 1)
XCTAssertEqual(d4?.start, testInterval3.end)

// Tests for non-existing intervals between subjects.
// |testInterval5|
// |testInterval6|
//
// As a single timeline: |555|565656|666|
let testInterval5 = DateInterval(start: Date(timeIntervalSince1970: 0), end: Date(timeIntervalSinceReferenceDate: 0))
let testInterval6 = DateInterval(start: Date(timeIntervalSinceReferenceDate: -1), end: Date(timeIntervalSinceReferenceDate: 100))
let t5 = testInterval5.timeIntervalSince(testInterval6)
XCTAssertEqual(t5, nil)

let t6 = testInterval6.timeIntervalSince(testInterval5)
XCTAssertEqual(t6, nil)

let d5 = testInterval5.dateIntervalSince(testInterval6)
XCTAssertEqual(d5, nil)

let d6 = testInterval6.dateIntervalSince(testInterval5)
XCTAssertEqual(d6, nil)

// |---testInterval7---|
// |testInterval8|
//
// As a single timeline: |777|787878|777|
let testInterval7 = DateInterval(start: Date(timeIntervalSince1970: 0), end: Date(timeIntervalSinceReferenceDate: 0))
let testInterval8 = DateInterval(start: Date(timeIntervalSince1970: 10), end: Date(timeIntervalSince1970: 20))
let t7 = testInterval7.timeIntervalSince(testInterval8)
XCTAssertEqual(t7, nil)

let t8 = testInterval8.timeIntervalSince(testInterval7)
XCTAssertEqual(t8, nil)

let d7 = testInterval7.dateIntervalSince(testInterval8)
XCTAssertEqual(d7, nil)

let d8 = testInterval8.dateIntervalSince(testInterval7)
XCTAssertEqual(d8, nil)

// |testInterval9|
// |testInterval10---|
let testInterval9 = DateInterval(start: Date(timeIntervalSince1970: 0), end: Date(timeIntervalSinceReferenceDate: 0))
let testInterval10 = DateInterval(start: Date(timeIntervalSince1970: 0), end: Date(timeIntervalSinceReferenceDate: 100))
let t9 = testInterval9.timeIntervalSince(testInterval10)
XCTAssertEqual(t9, nil)

let t10 = testInterval10.timeIntervalSince(testInterval9)
XCTAssertEqual(t10, nil)

let d9 = testInterval9.dateIntervalSince(testInterval10)
XCTAssertEqual(d9, nil)

let d10 = testInterval10.dateIntervalSince(testInterval9)
XCTAssertEqual(d10, nil)

// |testInterval11| on itself
let testInterval11 = DateInterval(start: Date(timeIntervalSince1970: 0), end: Date(timeIntervalSinceReferenceDate: 0))
let t11 = testInterval11.timeIntervalSince(testInterval11)
XCTAssertEqual(t11, nil)

let d11 = testInterval11.dateIntervalSince(testInterval11)
XCTAssertEqual(d11, nil)
}

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

0 comments on commit 9b085d6

Please sign in to comment.