Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

ios

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

like image 205
sharedev Avatar asked Oct 20 '17 21:10

sharedev


2 Answers

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

like image 51
Andrey Gordeev Avatar answered Oct 12 '22 23:10

Andrey Gordeev


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)")
like image 28
rmaddy Avatar answered Oct 12 '22 23:10

rmaddy