1

I have an array of NSDate objects. These objects have a year value, but I don't care about that. I want to sort the dates by most recent using only month and day. So in July, for example, a June date would be near the top of the list and an August date would be near the bottom, regardless of the years contained in those NSDate objects.

I'm having trouble envisioning a way to accomplish this. Does anyone have an elegant solution for this problem?

Thanks!

EDIT: Apparently I didn't explain very clearly what I'm looking for. I want to sort the array so that the most recent date is first, ignoring year. So for example, given MM/dd/yy format, if today is 06/05/14, and my array contains these dates:

01/04/14 04/05/14 07/21/14 12/30/14

The correct order I want is:

04/05 01/04 12/30 07/21

James Harpe
  • 4,315
  • 8
  • 47
  • 74
  • I'd just make a comparator function that used an NSDateFormatter to format the two dates sans year and compare the resulting strings. Be sure to set the timezone appropriately. – Hot Licks Jan 14 '14 at 20:55
  • So you want to sort relative to today, sans year. Like a birthday list. – Hot Licks Jan 14 '14 at 21:15

3 Answers3

4

You can write a custom comparer function, that sorts on the month and day parts. Use sortedArrayUsingComparator, for instance. Pseudo code (written from memory not compiled nor tried):

NSArray* sortedArray = [yourArray sortedArrayUsingComparator:^NSComparisonResult(NSDate* lhs, NSDate* rhs)
{
    // Use NSDateComponents to get the month and day from each date.

    int diff = lhs.month - rhs.month;
    if (diff == 0)
        diff = lhs.day - rhs.day;

    if (diff < 0)
        return NSOrderedDescending;

    if (diff > 0)
        return NSOrderedAscending;

    return NSOrderedSame;    
}];

Edit

Code example is incomplete because NSdate does not have day or month methods. You need to jump trough some hoops first to get the components, see NSDate get year/month/day. Sorry, not on a Mac right now so I cannot try it out.

Community
  • 1
  • 1
driis
  • 161,458
  • 45
  • 265
  • 341
  • Where do you get the `month` and `day` methods on NSDate? – Hot Licks Jan 14 '14 at 20:54
  • This would just sort the dates chronologically though right? I want to sort the array so that the most recent (in terms of month and day) is first. – James Harpe Jan 14 '14 at 20:59
  • @JamesHarpe - That's just a question of whether you sort ascending or descending, and you can change it by interchanging the operands in the comparator. – Hot Licks Jan 14 '14 at 21:00
  • @HotLicks, you are right, coding by memory did not serve me well. `month` and `day` does not exist (even though I think they should). Edited the answer. – driis Jan 14 '14 at 21:05
  • @HotLicks Please see my edit, perhaps that will explain what I'm going for. – James Harpe Jan 14 '14 at 21:05
2

Like stated here, you can get the date components of an NSDate and sort by that value.

NSDateComponents *components = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth | NSCalendarUnitYear fromDate:[NSDate date]];

NSInteger day = [components day];
NSInteger month = [components month];
NSInteger year = [components year];

You can ignore any values you would like.

EDIT: Just combining the two answers given. Credit to driis for the sorting code.

NSArray* sortedArray = [yourArray sortedArrayUsingComparator:^NSComparisonResult(NSDate* lhs, NSDate* rhs)
{
    NSDateComponents *lscomponents = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth fromDate:lhs];
    NSDateComponents *rhcomponents = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth fromDate:rhs];
    NSDateComponents *currentcomponents = [[NSCalendar currentCalendar] components:NSCalendarUnitDay | NSCalendarUnitMonth fromDate:[NSDate date]];

    NSInteger lday = [lscomponents day];
    NSInteger lmonth = [lscomponents month];
    NSInteger rday = [rhcomponents day];
    NSInteger rmonth = [rhcomponents month];

    if(lmonth < [currentcomponents month])
        lmonth += 12;
    else if(lmonth == [currentcomponents month])
    {
        if(lday < [currentcomponents day])
            lmonth += 12;
    }

    if(rmonth < [currentcomponents month])
        rmonth += 12;
    else if(rmonth == [currentcomponents month])
    {
        if(rday < [currentcomponents day])
            rmonth += 12;
    }
    int diff = lmonth - rmonth;
    if (diff == 0)
        diff = lday - rday;

    if (diff < 0)
        return NSOrderedDescending;

    if (diff > 0)
        return NSOrderedAscending;

    return NSOrderedSame;    
}];

You can make this much faster by cacheing the calendar.

EDIT2:

I have updated my sorting to take into account your custom need. If the month and day is before right now you add 12 to the month. Then compare based on the new pseudo-month. That should give your custom sorting around today.

Community
  • 1
  • 1
Putz1103
  • 6,211
  • 1
  • 18
  • 25
1
NSDate* today = [NSDate date];
NSDate* left = <left side value>;
NSDate* right = <right side value>;

NSDateFormatter* dateFormatter = [NSDateFormatter new];

// Set time zone as desired

/* Skip this
** Get an NSDate for Dec 31 this year, so we can calc it's day-of-year.  But note that it would probably suffice to just use the literal value 366 all the time.
[dateFormatter setDateFormat:@"yyyy"];
NSString* yearString = [dateFormatter stringFromDate:today];
NSString* dec31Dummy = [yearString stringByAppendingString:@"/12/31"];

[dateFormatter setDateFormat:@"yyyy/MM/dd"];
NSDate* dec31 = [dateFormatter dateFromString:dec31Dummy];
*/

// Date format for "day-of-year"
[dateFormatter setDateFormat:@"D"];

// Note that these are retrieving "day-of-year" values
NSString* todayString = [dateFormatter stringFromDate:today];
NSString* leftString = [dateFormatter stringFromDate:left];
NSString* rightString = [dateFormatter stringFromDate:right];
// NSString* dec31String = [dateFormatter stringFromDate:dec31];

// These convert the day-of-year values to numeric
int todayNum = [todayString intValue];
int leftNum = [leftString intValue];
int rightNum = [rightString intValue];
// int dec31Num = [dec31String intValue]; 
int dec31Num = 366;

// Adjust for dates that have already passed this year -- push them out into next year
If (leftNum < todayNum) leftNum += dec31Num;
if (rightNum < todayNum) rightNum += dec31Num;

int diff = leftNum - rightNum;
if (diff < 0) { 
    return NSOrderedDescending;
}
else if (diff > 0) {
    return NSOrderedAscending;
}
else {
    return NSOrderedSame;
}

Of course, one would want to move the invariant setup logic out of the comparitor proper.

Hot Licks
  • 47,103
  • 17
  • 93
  • 151
  • More concise, yes. Well answered. – Putz1103 Jan 14 '14 at 21:51
  • Of course, another way to do it would be to simply sort by date, sans year, and then "rotate" the array until today's date (or the first past it) came up. – Hot Licks Jan 14 '14 at 22:07
  • I thought about that, but the adding a year seemed so much easier to understand. And it would only require one manipulation block to be placed in the code. – Putz1103 Jan 14 '14 at 22:08