Create a gist now

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.

Show comment
Hide comment
@FrancisBaileyH

FrancisBaileyH 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)

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.

Show comment
Hide comment
@alexeyismirnov

alexeyismirnov 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.

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.

Show comment
Hide comment
@preble

preble 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.

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.

Show comment
Hide comment
@leeprobert

leeprobert Nov 16, 2016

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

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

@Nidhee

This comment has been minimized.

Show comment
Hide comment
@Nidhee

Nidhee 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

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.

Show comment
Hide comment
@aaronfalls

aaronfalls May 26, 2017

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

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

@phatmann

This comment has been minimized.

Show comment
Hide comment
@phatmann

phatmann 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
        }
    }
}

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