Iterating Over a Range of Dates in Swift

One thing I’ve been wanting to do with Swift is iterate over a range of NSDate objects in a for loop. Something like this:

letstartDate=...letendDate=...fordateinstartDate...endDate{...}

While I think it might be possible to do this by making NSDate conform to ForwardIndexType, it would be fairly inflexible. As I understand date arithmetic, to do it right you need a reference to the NSCalendar being used, and of course you need to know how much to ‘step’ the date each time. You could just make it step by days but what if you later want to step by hours?

So I decided on a different approach: create a struct, DateRange, that conforms to SequenceType. It’s not nearly as succinct, but it is much more flexible. Create an instance of the struct using an extension on NSCalendar, as this seems to be in keeping with calendar-dependent date APIs. It looks like this:

The complete code is below (also in a gist), but first a bit of a disclaimer: this code works, but I half expect to look back on it in a year, cringe, and contemplate deleting this post. My crystal ball of Swift faux pas is cloudy.

Note also that at the time this was written, NSDate did not have any Swift comparison operators built in, so I implemented >. Presumably that will change.

importFoundationfunc>(left:NSDate,right:NSDate)->Bool{returnleft.compare(right)==.OrderedDescending}extensionNSCalendar{funcdateRange(startDatestartDate:NSDate,endDate:NSDate,stepUnits:NSCalendarUnit,stepValue:Int)->DateRange{letdateRange=DateRange(calendar:self,startDate:startDate,endDate:endDate,stepUnits:stepUnits,stepValue:stepValue,multiplier:0)returndateRange}}structDateRange:SequenceType{varcalendar:NSCalendarvarstartDate:NSDatevarendDate:NSDatevarstepUnits:NSCalendarUnitvarstepValue:Intprivatevarmultiplier:Intfuncgenerate()->Generator{returnGenerator(range:self)}structGenerator:GeneratorType{varrange:DateRangemutatingfuncnext()->NSDate?{guardletnextDate=range.calendar.dateByAddingUnit(range.stepUnits,value:range.stepValue*range.multiplier,toDate:range.startDate,options:[])else{returnnil}ifnextDate>range.endDate{returnnil}else{range.multiplier+=1returnnextDate}}}}// Usage:functestDateRange(){letcalendar=NSCalendar(calendarIdentifier:NSCalendarIdentifierGregorian)!letstartDate=NSDate(timeIntervalSinceNow:0)letendDate=NSDate(timeIntervalSinceNow:24*60*60*7-1)letdateRange=calendar.dateRange(startDate:startDate,endDate:endDate,stepUnits:.Day,stepValue:1)letdatesInRange=Array(dateRange)XCTAssertEqual(datesInRange.count,7,"Expected 7 days")XCTAssertEqual(datesInRange.first,startDate,"First date should have been the start date.")}