Grouping Date Objects by year and month I have an array of date objects, which I want to group by year and month. The data structure I have chosen for my target is an array of arrays of Date objects.

Two Date objects should be in the same array if year AND month match up.

I'm supplying two extensions I added to Date and Formatter to easily extract year and month of a given date, as well as creating date objects more easily.

import Foundation

extension Formatter {

    static let year: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyy"
        return formatter
    }()

    static let monthString: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "LLL"
        return formatter
    }()

    static let monthInt: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "LL"
        return formatter
    }()
}


extension Date {

    var monthString: String  { return Formatter.monthString.string(from: self) }
    var monthInt: Int { return Int(Formatter.monthInt.string (from:self))! }
    var yearInt: Int { return Int(Formatter.year.string(from: self))!}

    static func from(year: Int, month: Int, day: Int) -&gt; Date {
        let gregorianCalendar = NSCalendar(calendarIdentifier: .gregorian)!

        var dateComponents = DateComponents()
        dateComponents.year = year
        dateComponents.month = month
        dateComponents.day = day

        let date:Date = gregorianCalendar.date(from: dateComponents)!
        return date
    }

    static func parse(_ string: String, format: String = "yyyy-MM-dd") -&gt; Date {
        let dateFormatter = DateFormatter()
        dateFormatter.timeZone = NSTimeZone.default
        dateFormatter.dateFormat = format

        let date = dateFormatter.date(from: string)!
        return date
    }
}


I then created a helper class with a static method to encapsulate the functionality:

class DateArrayConversionHelper {

    static func sortDateByMonth(dateArray:[Date]) -&gt; [[Date]] {

        //create a copy of input array and sort it

        var inputArray:[Date] = dateArray
        inputArray.sort()

        //create target data structure

        var resultArray:[[Date]] = [[]]

        //set initial variable and add it to target data structure

        resultArray[0].append(inputArray[0])
        var k = 0

        for i in 1 ..&lt; (inputArray.count) {

            if (inputArray[i].yearInt == inputArray[i-1].yearInt)
                &amp;&amp; (inputArray[i].monthInt == inputArray[i-1].monthInt) {
                    resultArray[k].append(inputArray[i])
                }
            else {
                    k = k+1
                    resultArray.append([])
                    resultArray[k].append(inputArray[i])
                }
        }

    return resultArray
    }
}


For testing:

let randomDates:[Date] = [Date.parse("2014-05-20"), Date.parse("2012-07-21"), Date.parse("2012-07-01"), Date.parse("2017-01-24"), Date.parse("2017-01-11"), Date.parse("2017-01-14"), Date.parse("2000-01-02"), Date.parse("2000-05-20")]

let resultData:[[Date]] = DateArrayConversionHelper.sortDateByMonth(dateArray: randomDates)


I'm looking for ways to improve my algorithm. I'm also still pretty new to Swift so if you're wondering why I didn't do/use x, it's usually because I didn't know better.

My end game is to use these arrays as a source for a UITableView.
I'll focus first on 

static func sortDateByMonth(dateArray:[Date]) -&gt; [[Date]]


because that contains the main logic, then mention some other points, and finally
present an alternative solution.




Your code seems to work correctly (as far as I can tell), with one exception: Apparently
it is assumed that the given array is not empty, and it will crash at

resultArray[0].append(inputArray[0])


otherwise. Adding

if dateArray.isEmpty {
    return []
}


right at the top of the function would solve that problem.
In

var inputArray:[Date] = dateArray
inputArray.sort()


the type annotation is not needed because the type can be inferred automatically:

var inputArray = dateArray
inputArray.sort()


But this can be shortened to

let inputArray = dateArray.sorted()

Similarly as above,

var resultArray:[[Date]] = [[]]
resultArray[0].append(inputArray[0])


can be combined to

var resultArray = [[inputArray[0]]]

In

for i in 1 ..&lt; (inputArray.count) { ... }


the parentheses on the right are not needed. But I prefer

for i in inputArray.indices.dropFirst() { ... }


because that works for other collection types as well, where the index is not 
necessarily zero-based (such as ArraySlice).
Inside the loop, the statement 

        resultArray[k].append(inputArray[i])


appears twice, this can be improved to

for i in inputArray.indices.dropFirst() {
    if inputArray[i].yearInt != inputArray[i-1].yearInt
        || inputArray[i].monthInt != inputArray[i-1].monthInt {
        resultArray.append([])
        k += 1
    }
    resultArray[k].append(inputArray[i])
}

Accessing the count of an array is a O(1) operation, therefore one can get even
get rid of the variable k:

for i in inputArray.indices.dropFirst() {
    if inputArray[i].yearInt != inputArray[i-1].yearInt
        || inputArray[i].monthInt != inputArray[i-1].monthInt {
        resultArray.append([]) // Start new row
    }
    resultArray[resultArray.count - 1].append(inputArray[i])
}



Now let's try to make the date comparison simpler and more efficient. Your code 
requires 4 calls to a format parsing method in each iteration. Generally, if you
are interested in the numerical values of the date components (such as year and
month number) then using 

let yearAndMonth = calendar.dateComponents([.year, .month], from: inputArray[i])


is more efficient, it computes both numbers in a single call and gives you
integers instead of strings.

But here we can do even better: There is a dedicated Calendar method to check
if two dates are equal down to some specified component:

calendar.isDate(date1, equalTo: date2, toGranularity: .month)


so that the loop becomes

    let calendar = Calendar(identifier: .gregorian)
    for i in inputArray.indices.dropFirst() {
        if !calendar.isDate(inputArray[i-1], equalTo: inputArray[i], toGranularity: .month) {
            resultArray.append([]) // Start new row
        }
        resultArray[resultArray.count - 1].append(inputArray[i])
    }


This makes your extension Formatter and some of the extension Date
properties obsolete, at least for this application.

Note also that I used the Swift Calendar type instead of the Foundation NSCalendar
type.

One can even get rid of the index i in the above loop by iterating over 
the inputArray and a shifted version of that array, and then the
complete function looks like this:

static func sortDateByMonth(dateArray:[Date]) -&gt; [[Date]] {

    if dateArray.isEmpty {
        return []
    }

    let inputArray = dateArray.sorted()
    var resultArray = [[inputArray[0]]]

    let calendar = Calendar(identifier: .gregorian)
    for (prevDate, nextDate) in zip(inputArray, inputArray.dropFirst()) {
        if !calendar.isDate(prevDate, equalTo: nextDate, toGranularity: .month) {
            resultArray.append([]) // Start new row
        }
        resultArray[resultArray.count - 1].append(nextDate)
    }
    return resultArray
}




Now some additional remarks:


Setting the timezone to NSTimeZone.default is not needed because that is the default. On the other hand, it is
a good idea to set the locale to some well-defined value, such as "en_US_POSIX",
because otherwise the formatter behavior can depend on the user's regional settings
(see Technical Q&amp;A QA1480 – NSDateFormatter and Internet Dates):

extension Date {

    static func parse(_ string: String, format: String = "yyyy-MM-dd") -&gt; Date {
        let dateFormatter = DateFormatter()
        dateFormatter.locale = Locale(identifier: "en_US_POSIX")
        dateFormatter.dateFormat = format
        return dateFormatter.date(from: string)!
    }
}


Of course this will crash if the string does not match the provided date format.
An alternative is to define a (failable) initializer method:

extension Date {

    init?(_ string: String, format: String = "yyyy-MM-dd") {
        let dateFormatter = DateFormatter()
        dateFormatter.locale = Locale(identifier: "en_US_POSIX")
        dateFormatter.dateFormat = format
        guard let date = dateFormatter.date(from: string) else { return nil }
        self = date
    }
}


which can be used as

let date = Date("2014-05-20")


and the caller decides whether to force-unwrap the result, or to safely unwrap it
with optional binding.
Your 

class DateArrayConversionHelper


is apparently used to provide a namespace for the static helper function.
If that is the only purpose and no instances of that type are ever created, then
a struct with a private init method:

struct DateArrayConversionHelper {
    private init() {}

    static func sortDateByMonth(dateArray:[Date]) -&gt; [[Date]] { ... }
    // ... other helper methods ...
}


or a caseless enum

enum DateArrayConversionHelper {

    static func sortDateByMonth(dateArray:[Date]) -&gt; [[Date]] { ... }
    // ... other helper methods ...
}


are good alternatives, compare for example
Swift constants: Struct or Enum
on Stack Overflow.




Finally an alternative approach, which might be simpler for the usage as
a table view data source.

The Dictionary(grouping:by:)
initializer (introduces in Swift 4)


  creates a new dictionary whose keys are the groupings returned by the given closure
  and whose values are arrays of the elements that returned each key.


and can in your case be used as

let calendar = Calendar(identifier: .gregorian)
let groupedDates = Dictionary(grouping: dateArray.sorted()) { (date) in
    return calendar.dateInterval(of: .month, for: date)?.start ?? Date.distantPast
}


This returns a dictionary of type [Date : [Date]] where each key is a date 
representing a start of the month, and the corresponding values are the dates in
that month.

Then

let availableMonths = groupedDates.keys.sorted()


is an array of dates, each date representing the start of one month, this can be used to compute the number
of sections and the section titles.

For each month,

let datesInMonth = groupedDates[month]


is an array of dates in that month, this can be used to compute the number of rows in a section and for the
tableView(_:cellForRowAt:) method.