Last active
January 29, 2025 06:06
-
-
Save preble/b08b6d9ee161534e6c1f to your computer and use it in GitHub Desktop.
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/
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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.") | |
} |
Yes, the start index is non-inclusive. I guess one needs to implement constructor that will subtract 1 day from start index.
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.
I'm guessing it's not that easy to create an option to iterate backwards through the range?
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 you probably don't need this still, but I updated for Swift 3 today :)
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
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)