5

I am working on a scheduling app. I want all the dates of the given months I am not able to group dates by months that is what I tried but I want a different expected result

extension Date {
    static func dates(from fromDate: Date, to toDate: Date) -> [Date] {
        var dates: [Date] = []
        var date = fromDate

        while date <= toDate {
            dates.append(date)
            guard let newDate = Calendar.current.date(byAdding: .day, value: 1, to: date) else { break }
            date = newDate
        }
        return dates
    }
    var month: Int {
        return Calendar.current.component(.month, from: self)
    }
}

let fromDate = Calendar.current.date(byAdding: .day, value: 30, to: Date())
let datesBetweenArray = Date.dates(from: Date(), to: fromDate!)
var sortedDatesByMonth: [[Date]] = []
let filterDatesByMonth = { month in datesBetweenArray.filter { $0.month == month } }
(1...12).forEach { sortedDatesByMonth.append(filterDatesByMonth($0)) }

The result is in this format [[], [], [], [], [], [], [2019-07-31 03:51:19 +0000],……., [], [], [], []]

This kinda result I want expecting

struct ScheduleDates {
    var month: String
    var dates: [Date]

    init(month: String, dates: [Date]) {
        self.month = month
        self.dates = dates
    }
}
var sections = [ScheduleDates]()
5
  • 1
    struct names should be CapitalizedCamelCase, so it should be struct ScheduleDates { // stuff } Commented Jul 31, 2019 at 4:19
  • Do you want to generate this from an array of the structs that you defined or do you want to feed it an array of dates? Commented Jul 31, 2019 at 5:09
  • @Adrian In the above code, it is in Array of Array format but I want the result on the format of Array of the struct. Commented Jul 31, 2019 at 5:12
  • I posted below how to do what you describe along with some suggestions if you're going to be displaying this information in something like a UITableView or UICollectionView. Commented Jul 31, 2019 at 5:59
  • @Adrian Yeah Thanks... I am displaying it on UITableView Commented Jul 31, 2019 at 6:07

3 Answers 3

5

If you want to group your dates by month you can create a Dictionary like this:

Dictionary(grouping: datesBetweenArray, by: { $0.month })

This results in the following output of the format [Int: [Date]]

The key of the dictionary will be your month.

Now you can initialize your scheduleDates struct by looping through this dictionary in this way:

var sections = Dictionary(grouping: datesBetweenArray,
                          by: ({$0.month}))
    .map { tuple in
        ScheduleDates(month: String(tuple.0), dates: tuple.1)
}
Sign up to request clarification or add additional context in comments.

4 Comments

But in Dictionary the sequence might change white updating it as I know.. that's why using Array.
@Gitz I didn't understand that the sequence might change. My understanding is that you have an array of dates and you want to convert it to an array of ScheduleDates, right ?
How can I loop to the dictionary for 'ScheduleDates' Array
@Gitz Edited the answer to add code for looping through dict and creating [ScheduleDates]
4

Here's the code for a Playground

I think your structs should probably be Int values for the months, as when you go to populate something like a tableview, it'll be a PITA to re-order months if you've got them as Strings.

struct ScheduleDates {
    var month: String
    var dates: [Date]
}

Anyway, here's the extension I wrote based on what you provided. I frankly think you should return a dictionary with an Int as the key and an array of Dates as the value, but here's what you wanted...

I used Dictionary(grouping:by:) to construct a dictionary from an array of dates.

extension Date {
    static func dateDictionary(from arrayOfDates: [Date]) -> [String: [Date]] {
        // declare a dictionary
        return Dictionary(grouping: arrayOfDates) { date -> String in
            // get the month as an int
            let monthAsInt = Calendar.current.dateComponents([.month], from: date).month
            // convert the int to a string...i think you probably want to return an int value and do the month conversion in your tableview or collection view
            let monthName = DateFormatter().monthSymbols[(monthAsInt ?? 0) - 1]
            // return the month string
            return monthName
        }
    }
}

Here's a utility method I wrote to generate data while I figured out how to do it. If you're going to be in production, don't force unwrap stuff as I did here.

// Utility method to generate dates
func createDate(month: Int, day: Int, year: Int) -> Date? {
    var components = DateComponents()
    components.month = month
    components.day = day
    components.year = year

    return Calendar.current.date(from: components)!
}

Below is how I generated an array of sample dates to experiment.

// generate array of sample dates
let dateArray: [Date] = {
    let months = Array(1...12)
    let days = Array(1...31)
    let years = [2019]

    var dateArray: [Date] = []

    while dateArray.count < 100 {
        let randomMonth = months.randomElement()
        let randomDay = days.randomElement()
        let randomYear = years.randomElement()

        if let month = randomMonth,
            let day = randomDay,
            let year = randomYear,
            let date = createDate(month: month,
                                  day: day,
                                  year: year) {
            dateArray.append(date)
        }
    }

    return dateArray
}()

let monthDictionary = Date.dateDictionary(from: dateArray)

var arrayOfStructs: [ScheduleDates] = []

monthDictionary.keys.forEach { key in
    let scheduleDate = ScheduleDates(month: key,
                                     dates: monthDictionary[key] ?? [])
    arrayOfStruct.append(scheduleDate)
}

print(arrayOfStructs)

2 Comments

Thanks for your reply. This answer is exactly what I am looking for. What if I take Int instead of String ?
Here's a GitHub repo with the code updated from month string to months as Ints. github.com/AdrianBinDC/DatesByMonth
0

You can use my code. Which i write to adapt your case.

let fromDate = Calendar.current.date(byAdding: .day, value: 30, to: Date())
let datesBetweenArray = Date.dates(from: Date(), to: fromDate!)
if datesBetweenArray.count <= 1 {
    print(datesBetweenArray)
}
var sortedDatesByMonth: [[Date]] = []
var tempMonth = datesBetweenArray[0].month
var dates: [Date] = []
for i in 0..<datesBetweenArray.count {

    if tempMonth == datesBetweenArray[i].month {
        dates.append(datesBetweenArray[i])
        if i == datesBetweenArray.count - 1 {
            sortedDatesByMonth.append(dates)
        }
    } else {
        sortedDatesByMonth.append(dates)
        tempMonth = datesBetweenArray[i].month
        dates.removeAll()
        dates.append(datesBetweenArray[i])
    }
}
print(sortedDatesByMonth.count)
print(sortedDatesByMonth)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.