72

What is the most efficient/recommended way of comparing two NSDates? I would like to be able to see if both dates are on the same day, irrespective of the time and have started writing some code that uses the timeIntervalSinceDate: method within the NSDate class and gets the integer of this value divided by the number of seconds in a day. This seems long winded and I feel like I am missing something obvious.

The code I am trying to fix is:

if (!([key compare:todaysDate] == NSOrderedDescending))
{
    todaysDateSection = [eventSectionsArray count] - 1;
}

where key and todaysDate are NSDate objects and todaysDate is creating using:

NSDate *todaysDate = [[NSDate alloc] init];

Regards

Dave

Magic Bullet Dave
  • 9,006
  • 10
  • 51
  • 81

16 Answers16

83

I'm surprised that no other answers have this option for getting the "beginning of day" date for the objects:

[[NSCalendar currentCalendar] rangeOfUnit:NSCalendarUnitDay startDate:&date1 interval:NULL forDate:date1];
[[NSCalendar currentCalendar] rangeOfUnit:NSCalendarUnitDay startDate:&date2 interval:NULL forDate:date2];

Which sets date1 and date2 to the beginning of their respective days. If they are equal, they are on the same day.

Or this option:

NSUInteger day1 = [[NSCalendar currentCalendar] ordinalityOfUnit:NSDayCalendarUnit inUnit: forDate:date1];
NSUInteger day2 = [[NSCalendar currentCalendar] ordinalityOfUnit:NSCalendarUnitDay inUnit:NSCalendarUnitEra forDate:date2];

Which sets day1 and day2 to somewhat arbitrary values that can be compared. If they are equal, they are on the same day.

fabb
  • 11,660
  • 13
  • 67
  • 111
Ed Marty
  • 39,590
  • 19
  • 103
  • 156
73

You set the time in the date to 00:00:00 before doing the comparison:

unsigned int flags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay;
NSCalendar* calendar = [NSCalendar currentCalendar];

NSDateComponents* components = [calendar components:flags fromDate:date];

NSDate* dateOnly = [calendar dateFromComponents:components];

// ... necessary cleanup

Then you can compare the date values. See the overview in reference documentation.

rptwsthi
  • 10,094
  • 10
  • 68
  • 109
Gregory Pakosz
  • 69,011
  • 20
  • 139
  • 164
  • Thanks for the quick response Gregory, this is really helpful. I guess my next question is, as this is part of a tight loop, do you think it is more efficient to use the timeIntervalSinceDate: method and some integer arithmetic, rather than creating more objects, doing the necessary computation and then cleaning up? Thanks again for your help, Dave – Magic Bullet Dave Dec 06 '09 at 10:00
  • 13
    My advice is: correct implementation is gold; then profile. If this particular loop is really a bottleneck, then optimize – Gregory Pakosz Dec 06 '09 at 10:09
  • 3
    Since you're only pulling out the Year, Month, and Day units, the Hour, Minutes, and Seconds are automatically set to 0. Hence, you don't have to explicitly do it yourself. – Dave DeLong Dec 07 '09 at 02:23
  • 1
    I tried to use this method didn't work. i am always having the time not equal to 00:00? – iosMentalist Sep 13 '12 at 08:33
  • `dateOnly` always has hours not equal to '00:00' - so the method is incorrect! – Dmitry Apr 19 '13 at 14:28
  • The answer is correct, the nonzero hour that Xcode printed is because of your local time zone is not GMT, the date that printed is explained with GMT. – KudoCC Oct 15 '14 at 05:45
  • 1
    I had to configure the calendar object with [calendar setTimeZone:[NSTimeZone timeZoneWithAbbreviation:@"GMT"]]; Otherwise the comparison does not work properly. – Vladimír Slavík May 07 '15 at 09:09
  • @VladimírSlavík I've had to do the same, even though I live in London and my device is set up with UK English as the language and region. Is this a bug or just new? – Kane Cheshire Jun 06 '15 at 13:20
  • @kanecheshire I was not investigating the issue in detail. I guess it depends on the regional/time zone settings in the phone. I rather use an explicit solution instead of expecting something. – Vladimír Slavík Jun 07 '15 at 20:41
20

There's a new method that was introduced to NSCalendar with iOS 8 that makes this much easier.

- (NSComparisonResult)compareDate:(NSDate *)date1 toDate:(NSDate *)date2 toUnitGranularity:(NSCalendarUnit)unit NS_AVAILABLE(10_9, 8_0);

You set the granularity to the unit(s) that matter. This disregards all other units and limits comparison to the ones selected.

static0886
  • 784
  • 9
  • 25
  • That's a very cool method. (voted) Pity it's only available in iOS 8. I guess if you still need to support iOS <8 you could write a method using the code I listed for iOS <8, and `compareDate:toDate:toUnitGranularity:` on iOS >=8. Then, once you drop support for OS versions <8, you could collapse the implementation down to just the call to `compareDate:toDate:toUnitGranularity:`. – Duncan C Feb 24 '15 at 00:57
  • 1
    Of course the ultimate method would be `differenceBetweenDate:andDate:usingUnit:`, that would return negative, zero, or positive integer values denoting the amount of difference between the dates in the requested uint. – Duncan C Feb 24 '15 at 01:00
  • This method can be used but the explanation for the unit parameter is wrong! It only expects the smallest unit that should be used. Apple documentation: The smallest unit that must, along with all larger units, be equal for the given dates to be considered the same – Matoz Oct 19 '16 at 14:52
  • Example usage: BOOL competitionStarted = [[NSCalendar currentCalendar] compareDate:self.competition.startDate toDate:[NSDate date] toUnitGranularity:NSCalendarUnitDay] != NSOrderedDescending; – Leon Oct 20 '17 at 14:50
17

For iOS8 and later, checking if two dates occur on the same day is as simple as:

[[NSCalendar currentCalendar] isDate:date1 inSameDayAsDate:date2]

See documentation

James
  • 3,597
  • 2
  • 39
  • 38
10

This is a shorthand of all the answers:

NSInteger interval = [[[NSCalendar currentCalendar] components: NSDayCalendarUnit
                                                                  fromDate: date1
                                                                    toDate: date2
                                                                   options: 0] day];
    if(interval<0){
       //date1<date2
    }else if (interval>0){
       //date2<date1
    }else{
       //date1=date2
    }
Bms270
  • 1,586
  • 15
  • 18
  • 1
    This code doesn't work. If you add 23 hours to [NSDate date] it will show both dates to be on the same day, which is obviously wrong in most cases. – Gottfried Mar 13 '14 at 14:55
  • @ Gottfired This will ignore the time part as the OP requested. Not sure what you mean by adding 23 hours. You mean changing the dates? – Bms270 Mar 15 '14 at 18:02
  • @Bms270 He means changing the day. If you compare today @ 3PM to tomorrow @ 9AM, your code returns a delta of 0 days, even though the second date is clearly another day. – Christian Schnorr Jul 17 '14 at 19:45
  • @Jenox Have you tried this code? Your example should return: today>tomorrow no matter what time it is. It simply ignores the hours as if you were comparing 12am with 12am. OP says " irrespective of the time". – Bms270 Jul 17 '14 at 19:56
  • The whole _point_ of the calendar methods is that they are clever. The call maps both dates into the timezone of the calendar, ignores all smaller units, and if you compare 2 am today with 1 am tomorrow, they are not on the same day, so the result is 1. – gnasher729 Feb 10 '16 at 16:55
6

I used the Duncan C approach, I have fixed some mistakes he made

-(NSInteger) daysBetweenDate:(NSDate *)firstDate andDate:(NSDate *)secondDate { 

    NSCalendar *currentCalendar = [NSCalendar currentCalendar];
    NSDateComponents *components = [currentCalendar components: NSDayCalendarUnit fromDate: firstDate toDate: secondDate options: 0];

    NSInteger days = [components day];

    return days;
}
Juan Mellado
  • 14,973
  • 5
  • 47
  • 54
jcesarmobile
  • 51,328
  • 11
  • 132
  • 176
6

I use this little util method:

-(NSDate*)normalizedDateWithDate:(NSDate*)date
{
   NSDateComponents* components = [calendar components:(NSYearCalendarUnit | NSMonthCalendarUnit | NSDayCalendarUnit)
                                              fromDate: date];
   return [calendar_ dateFromComponents:components]; // NB calendar_ must be initialized
}

(You obviously need to have an ivar called calendar_ containing an NSCalendar.)

Using this, it is easy to check if a date is today like this:

[[self normalizeDate:aDate] isEqualToDate:[self normalizeDate:[NSDate date]]];

([NSDate date] returns the current date and time.)

This is of course very similar to what Gregory suggests. The drawback of this approach is that it tends to create lots of temporary NSDate objects. If you're going to process a lot of dates, I would recommend using some other method, such as comparing the components directly, or working with NSDateComponents objects instead of NSDates.

Felixyz
  • 19,053
  • 14
  • 65
  • 60
3

From iOS 8.0 onwards, you can use:

NSCalendar *calendar = [NSCalendar currentCalendar];
NSComparisonResult dateComparison = [calendar compareDate:[NSDate date] toDate:otherNSDate toUnitGranularity:NSCalendarUnitDay];

If the result is e.g. NSOrderedDescending, otherDate is before [NSDate date] in terms of days.

I do not see this method in the NSCalendar documentation but it is in the iOS 7.1 to iOS 8.0 API Differences

Arjan
  • 16,210
  • 5
  • 30
  • 40
3

For developers coding in Swift 3

if(NSCalendar.current.isDate(selectedDate, inSameDayAs: NSDate() as Date)){
     // Do something
}
jo3birdtalk
  • 616
  • 5
  • 20
3

With Swift 3, according to your needs, you can choose one of the two following patterns in order to solve your problem.


#1. Using compare(_:to:toGranularity:) method

Calendar has a method called compare(_:​to:​to​Granularity:​). compare(_:​to:​to​Granularity:​) has the following declaration:

func compare(_ date1: Date, to date2: Date, toGranularity component: Calendar.Component) -> ComparisonResult

Compares the given dates down to the given component, reporting them ordered​Same if they are the same in the given component and all larger components, otherwise either ordered​Ascending or ordered​Descending.

The Playground code below shows hot to use it:

import Foundation

let calendar = Calendar.current
let date1 = Date() // "Mar 31, 2017, 2:01 PM"
let date2 = calendar.date(byAdding: .day, value: -1, to: date1)! // "Mar 30, 2017, 2:01 PM"
let date3 = calendar.date(byAdding: .hour, value: 1, to: date1)! // "Mar 31, 2017, 3:01 PM"

/* Compare date1 and date2 */
do {
    let comparisonResult = calendar.compare(date1, to: date2, toGranularity: .day)
    switch comparisonResult {
    case ComparisonResult.orderedSame:
        print("Same day")
    default:
        print("Not the same day")
    }
    // Prints: "Not the same day"
}

/* Compare date1 and date3 */
do {
    let comparisonResult = calendar.compare(date1, to: date3, toGranularity: .day)
    if case ComparisonResult.orderedSame = comparisonResult {
        print("Same day")
    } else {
        print("Not the same day")
    }
    // Prints: "Same day"
}

#2. Using dateComponents(_:from:to:)

Calendar has a method called dateComponents(_:from:to:). dateComponents(_:from:to:) has the following declaration:

func dateComponents(_ components: Set<Calendar.Component>, from start: Date, to end: Date) -> DateComponents

Returns the difference between two dates.

The Playground code below shows hot to use it:

import Foundation

let calendar = Calendar.current
let date1 = Date() // "Mar 31, 2017, 2:01 PM"
let date2 = calendar.date(byAdding: .day, value: -1, to: date1)! // "Mar 30, 2017, 2:01 PM"
let date3 = calendar.date(byAdding: .hour, value: 1, to: date1)! // "Mar 31, 2017, 3:01 PM"

/* Compare date1 and date2 */
do {
    let dateComponents = calendar.dateComponents([.day], from: date1, to: date2)
    switch dateComponents.day {
    case let value? where value < 0:
        print("date2 is before date1")
    case let value? where value > 0:
        print("date2 is after date1")
    case let value? where value == 0:
        print("date2 equals date1")
    default:
        print("Could not compare dates")
    }
    // Prints: date2 is before date1
}

/* Compare date1 and date3 */
do {
    let dateComponents = calendar.dateComponents([.day], from: date1, to: date3)
    switch dateComponents.day {
    case let value? where value < 0:
        print("date2 is before date1")
    case let value? where value > 0:
        print("date2 is after date1")
    case let value? where value == 0:
        print("date2 equals date1")
    default:
        print("Could not compare dates")
    }
    // Prints: date2 equals date1
}
Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
3

The answer is simpler than everybody makes it out to be. NSCalendar has a method

components:fromDate:toDate:options

That method lets you calculate the difference between two dates using whatever units you want.

So write a method like this:

-(NSInteger) daysBetweenDate: (NSDate *firstDate) andDate: (NSDate *secondDate)
{
  NSCalendar *currentCalendar = [NSCalendar currentCalendar];
  NSDateComponents components* = [currentCalendar components: NSDayCalendarUnit
    fromDate: firstDate 
    toDate: secondDate
    options: 0];

  NSInteger days = [components days];
  return days;
}

If the above method returns zero, the two dates are on the same day.

David Rönnqvist
  • 56,267
  • 18
  • 167
  • 205
Duncan C
  • 128,072
  • 22
  • 173
  • 272
1
int interval = (int)[firstTime timeIntervalSinceDate:secondTime]/(60*60*24);
if (interval!=0){
   //not the same day;
}
ChihHao
  • 273
  • 1
  • 4
  • 15
1

my solution was two conversions with NSDateFormatter:

    NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init];
    [dateFormat setDateFormat:@"yyyyMMdd"];
    [dateFormat setTimeZone:[NSTimeZone timeZoneWithName:@"GMT"]];

    NSDate *today = [NSDate dateWithTimeIntervalSinceNow:0];
    NSString *todayString=[dateFormat stringFromDate:today];
    NSDate *todayWithoutHour=[dateFormat dateFromString:todayString];

    if ([today compare:exprDate] == NSOrderedDescending)
    {
       //do  
    }
Zaster
  • 860
  • 7
  • 6
0

This is a particularly ugly cat to skin, but here's another way to do it. I don't say it's elegant, but it's probably as close as you can get with the date/time support in iOS.

bool isToday = [[NSDateFormatter localizedStringFromDate:date dateStyle:NSDateFormatterFullStyle timeStyle:NSDateFormatterNoStyle] isEqualToString:[NSDateFormatter localizedStringFromDate:[NSDate date] dateStyle:NSDateFormatterFullStyle timeStyle:NSDateFormatterNoStyle]];
Scott Means
  • 635
  • 6
  • 8
0

The documentation regarding NSDate indicates that the compare: and isEqual: methods will both perform a basic comparison and order the results, albeit they still factor in time.

Probably the simplest way to manage the task would be to create a new isToday method to the effect of the following:

- (bool)isToday:(NSDate *)otherDate
{
    currentTime = [however current time is retrieved]; // Pardon the bit of pseudo-code

    if (currentTime < [otherDate timeIntervalSinceNow])
    {
        return YES;
    }
    else
    {
        return NO;
    }
}
Kaji
  • 2,220
  • 6
  • 30
  • 45
0
NSUInteger unit = NSMonthCalendarUnit | NSDayCalendarUnit | NSHourCalendarUnit;
NSDateComponents *comp = [cal components:unit
                                fromDate:nowDate
                                  toDate:setDate
                                 options:0];

NSString *dMonth;
dMonth = [NSString stringWithFormat:@"%02ld",comp.month];
NSString *dDay;
dDay = [NSString stringWithFormat:@"%02ld",comp.day + (comp.hour > 0 ? 1 : 0)];

compare hour as well to fix 1day difference

Minho Yi
  • 210
  • 2
  • 6