sharedev
sharedev

Reputation: 419

What is the most effective way to iterate over a date range in Swift?

I am building a Swift habit-building application. At a certain point, a user selects the duration (in days) of the habit they'd like to create. The user can also select which days of the week they'd like to practice it (sunday, monday, tuesday, etc.). The start date is the day they create the habit.

I currently have the start date and end date (based on number of days selected by user). Within that date range, however, I need to extract dates for the specific days the user has chosen.

My thinking is that for every weekday value 'x' within time interval (startDate, endDate), return Calendar date. However, I have tried creating a DateInterval, and I am unable to iterate over it because it does not conform to protocol 'Sequence'.

Is there an efficient way of iterating over this date range and extracting these other dates? Is there a better way that I'm not yet thinking of?

Here's my current date starter code below:

var date = Date()
var duration = 10
let calendar = Calendar.current

let dateEnding = Calendar.current.date(byAdding: .day, value: duration, to: date as Date)!

//Returns a number representing day of the week.  i.e. 1 = Sunday
let weekDay = calendar.component(.weekday, from: date)


//Initializing a time interval, based on user selected duration and start date
let timeInterval = DateInterval.init(start: date, end: dateEnding)

Thanks for your help...

Upvotes: 16

Views: 17473

Answers (3)

Andrey Gordeev
Andrey Gordeev

Reputation: 32529

We can take a power of Strideable protocol:

extension Date: Strideable {
    public func distance(to other: Date) -> TimeInterval {
        return other.timeIntervalSinceReferenceDate - self.timeIntervalSinceReferenceDate
    }

    public func advanced(by n: TimeInterval) -> Date {
        return self + n
    }
}

and then in your func:

// Iterate by 1 day
// Feel free to change this variable to iterate by week, month etc.
let dayDurationInSeconds: TimeInterval = 60*60*24
for date in stride(from: startDate, to: endDate, by: dayDurationInSeconds) {
    print(date)
}

Output:

2018-07-19 12:18:07 +0000
2018-07-20 12:18:07 +0000
2018-07-21 12:18:07 +0000
2018-07-22 12:18:07 +0000
2018-07-23 12:18:07 +0000
....

The code is taken from this proposal

Upvotes: 37

rmaddy
rmaddy

Reputation: 318894

If I understand your question correctly, the user will check off some weekdays and provide a duration as a number of days.

Assuming you have the selected weekdays in an array and the duration, you can get the list of matching dates as follows:

// User selected weekdays (1 = Sunday, 7 = Saturday)
var selectedWeekdays = [2, 4, 6] // Example - Mon, Wed, Fri
var duration = 10 // Example - 10 days

let calendar = Calendar.current
var today = Date()
let dateEnding = calendar.date(byAdding: .day, value: duration, to: today)!

var matchingDates = [Date]()
// Finding matching dates at midnight - adjust as needed
let components = DateComponents(hour: 0, minute: 0, second: 0) // midnight
calendar.enumerateDates(startingAfter: today, matching: components, matchingPolicy: .nextTime) { (date, strict, stop) in
    if let date = date {
        if date <= dateEnding {
            let weekDay = calendar.component(.weekday, from: date)
            print(date, weekDay)
            if selectedWeekdays.contains(weekDay) {
                matchingDates.append(date)
            }
        } else {
            stop = true
        }
    }
}

print("Matching dates = \(matchingDates)")

Upvotes: 17

Alexander Vitanov
Alexander Vitanov

Reputation: 4141

Try this

let calendar = NSCalendar.currentCalendar()
let startDate = ...
let endDate = ...
let dateRange = calendar.dateRange(startDate: startDate,
                                     endDate: endDate,
                                   stepUnits: .Day,
                                   stepValue: 1)

for date in dateRange {
    print("It's \(date)!")
}

Upvotes: 7

Related Questions