87

I have an NSArray that contains date strings (i.e. NSString) like this: "Thu, 21 May 09 19:10:09 -0700"

I need to sort the NSArray by date. I thought about converting the date string to an NSDate object first, but got stuck there on how to sort by the NSDate object.

Thanks.

Abhishek Bedi
  • 5,205
  • 2
  • 36
  • 62
postalservice14
  • 2,515
  • 4
  • 25
  • 33

9 Answers9

115

If I have an NSMutableArray of objects with a field "beginDate" of type NSDate I am using an NSSortDescriptor as below:

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"beginDate" ascending:TRUE];
[myMutableArray sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[sortDescriptor release];
Paras Joshi
  • 20,427
  • 11
  • 57
  • 70
vinzenzweber
  • 3,379
  • 4
  • 24
  • 26
  • 3
    I know a lot of time passed(when previous answer was accepted there could be no NSSortDescriptor yet), but for future use this one should be the proper answer. – Nat Aug 27 '12 at 15:05
  • 1
    This is an awesome solution. solved my problem in a few lines of code.Thanx a ton @vinzenzweber – Pratyusha Terli Sep 27 '12 at 07:08
  • 1
    Really...I was actually shocked this worked on first run! I thought initWithKey was reserved for dictionaries where the key is defined, but all you need to do is reference a property in an array of objects? Let the light shine vinzenzweberskavitchski!! – whyoz Oct 19 '12 at 22:12
  • NSSortDescriptor has been around since OS X 10.3, in late 2003. This is a slightly different scenario, where the date is a field in another object. With even-more-modern API, you might as well use `-[NSMutableArray sortUsingComparator:]` (10.6+) and pass a block that returns the result of calling `-compare:` on the two fields. It would probably be faster than calling `-valueForKey:` on each object. You could even use `-sortWithOptions:usingComparator:` and pass options to sort concurrently as well. – Quinn Taylor Nov 01 '12 at 23:32
  • how does the above NSSortDescriptor handle nil dates? Do we need to tell the desecriptor what to do with them if so, how? – Ash Nov 25 '13 at 22:42
  • You can't store nil in an NSArray, so it's not clear what you are asking. – smorgan Nov 30 '13 at 23:53
  • Thank you. Now I understand the difference between sorting by object and sorting by object's keypath. You saved my day. – Li Fumin Aug 28 '15 at 06:04
  • Great on iOS 11 (removing "[sortDescriptor release];") – Fernando García Corrochano Nov 06 '17 at 15:49
112

Store the dates as NSDate objects in an NS(Mutable)Array, then use -[NSArray sortedArrayUsingSelector: or -[NSMutableArray sortUsingSelector:] and pass @selector(compare:) as the parameter. The -[NSDate compare:] method will order dates in ascending order for you. This is simpler than creating an NSSortDescriptor, and much simpler than writing your own comparison function. (NSDate objects know how to compare themselves to each other at least as efficiently as we could hope to accomplish with custom code.)

Community
  • 1
  • 1
Quinn Taylor
  • 44,553
  • 16
  • 113
  • 131
  • 11
    It's not clear if the original question was asking about sorting an array of dates, or sorting an array of objects that have a date field. This is the easiest approach if it's the former, but if it's the latter, NSSortDescriptor is the way to go. – smorgan Jul 16 '09 at 03:48
  • @smorgan how does NSSortDescriptor handle nil dates? – Ash Nov 25 '13 at 22:35
  • Thanks for your help, I think we should read more in Apple Docs but it seems so boring, until you use it. – Atef Dec 22 '13 at 17:28
  • 1
    Could you show this in a snippet code as an example? Thanks in advance. – uplearned.com Aug 29 '14 at 06:42
17

You may also use something like the following:

//Sort the array of items by  date
    [self.items sortUsingComparator:^NSComparisonResult(id obj1, id obj2){
        return [obj2.date compare:obj1.date];
    }];

But this does assume that the date is stored as a NSDate rather a NString, which should be no problem to make/do. Preferably, I recommend also storing the data in it's raw format. Makes it easier to manipulate in situations like this.

Abhishek Bedi
  • 5,205
  • 2
  • 36
  • 62
TheCodingArt
  • 3,436
  • 4
  • 30
  • 53
10

You can use blocks to sort in place:

sortedDatesArray = [[unsortedDatesArray sortedArrayUsingComparator: ^(id a, id b) {
    NSDate *d1 = [NSDate dateWithString: s1];
    NSDate *d2 = [NSDate dateWithString: s2];
    return [d1 compare: d2];
}];

I suggest you convert all your strings to dates before sorting not to do the conversion more times than there are date items. Any sorting algorithm will give you more string to date conversions than the number of items in the array (sometimes substantially more)

a bit more on blocks sorting: http://sokol8.blogspot.com/2011/04/sorting-nsarray-with-blocks.html

Quinn Taylor
  • 44,553
  • 16
  • 113
  • 131
  • This is a variant of the same sub-optimal idea presented by @notoop. In any case, converting to NSDate first is definitely the way to go. – Quinn Taylor Jan 23 '12 at 08:17
  • He basically just put my answer with @notoop's answer..... quite unnecessary to double post those if you ask me.... – TheCodingArt Mar 26 '13 at 15:15
  • This is much slower than parse date strings into array of NSDate and then sort the array of NSDate, cos there will be 2 * (n - 1) times extra parsing of string. Parsing strings is generally heavy job on CPU. – CarlLee Sep 09 '14 at 06:23
5

You can use sortedArrayUsingFunction:context:. Here is a sample:

NSComparisonResult dateSort(NSString *s1, NSString *s2, void *context) {
    NSDate *d1 = [NSDate dateWithString:s1];
    NSDate *d2 = [NSDate dateWithString:s2];
    return [d1 compare:d2];
}

NSArray *sorted = [unsorted sortedArrayUsingFunction:dateSort context:nil];

When using a NSMutableArray, you can use sortArrayUsingFunction:context: instead.

Alexandre
  • 13
  • 1
  • 4
notnoop
  • 58,763
  • 21
  • 123
  • 144
  • 10
    This is a bad idea — a comparison function should be fast, and shouldn't have to create NSDate objects from strings for each comparison. If you're already using -[NSDate compare:], just store the dates in the array and use -sortedArrayUsingSelector: directly. – Quinn Taylor Jul 15 '09 at 21:14
  • I think this is a good solution if you are sorting objects that already have an NSDate field though. No? – GnarlyDog Aug 28 '12 at 22:12
  • 1
    If the array already contains `NSDate` objects, you can use my answer above, or even use `-[NSMutableArray sortUsingComparator:]`, which was added in 10.6. – Quinn Taylor Nov 01 '12 at 23:35
  • If you are using an array of dictionary in which there is a key-value object for date as NSString along with some other key-value objects, then this solution is the best so far. Just need a bit modification inside the dateSort function. Thumbs up! :) – Erfan Jul 10 '15 at 17:34
5

What it worked in my case was the following:

    NSArray *aUnsorted = [dataToDb allKeys];
    NSArray *arrKeys = [aUnsorted sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        NSDateFormatter *df = [[NSDateFormatter alloc] init];
        [df setDateFormat:@"dd-MM-yyyy"];
        NSDate *d1 = [df dateFromString:(NSString*) obj1];
        NSDate *d2 = [df dateFromString:(NSString*) obj2];
        return [d1 compare: d2];
    }];

I had a dictionary, where all keys where dates in format dd-MM-yyyy. And allKeys returns the dictionary keys unsorted, and I wanted to present the data in chronological order.

4

Once you have an NSDate, you can create an NSSortDescriptor with initWithKey:ascending: and then use sortedArrayUsingDescriptors: to do the sorting.

Paras Joshi
  • 20,427
  • 11
  • 57
  • 70
smorgan
  • 20,228
  • 3
  • 47
  • 55
  • This approach will work, but using -sortedArrayUsingSelector: is a bit simpler and doesn't require creating an extra object. – Quinn Taylor Jul 15 '09 at 21:28
  • Pre-editing, the question mentioned "a 'date' key", so I assumed this was really an array of objects/dictionaries that had a date as one field, not an array of raw dates. In that case sortedArrayUsingSelector: is more complex, since it requires writing a custom sort routine to do the lookup. – smorgan Jul 16 '09 at 03:44
  • Yeah, sorry for any confusion — I cleaned up that reference since he was just saying he was storing his array with a 'date' key, and I realized it just made the question more confusing. You're definitely correct about relative complexity given a more complex data structure. – Quinn Taylor Jul 16 '09 at 04:24
2

Swift 3.0

myMutableArray = myMutableArray.sorted(by: { $0.date.compare($1.date) == ComparisonResult.orderedAscending })
Abhishek Jain
  • 4,557
  • 2
  • 32
  • 31
1

Change this

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"beginDate" ascending:TRUE];
[myMutableArray sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[sortDescriptor release];

To

NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"Date" ascending:TRUE];
[myMutableArray sortUsingDescriptors:[NSArray arrayWithObject:sortDescriptor]];
[sortDescriptor release];

Just change the KEY: it must be Date always

nhahtdh
  • 55,989
  • 15
  • 126
  • 162