-1

I have a DateInterval where I'd like to get dates every 15 seconds between the two dates. For example, from July 15 10:14:00 AM to July 15 10:16:00 AM, I'd like to generate the following dates:

July 15 10:14:00 AM
July 15 10:14:15 AM
July 15 10:14:30 AM
July 15 10:14:45 AM
July 15 10:15:00 AM
July 15 10:15:15 AM
July 15 10:15:30 AM
July 15 10:15:45 AM
July 15 10:16:00 AM

It doesn't have to be a DateInterval, but thought it would be better to encapsulate the range using it. More importantly though, I’d like to use higher order functions and an immutable variable to accomplish this. I've tried a few things but couldn't quite get it:

let dateInterval = DateInterval(start: date1, duration: addingTimeInterval(10 * 60.0))

for var date in date1...date1.addingTimeInterval(10 * 60.0) {
   ...
}

var intervals = [date1]

while intervals.last <= date1.addingTimeInterval(10 * 60) {
    intervals.append(intervals.last.addingTimeInterval(15))
}

Would greatly appreciate any help!

TruMan1
  • 33,665
  • 59
  • 184
  • 335

3 Answers3

2

One possibility to make this work is to make Date conform to Strideable. Doing this will enable you to write a function like so:

let startDate: Date = ...
let endDate: Date = ...

for date in stride(from: startDate, to: endDate, by: TimeInterval(10 * 60)) {
    print(date)
}

Date already conforms to Strideable, however it doesn't do so immediately (probably because of this). What you need to do therefore is to simply have it conform to Strideable in an extension:

extension Date: Strideable {}
Schottky
  • 1,549
  • 1
  • 4
  • 19
  • What concerns me is the warning in docs: https://developer.apple.com/documentation/swift/strideable: `The Strideable protocol provides default implementations for the equal-to (==) and less-than (<) operators that depend on the Stride type’s implementations. If a type conforming to Strideable is its own Stride type, it must provide concrete implementations of the two operators to avoid infinite recursion.` `Date` does have its own equal-to and less-than implementations which makes me wonder if it will clash with `Strideable` default implementations. – TruMan1 Jul 15 '21 at 21:04
  • 1
    There must be a reason why there's so much debate about conforming `Date` to `Strideable` and why the Swift team is so reluctant to do it. Seems like a time paradox, the result of which could cause a chain reaction that would unravel the very fabric of the space-time continuum. – TruMan1 Jul 15 '21 at 21:15
1

You should always use Calendar methods for you calendrical calculations. What you need is to create a while loop and add a date component value on each iteration to your a date and stop the iteration when it reaches the end date:

var date = DateComponents(calendar: .current, year: 2021, month: 7, day: 15, hour: 10, minute: 14).date!
let end = DateComponents(calendar: .current, year: 2021, month: 7, day: 15, hour: 10, minute: 16).date!

var dates = [date]
while let nextDate = Calendar.current.date(byAdding: .second, value: 15, to: date) {
    if nextDate <= end {
        dates.append(nextDate)
        date = nextDate
    } else {
        break
    }
}

dates.forEach { print($0.description(with: .current)) }

edit/update:

If all you need to add to your date is seconds you can simply use += operator:

var date = DateComponents(calendar: .current, year: 2021, month: 7, day: 15, hour: 10, minute: 14).date!
let end = DateComponents(calendar: .current, year: 2021, month: 7, day: 15, hour: 10, minute: 16).date!

var dates = [date]
while date <= end {
    date += 15
    if date <= end {
        dates.append(date)
    } else {
        break
    }
}

dates.forEach { print($0.description(with: .current)) }

Those will print:

Thursday, July 15, 2021 at 10:14:00 AM Brasilia Standard Time
Thursday, July 15, 2021 at 10:14:15 AM Brasilia Standard Time
Thursday, July 15, 2021 at 10:14:30 AM Brasilia Standard Time
Thursday, July 15, 2021 at 10:14:45 AM Brasilia Standard Time
Thursday, July 15, 2021 at 10:15:00 AM Brasilia Standard Time
Thursday, July 15, 2021 at 10:15:15 AM Brasilia Standard Time
Thursday, July 15, 2021 at 10:15:30 AM Brasilia Standard Time
Thursday, July 15, 2021 at 10:15:45 AM Brasilia Standard Time
Thursday, July 15, 2021 at 10:16:00 AM Brasilia Standard Time

And a not so clean but one liner:

let dates = (0...Int(end.timeIntervalSince(date)/15))
    .map { date.addingTimeInterval(.init($0)*15) }
Leo Dabus
  • 229,809
  • 59
  • 489
  • 571
  • I’ve been wondering why is date component necessary when adding seconds. I could see if adding days or months, but striding through time stamps doesn’t seem to need a calendar, until needing to format them to strings if ever. – TruMan1 Jul 15 '21 at 20:19
  • 1
    Well if all you need is to add seconds you can simply do `date += 15` – Leo Dabus Jul 15 '21 at 21:09
1

Too much debate about conforming to Strideable to feel comfortable using. So created an extension to DateInterval that stays out of the way. Thanks to the accepted answer for the groundwork:

public extension DateInterval {
    /// Returns offset dates between the range that are offset.
    /// - Parameter timeInterval: The time interval to offset the dates by.
    func stride(by timeInterval: TimeInterval) -> [Date] {
        (0...Int(end.timeIntervalSince(start) / timeInterval))
            .map { start.advanced(by: TimeInterval($0) * timeInterval) }
    }
}

And tests:

func testDateIntervalStride() throws {
    let startDate = Date(timeIntervalSince1970: 1626386307)

    let interval1 = DateInterval(start: startDate, duration: 125)
    let dates1 = interval1.stride(by: 15)
    XCTAssertEqual(dates1.count, 9)
    XCTAssertEqual(dates1[0].timeIntervalSince1970, 1626386307)
    XCTAssertEqual(dates1[1].timeIntervalSince1970, 1626386307 + 15)
    XCTAssertEqual(dates1[2].timeIntervalSince1970, 1626386307 + 15 * 2)
    XCTAssertEqual(dates1[3].timeIntervalSince1970, 1626386307 + 15 * 3)
    XCTAssertEqual(dates1[4].timeIntervalSince1970, 1626386307 + 15 * 4)
    XCTAssertEqual(dates1[5].timeIntervalSince1970, 1626386307 + 15 * 5)
    XCTAssertEqual(dates1[6].timeIntervalSince1970, 1626386307 + 15 * 6)
    XCTAssertEqual(dates1[7].timeIntervalSince1970, 1626386307 + 15 * 7)
    XCTAssertEqual(dates1[8].timeIntervalSince1970, 1626386307 + 15 * 8)

    let interval2 = DateInterval(start: startDate, duration: 0)
    let dates2 = interval2.stride(by: 15)
    XCTAssertEqual(dates2.count, 1)
    XCTAssertEqual(dates2[0].timeIntervalSince1970, 1626386307)

    let interval3 = DateInterval(start: startDate, duration: 600)
    let dates3 = interval3.stride(by: 60)
    XCTAssertEqual(dates3.count, 11)
    XCTAssertEqual(dates3[0].timeIntervalSince1970, 1626386307)
    XCTAssertEqual(dates3[1].timeIntervalSince1970, 1626386307 + 60)
    XCTAssertEqual(dates3[2].timeIntervalSince1970, 1626386307 + 60 * 2)
    XCTAssertEqual(dates3[3].timeIntervalSince1970, 1626386307 + 60 * 3)
    XCTAssertEqual(dates3[4].timeIntervalSince1970, 1626386307 + 60 * 4)
    XCTAssertEqual(dates3[5].timeIntervalSince1970, 1626386307 + 60 * 5)
    XCTAssertEqual(dates3[6].timeIntervalSince1970, 1626386307 + 60 * 6)
    XCTAssertEqual(dates3[7].timeIntervalSince1970, 1626386307 + 60 * 7)
    XCTAssertEqual(dates3[8].timeIntervalSince1970, 1626386307 + 60 * 8)
    XCTAssertEqual(dates3[9].timeIntervalSince1970, 1626386307 + 60 * 9)
}
TruMan1
  • 33,665
  • 59
  • 184
  • 335