Instantly share code, notes, and snippets.

Embed
What would you like to do?
Experimenting with creating a SequenceType for iterating over a range of dates. Blogged here: http://adampreble.net/blog/2014/09/iterating-over-range-of-dates-swift/
import Foundation
func > (left: NSDate, right: NSDate) -> Bool {
return left.compare(right) == .OrderedDescending
}
extension NSCalendar {
func dateRange(startDate startDate: NSDate, endDate: NSDate, stepUnits: NSCalendarUnit, stepValue: Int) -> DateRange {
let dateRange = DateRange(calendar: self, startDate: startDate, endDate: endDate, stepUnits: stepUnits, stepValue: stepValue, multiplier: 0)
return dateRange
}
}
struct DateRange : SequenceType {
var calendar: NSCalendar
var startDate: NSDate
var endDate: NSDate
var stepUnits: NSCalendarUnit
var stepValue: Int
private var multiplier: Int
func generate() -> Generator {
return Generator(range: self)
}
struct Generator: GeneratorType {
var range: DateRange
mutating func next() -> NSDate? {
guard let nextDate = range.calendar.dateByAddingUnit(range.stepUnits,
value: range.stepValue * range.multiplier,
toDate: range.startDate,
options: []) else {
return nil
}
if nextDate > range.endDate {
return nil
}
else {
range.multiplier += 1
return nextDate
}
}
}
}
// Usage:
func testDateRange() {
let calendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
let startDate = NSDate(timeIntervalSinceNow: 0)
let endDate = NSDate(timeIntervalSinceNow: 24*60*60*7-1)
let dateRange = calendar.dateRange(startDate: startDate,
endDate: endDate,
stepUnits: .Day,
stepValue: 1)
let datesInRange = Array(dateRange)
XCTAssertEqual(datesInRange.count, 7, "Expected 7 days")
XCTAssertEqual(datesInRange.first, startDate, "First date should have been the start date.")
}
@FrancisBaileyH

This comment has been minimized.

FrancisBaileyH commented Mar 25, 2015

The one issue I've found with this, is it seems to skip over the first date you pass to it. I.e. Date 1 (exclusive) to Date N (inclusive)

@alexeyismirnov

This comment has been minimized.

alexeyismirnov commented Aug 25, 2015

Yes, the start index is non-inclusive. I guess one needs to implement constructor that will subtract 1 day from start index.

@preble

This comment has been minimized.

Owner

preble commented Aug 25, 2016

Thanks for the comments pointing out the issue with the first date! I've updated the gist to fix that issue, as well as changed it to be created using an extension on NSCalendar, which I think is more idiomatic for NSCalendar-related date functions.

@leeprobert

This comment has been minimized.

leeprobert commented Nov 16, 2016

I'm guessing it's not that easy to create an option to iterate backwards through the range?

@Nidhee

This comment has been minimized.

Nidhee commented Feb 8, 2017

I move from swift 2.2 to swift 3.0. Which is getting lot of errors. can you help in converting to swift 3.0

@aaronfalls

This comment has been minimized.

aaronfalls commented May 26, 2017

@Nidhee you probably don't need this still, but I updated for Swift 3 today :)

@phatmann

This comment has been minimized.

phatmann commented Dec 7, 2017

For Swift 4:

extension Calendar {
    func dateRange(startDate: Date, endDate: Date, component: Calendar.Component, step: Int) -> DateRange {
        let dateRange = DateRange(calendar: self, startDate: startDate, endDate: endDate, component: component, step: step, multiplier: 0)
        return dateRange
    }
}

struct DateRange : Sequence, IteratorProtocol {
    var calendar: Calendar
    var startDate: Date
    var endDate: Date
    var component: Calendar.Component
    var step: Int
    var multiplier: Int
        
    mutating func next() -> Date? {
        guard let nextDate = calendar.date(byAdding: component, value: step * multiplier, to: startDate)
        else {
            return nil
        }
        
        if nextDate > endDate {
            return nil
        } else {
            multiplier += 1
            return nextDate
        }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment