79

I have an array containing an object called HistoryObject and it has properties such as "date", "name", etc.

I am sorting the array like so:

 let sortedArray = HistoryArray.sort({ $0.date.compare($1.date) == NSComparisonResult.OrderedDescending})

which is supposed to sort the date from newer to oldest. For example:

  • Jun 30, 2016
  • Jun 29, 2016

etc..

But when my array contains "Jul 2, 2016" the sorted array becomes:

  • Jun 30, 2016
  • Jun 29, 2016
  • Jul 2, 2016

Where "Jul 2, 2016" should be on top after sorting, now it's on the bottom? How can I fix this problem?

3
  • Did you used the sort function again? This cannot be set automatically. The only way to sort it every time you add new object, you can create array extension and every time you use AddObject for example, you have to call this sort function. Commented Jul 3, 2016 at 9:41
  • i meant after i get the full array from api, i sort the whole array with its objects, meaning i don't add objects after sorting. but its not sorting it correctly. @AltimirAntonov Commented Jul 3, 2016 at 9:43
  • Convert the string to NSDate first. I will write the answer in few minutes. Commented Jul 3, 2016 at 9:56

7 Answers 7

151

Using Swift 4 & Swift 3

let testArray = ["25 Jun, 2016", "30 Jun, 2016", "28 Jun, 2016", "2 Jul, 2016"]
var convertedArray: [Date] = []

var dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd MM, yyyy"// yyyy-MM-dd"

for dat in testArray {
    let date = dateFormatter.date(from: dat)
    if let date = date {
        convertedArray.append(date)
    }
}

var ready = convertedArray.sorted(by: { $0.compare($1) == .orderedDescending })

print(ready)

Using Swift 2

For example you have the array with dates and another 1 array, where you will save the converted dates:

var testArray = ["25 Jun, 2016", "30 Jun, 2016", "28 Jun, 2016", "2 Jul, 2016"]
var convertedArray: [NSDate] = []

After that we convert the dates:

var dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "dd MM, yyyy"// yyyy-MM-dd"

for dat in testArray {
    var date = dateFormatter.dateFromString(dat)
    convertedArray.append(date!)
}

And the result:

var ready = convertedArray.sort({ $0.compare($1) == .OrderedDescending })

print(ready)
Sign up to request clarification or add additional context in comments.

1 Comment

You don't answer the OPs question. The OP wants to sort an array of objects that contain date strings. Your code only sorts dates.
29

For Swift 3

var testArray = ["25 Jun, 2016", "30 Jun, 2016", "28 Jun, 2016", "2 Jul, 2016"]
var convertedArray: [Date] = []

var dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd/MM/yyyy"

for dat in testArray {
    var date = dateFormatter.date(from: dat)
    convertedArray.append(date!)
}

//Approach : 1
convertedArray.sort(){$0 < $1}    

//Approach : 2
convertedArray.sorted(by: {$0.timeIntervalSince1970 < $1.timeIntervalSince1970})

print(convertedArray)

1 Comment

You don't answer the OPs question. The OP wants to sort an array of objects that contain date strings. Your code only sorts dates.
10

Avoiding extra variable of convertedArray

Using Swift 4 & Swift 3

let testArray = ["25 Jun, 2016", "30 Jun, 2016", "28 Jun, 2016", "2 Jul, 2016"]

var dateFormatter = DateFormatter()
dateFormatter.dateFormat = "dd MM, yyyy"// yyyy-MM-dd"

var ready = convertedArray.sorted(by: { dateFormatter.date(from:$0).compare(dateFormatter.date(from:$1)) == .orderedDescending })

print(ready)

1 Comment

Note that converting strings to dates is quite time-consuming, and doing it inside the closure to a sorted() call makes the sort very slow. I did a test and found that sorting an object containing date strings and using a DateFormatter inside the sorted() closure like you suggest made the sort take ≈ 1200 TIMES LONGER than converting all the date strings to dates and then sorting them. Thus this a very bad idea.
9

Swift 5

  1. Assuming we have an array of objects with a date as a String:
struct Test {
    let dateStr: String
}
let testObjects = ["25 Jun, 2016", "30 Jun, 2016", "28 Jun, 2016", "12 Jul, 2016"]
    .map(Test.init(dateStr:))
  1. Create a DateFormatter:
var dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
dateFormatter.dateFormat = "dd MMM, yyyy"
  1. map each item to a tuple (Test, Date), then sort the whole array by date and then map again to Test object:
let convertedObjects = testObjects
    .map { return ($0, dateFormatter.date(from: $0.dateStr)!) }
    .sorted { $0.1 > $1.1 }
    .map(\.0)

In a similar way we can sort an array of Dates as Strings:

  1. Assuming we have an array:
let testArray = ["25 Jun, 2016", "30 Jun, 2016", "28 Jun, 2016", "12 Jul, 2016"]
  1. Create a DateFormatter:
var dateFormatter = DateFormatter()
dateFormatter.locale = Locale(identifier: "en_US_POSIX")
dateFormatter.timeZone = TimeZone(abbreviation: "UTC")
dateFormatter.dateFormat = "dd MMM, yyyy"
  1. map each item to Date and then sort the whole array:
let convertedArray = testArray
    .compactMap(dateFormatter.date(from:))
    .sorted(by: >)

2 Comments

the correct dateFormat is MMM (not MMMM or MM shown at the accepted answer).
@LeoDabus Yes, thanks - I think I just assumed that an accepted answer with 100+ upvotes is correct. I should've checked it.
6

You may want to use ".sort" instead of ".sorted" :

convertedArray.sort { (($0)?.compare($1))! == .orderedDescending }

2 Comments

It would be worth noting what the difference between .sort() and .sorted() is.
I don't know why this is not the accepted answer. Much cleaner. Not much of a difference but if you are comparing a string timestamp. array.sort(by: {$0.timestamp.compare($1.timestamp) == .orderedDescending })
3

You have an array historyArray that contains an array of HistoryObject. Each HistoryObject contains a date string in the form "MM dd, yyyy"

Edit:

(You want to sort your history objects by their date values. It is a bad idea to try to sort objects with date strings by those date strings, since you have to convert the date strings to Cocoa Date objects for every comparison, so you end up converting the dates to date objects over and over and over. In a benchmark I did, this causes the sort to run 1200X slower than if you batch-convert your date strings to Date objects before sorting, as outlined below.)

In order to do that efficiently you need to first get Date values for all of the objects. One way to do that would be to add a lazy Date var to your HistoryObject that is calculated from the date string. If you don't want to do that you can:

  1. Map your array of history objects to an array of Date objects using a DateFormatter.
  2. Use the zip() function to combine the array of history objects and the array of date objects into an array of tuples.
  3. Sort the array of tuples.
  4. Map the array of tuples back to an array of history objects.

The code to do that might look something like this:

Version 1

var dateFormatter = DateFormatter()
dateFormatter.dateFormat = "MM dd, yyyy"

//I don't know what your HistoryObject looks like, so I'll fake it.
struct HistoryObject: CustomStringConvertible {
    let dateString: String
    let value: Int
    
    var description: String {
        return "date: \(dateString), value: \(value)"
    }
}

//Create an array of date strings.
let testArray = ["Jun 25, 2016", "Jun 30, 2016", "Jun 28, 2016", "Jul 2, 2016"]

//Use the array of date strings to create an array of type [HistoryObject]
let historyArray: [HistoryObject] = testArray.map {
    let value = Int(arc4random_uniform(1000))
    return HistoryObject(dateString: $0, value: value)
}

print("\n-----> Before sorting <-----")
historyArray.forEach { print($0) }

//Create an array of the `Dates` for each HistoryObject
let historyDates: [Date] = historyArray.map { dateFormatter.date(from: $0.dateString)!
}

//Combine the array of `Dates` and the array of `HistoryObjects` into an array of tuples
let historyTuples = zip(historyArray, historyDates)

//Sort the array of tuples and then map back to an array of type [HistoryObject]
let sortedHistoryObjects = historyTuples.sorted { $0.1 > $1.1}
    .map {$0.0}

print("\n-----> After sorting <-----")
sortedHistoryObjects.forEach { print($0) }

If you add a lazy var date to your HistoryObject the sorting code is a LOT simpler:

Version 2:

//I don't know what your HistoryObject looks like, so I'll fake it.
class HistoryObject: CustomStringConvertible {
    let dateString: String
    lazy var date: Date = { dateFormatter.date(from: self.dateString)! }()
    let value: Int
    
    var description: String {
        return "date: \(dateString), value: \(value)"
    }
    
    init(dateString: String, value: Int) {
        self.dateString = dateString
        self.value = value
    }
}

//Create an array of date strings.
let testArray = ["Jun 25, 2016", "Jun 30, 2016", "Jun 28, 2016", "Jul 2, 2016"]

//Use the array of date strings to create an array of type [HistoryObject]
let historyArray: [HistoryObject] = testArray.map {
    let value = Int(arc4random_uniform(1000))
    return HistoryObject(dateString: $0, value: value)
}

print("\n-----> Before sorting <-----")
historyArray.forEach { print($0) }

let sortedHistoryArray = historyArray.sorted { $0.date > $1.date }
print("\n-----> After sorting <-----")
sortedHistoryArray.forEach { print($0) }

Comments

1

Please try this, It work for me

let sortArr = prescriptionList?.medicationList?.sorted(by:{
            ($0.medicationDate?.stringDateToInt(requireFormatter: "yyyy-MM-dd HH:mm:ss", needFormatter: "yyyy-MM-dd HH:mm:ss", dateStr: $0.medicationDate ?? ""))! > $1.medicationDate!.stringDateToInt(requireFormatter: "yyyy-MM-dd HH:mm:ss", needFormatter: "yyyy-MM-dd HH:mm:ss", dateStr: $1.medicationDate ?? "")
        } )

extension String {
    
    func stringDateToInt(requireFormatter: String, needFormatter: String, dateStr: String) -> Int {
        var dateInt: Int = 0
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "\(requireFormatter)"
        if let date = dateFormatter.date(from: dateStr) {
            dateFormatter.dateFormat = "\(needFormatter)"
            dateInt = date.millisecondsSince1970
        }
        return dateInt
    }
}

1 Comment

I did not try running this code, but it led me to the answer. I'm running Swift 5. I found the dates sorted correctly for month & day, but not year. My date format was "MM dd, yyyy HH:mm a". After changing it to "yyyy MM dd, HH:mm a", I found that the year sorted first, then the month, then the day.

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.