154

How could I determine the number of days between two NSDate values (taking into consideration time as well)?

The NSDate values are in whatever form [NSDate date] takes.

Specifically, when a user enters the inactive state in my iPhone app, I store the following value:

exitDate = [NSDate date];

And when they open the app back up, I get the current time:

NSDate *now = [NSDate date];

Now I'd like to implement the following:

-(int)numberOfDaysBetweenStartDate:exitDate andEndDate:now
Bhavin Ramani
  • 3,221
  • 5
  • 30
  • 41
CodeGuy
  • 28,427
  • 76
  • 200
  • 317

16 Answers16

414

Here's an implementation I used to determine the number of calendar days between two dates:

+ (NSInteger)daysBetweenDate:(NSDate*)fromDateTime andDate:(NSDate*)toDateTime
{
    NSDate *fromDate;
    NSDate *toDate;

    NSCalendar *calendar = [NSCalendar currentCalendar];

    [calendar rangeOfUnit:NSCalendarUnitDay startDate:&fromDate
        interval:NULL forDate:fromDateTime];
    [calendar rangeOfUnit:NSCalendarUnitDay startDate:&toDate
        interval:NULL forDate:toDateTime];

    NSDateComponents *difference = [calendar components:NSCalendarUnitDay
        fromDate:fromDate toDate:toDate options:0];

    return [difference day];
}

EDIT:

Fantastic solution above, here's Swift version below as an extension on NSDate:

extension NSDate {
  func numberOfDaysUntilDateTime(toDateTime: NSDate, inTimeZone timeZone: NSTimeZone? = nil) -> Int {
    let calendar = NSCalendar.currentCalendar()
    if let timeZone = timeZone {
      calendar.timeZone = timeZone
    }

    var fromDate: NSDate?, toDate: NSDate?

    calendar.rangeOfUnit(.Day, startDate: &fromDate, interval: nil, forDate: self)
    calendar.rangeOfUnit(.Day, startDate: &toDate, interval: nil, forDate: toDateTime)

    let difference = calendar.components(.Day, fromDate: fromDate!, toDate: toDate!, options: [])
    return difference.day
  }
}

A bit of force unwrapping going on which you may want to remove depending on your use case.

The above solution also works for time zones other than the current time zone, perfect for an app that shows information about places all around the world.

Sam
  • 5,892
  • 1
  • 25
  • 27
Brian
  • 15,599
  • 4
  • 46
  • 63
  • 7
    why dont you use, NSDateComponents *difference = [calendar components:NSDayCalendarUnit fromDate:fromDate toDate:toDate options:0]; only. – karim Jul 19 '11 at 13:52
  • 12
    @karim if you only use that function the difference won't be in "calendar days". – João Portela Dec 30 '11 at 16:04
  • 40
    @karim: Just to provide an example, to clarify what João said: If fromDateTime = Feb 1st 11:00pm and toDateTime = Feb 2nd 01:00am the result should be 1 (even though it's only 2 hours, but it's another date). Without stripping the time part of the dates (which is done by the calls to `rangeOfUnit:...`), the result would be 0 (because 2h < 1 day). – Daniel Rinser Aug 22 '12 at 12:31
  • I'm interested in returning a value that is negative if the toDate is in the past and positive if it's in the future. This method doesn't work for that. I get an off by one error for any negative dates. @Biosopher's answer works better for me. – Xander Dunn May 19 '14 at 21:52
  • The only one solution that worked for me! – Thomás Pereira Jul 29 '14 at 14:23
  • NSDayCalendarUnit id deprecated now – ariel_556 Mar 04 '15 at 18:07
  • 1
    Karim - nice solution, this is what I was looking for. – Richard Topchii Mar 24 '15 at 17:19
  • This solution just have a problem! It does not consider timezone! so there are time at which the result are incorrect...(maximum 1 day error). The timezone for both NSDates should be the same. – Majid Apr 02 '15 at 21:52
  • 4
    I've added "[calendar setTimeZone:[NSTimeZone timeZoneForSecondsFromGMT:0]];" before "rangeOfUnit" methods, now it works correctly for me (local timezone of running environment is set to +4:30 GMT)! – Majid Apr 02 '15 at 22:37
  • Though this has worked very well previously, it has seem to have broken in Swift 3. Now, it is very simple, and can be done instead in one line. The following code should work: `let dateComparison = Calendar.current().compare(date1, to: date2, toUnitGranularity: .day).rawValue` – Acoop Jun 17 '16 at 15:36
  • Swift 3: `Calendar.current.dateComponents([.day], from: date1, to: date2).day!` – Blixt Oct 07 '16 at 21:40
  • @DanielRinser why should it be 1 day difference if the difference is 2 hours, i.e. 0 days difference. – dollar2048 Mar 16 '18 at 22:13
  • @dollar2048 Because it's the next day, and people might want to consider this as 1 day different for their needs. – Leon May 16 '18 at 10:26
118

Here's the best solution I've found. Seems to utilize the Apple approved method for determining any amount of units between NSDates.

- (NSInteger)daysBetween:(NSDate *)dt1 and:(NSDate *)dt2
{
    NSUInteger unitFlags = NSCalendarUnitDay;
    NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
    NSDateComponents *components = [calendar components:unitFlags fromDate:dt1 toDate:dt2 options:0];
    return [components day] + 1;
}

E.g. if you want months as well, then you could include 'NSMonthCalendarUnit' as a unitFlag.

To credit the original blogger, I found this info here (although there was a slight mistake that I've fixed above): http://cocoamatic.blogspot.com/2010/09/nsdate-number-of-days-between-two-dates.html?showComment=1306198273659#c6501446329564880344

Mario
  • 2,397
  • 2
  • 24
  • 41
Biosopher
  • 1,213
  • 1
  • 8
  • 2
  • That's the apropriate way,really.At WWDC Apple told us not to count days by dividing to number of seconds in day - that's a REALLy bad practice. – Nikita Pestrov Apr 04 '12 at 06:45
  • 8
    This is the method from the Apple docs. This should be accepted – Jonathan. Jul 07 '12 at 12:35
  • 1
    Where is the declaration for calendar? Why not implement this as a category? – Rob Sep 09 '12 at 22:15
  • Does this not leak the `NSCalendar`? – i_am_jorf Mar 25 '13 at 01:14
  • Not with ARC, @jeffamaphone. – Moshe Apr 11 '13 at 01:56
  • Side effect is that passing the same NSDate value for both parameters you will get 1, not 0. – jki Apr 12 '13 at 17:23
  • the +1 in the last line return value is incorrect and should be removed. Otherwise works great. – harryhorn Jul 02 '13 at 06:06
  • @harryhorn Well, it depends on whether or not you want it inclusive of the first date... – gtmtg Jul 24 '13 at 03:43
  • 3
    `return [components day]+1;` should be `return ABS([components day])+1;` to prevent wrong day count on negative value – Tek Yin Jul 25 '13 at 08:04
  • 5
    It works incorrect. It gives the same result for the same dates, and for dates with 1 day difference. components.day is 0 in this case. – Shmidt Nov 24 '13 at 19:17
  • 2
    I want a method that returns negative values for a toDate that's in the past and positive values for a toDate that's in the future, as well as 0 if the toDate is the same day as the fromDate. This method does that for me if I remove the +1 in the last line of code. – Xander Dunn May 19 '14 at 21:54
  • This doesn't take into effect timezones even when I set the timezone of the calendar. Brian's method above worked for me. – Brainware Aug 08 '15 at 01:45
  • This returns 1 for two identical dates - and 1 for two days side-by-side with each other. So whether you include the +1 or not, it's still not right sadly. – Ben Clayton Aug 25 '15 at 10:10
25

Swift 3.0 Update

extension Date {

    func differenceInDaysWithDate(date: Date) -> Int {
        let calendar = Calendar.current

        let date1 = calendar.startOfDay(for: self)
        let date2 = calendar.startOfDay(for: date)

        let components = calendar.dateComponents([.day], from: date1, to: date2)
        return components.day ?? 0
    }
}

Swift 2.0 Update

extension NSDate {

    func differenceInDaysWithDate(date: NSDate) -> Int {
        let calendar: NSCalendar = NSCalendar.currentCalendar()

        let date1 = calendar.startOfDayForDate(self)
        let date2 = calendar.startOfDayForDate(date)

        let components = calendar.components(.Day, fromDate: date1, toDate: date2, options: [])
        return components.day
    }

}

Original Solution

Another solution in Swift.

If your purpose is to get the exact day number between two dates, you can work around this issue like this:

// Assuming that firstDate and secondDate are defined
// ...

var calendar: NSCalendar = NSCalendar.currentCalendar()

// Replace the hour (time) of both dates with 00:00
let date1 = calendar.startOfDayForDate(firstDate)
let date2 = calendar.startOfDayForDate(secondDate)

let flags = NSCalendarUnit.DayCalendarUnit
let components = calendar.components(flags, fromDate: date1, toDate: date2, options: nil)

components.day  // This will return the number of day(s) between dates
Daniel Galasko
  • 23,617
  • 8
  • 77
  • 97
Emin Bugra Saral
  • 3,756
  • 1
  • 18
  • 25
  • 1
    A bit shorter and sweeter than using `rangeOfUnit`. But (for now) it should be noted that this requires iOS 8. – nschum Mar 23 '15 at 21:48
  • 1
    Only answer that worked for me! +1 for `calendar.startOfDayForDate`. Thank you. – Thomás Pereira Aug 31 '16 at 17:45
  • I think you need set a TimeZone on the Calendar otherwise the device time zone will be used and this can effect the results. – Leon May 16 '18 at 11:11
  • @Leon I don't see any reason that timezone would effect the result if you are calculating days between dates. Whichever timezone it's, it will give the same result. I haven't thought about it deeply but this is my initial idea. – Emin Bugra Saral May 21 '18 at 14:10
  • @EminBuğraSaral I'm in the UK and in testing I got a different number of days when one of the dates changed from 22:59 and 23:00. The `DateFormatter` was already set to UTC so that wasn't the issue. When I changed the time zone of the `Calendar` I got the correct answer. – Leon May 21 '18 at 16:17
  • @Leon That's the problem. You cannot just change one of the dates. If there is a change in the dates, it should be applied to both of them. Then timezone won't be a problem in this case. – Emin Bugra Saral May 21 '18 at 17:11
  • @EminBuğraSaral No you misunderstand, I was changing one of the dates for testing. In reality I have no control over the times because they come from an API as UTC timestamps. I needed to be able to calculate the number of calendar days between two dates but my test device returned two different results when a time stamp changed from <2300 to 2300 onwards, that's incorrect. – Leon May 22 '18 at 20:00
13

I use this as category method for NSDate class

// returns number of days (absolute value) from another date (as number of midnights beween these dates)
- (int)daysFromDate:(NSDate *)pDate {
    NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSCalendarIdentifierGregorian];
    NSInteger startDay=[calendar ordinalityOfUnit:NSCalendarUnitDay
                                           inUnit:NSCalendarUnitEra
                                          forDate:[NSDate date]];
    NSInteger endDay=[calendar ordinalityOfUnit:NSCalendarUnitDay
                                         inUnit:NSCalendarUnitEra
                                        forDate:pDate];
    return abs(endDay-startDay);
}
SimplyKiwi
  • 12,376
  • 22
  • 105
  • 191
jki
  • 4,617
  • 1
  • 34
  • 29
8

I needed the number of days between two dates including the beginning day. e.g. days between 14-2-2012 and 16-2-2012 would produce a result of 3.

+ (NSInteger)daysBetween:(NSDate *)dt1 and:(NSDate *)dt2 {
        NSUInteger unitFlags = NSDayCalendarUnit;
        NSCalendar* calendar = [NSCalendar currentCalendar];
        NSDateComponents *components = [calendar components:unitFlags fromDate:dt1 toDate:dt2 options:0];
        NSInteger daysBetween = abs([components day]);
    return daysBetween+1;
}

Note that it doesn't matter in which order you provide the dates. It will always return a positive number.

orkoden
  • 18,946
  • 4
  • 59
  • 50
7
NSDate *lastDate = [NSDate date];
NSDate *todaysDate = [NSDate date];
NSTimeInterval lastDiff = [lastDate timeIntervalSinceNow];
NSTimeInterval todaysDiff = [todaysDate timeIntervalSinceNow];
NSTimeInterval dateDiff = lastDiff - todaysDiff;

dateDiff will then be the number of second between the two dates. Just divide by the number of seconds in a day.

Dave DeLong
  • 242,470
  • 58
  • 448
  • 498
chris
  • 443
  • 3
  • 2
  • 2
    Just for clarity you would divide by 60*60*24, and then you need to decide if a partial day counts as a day or not, is 5.9 equal to 6 days? Figure out the appropriate rounding scheme for your application. – Chris Wagner Jan 19 '11 at 19:13
  • 71
    You should never do this sort of math in an application. This will fall down with time zone transitions (which can cause 23- or 25-hour days), leap seconds (which are applied at the end of some years but not others), and any number of other calendar complications. – Becca Royal-Gordon Jan 05 '12 at 06:46
  • @BrentRoyal-Gordon, then how do we go about doing something like this? – Mohamad Aug 02 '12 at 17:04
  • 8
    NSCalendar has methods for performing date math. The WWDC 2011 session videos include a session on performing date math safely. – Becca Royal-Gordon Aug 03 '12 at 19:14
  • Even if there weren't any issues with time zone transitions or other calendar complications, this answer is actually plain wrong. If the dateDiff (seconds) is 1 hour, that could be yesterday if it's 12:30am, or it could still be today if it's 1:30am. – Adam Dec 13 '12 at 04:00
  • 7
    wrong answer. see Biosopher's answer below – nont Dec 20 '12 at 02:49
  • 6
    This is so far beyond wrong its not even funny Biosopher's answer is the correct one. – Tony Million Mar 15 '13 at 07:49
  • This answer is incorrect because of daylight savings. Use the answer from Biosopher – tony.tc.leung Oct 15 '13 at 19:05
  • You should use proper NSDate API to do date calculations instead of dividing by the number of seconds etc... NSCalendar offer what you need already and should be the preferable choice to do date calculations. – zumzum Jan 16 '16 at 09:36
5

@Brian

Brian's answer while good, only calculates difference in days in terms of 24h chunks, but not calendar day differences. For example 23:59 on Dec 24th is only 1 minute away from Christmas Day, for the purpose of many application that is considered one day still. Brian's daysBetween function would return 0.

Borrowing from Brian's original implementation and beginning/end of day, I use the following in my program: (NSDate beginning of day and end of day)

- (NSDate *)beginningOfDay:(NSDate *)date
{
    NSCalendar *cal = [NSCalendar currentCalendar];
    NSDateComponents *components = [cal components:( NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit ) fromDate:date];
    [components setHour:0];
    [components setMinute:0];
    [components setSecond:0];
    return [cal dateFromComponents:components];
}

- (NSDate *)endOfDay:(NSDate *)date
{
    NSCalendar *cal = [NSCalendar currentCalendar];
    NSDateComponents *components = [cal components:( NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit ) fromDate:date];
    [components setHour:23];
    [components setMinute:59];
    [components setSecond:59];
    return [cal dateFromComponents:components];
}

- (int)daysBetween:(NSDate *)date1 and:(NSDate *)date2 {
    NSDate *beginningOfDate1 = [self beginningOfDay:date1];
    NSDate *endOfDate1 = [self endOfDay:date1];
    NSCalendar *calendar = [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];
    NSDateComponents *beginningDayDiff = [calendar components:NSDayCalendarUnit fromDate:beginningOfDate1 toDate:date2 options:0];
    NSDateComponents *endDayDiff = [calendar components:NSDayCalendarUnit fromDate:endOfDate1 toDate:date2 options:0];
    if (beginningDayDiff.day > 0)
        return beginningDayDiff.day;
    else if (endDayDiff.day < 0)
        return endDayDiff.day;
    else {
        return 0;
    }
}
Community
  • 1
  • 1
tooniz
  • 51
  • 1
  • 2
  • 1
    Actually the whole purpose of my solution is to calculate the difference in calendar days - try it :-). The time portion of the timestamp is discarded, so only the difference in days is returned (so Dec 24 at 23:59:59 and Dec 25 are considered 1 day apart). – Brian Mar 28 '14 at 15:19
3

Another approach:

NSDateFormatter* dayFmt = [[NSDateFormatter alloc] init];
[dayFmt setTimeZone:<whatever time zone you want>];
[dayFmt setDateFormat:@"g"];
NSInteger firstDay = [[dayFmt stringFromDate:firstDate] integerValue];    
NSInteger secondDay = [[dayFmt stringFromDate:secondDate] integerValue];
NSInteger difference = secondDay - firstDay;

Has the advantage over the timeIntervalSince... scheme that timezone can be taken into account, and there's no ambiguity with intervals a few seconds short or long of one day.

And a bit more compact and less confusing than the NSDateComponents approaches.

Hot Licks
  • 47,103
  • 17
  • 93
  • 151
3

Just adding an answer for those who visit this page trying to do this in Swift. The approach is pretty much the same.

private class func getDaysBetweenDates(startDate:NSDate, endDate:NSDate) -> NSInteger {

    var gregorian: NSCalendar = NSCalendar.currentCalendar();
    let flags = NSCalendarUnit.DayCalendarUnit
    let components = gregorian.components(flags, fromDate: startDate, toDate: endDate, options: nil)

    return components.day
}

This answer was found here, in the discussion section of the following method:

components(_:fromDate:toDate:options:)
Mihir
  • 982
  • 1
  • 7
  • 13
3

Here is an implementation of Brian's function in Swift:

class func daysBetweenThisDate(fromDateTime:NSDate, andThisDate toDateTime:NSDate)->Int?{

    var fromDate:NSDate? = nil
    var toDate:NSDate? = nil

    let calendar = NSCalendar.currentCalendar()

    calendar.rangeOfUnit(NSCalendarUnit.DayCalendarUnit, startDate: &fromDate, interval: nil, forDate: fromDateTime)

    calendar.rangeOfUnit(NSCalendarUnit.DayCalendarUnit, startDate: &toDate, interval: nil, forDate: toDateTime)

    if let from = fromDate {

        if let to = toDate {

            let difference = calendar.components(NSCalendarUnit.DayCalendarUnit, fromDate: from, toDate: to, options: NSCalendarOptions.allZeros)

            return difference.day
        }
    }

    return nil
}
PJeremyMalouf
  • 613
  • 6
  • 15
1

Got one, not sure it's exactly what you want, but it could help some of you, (helped me!!)

My goal was to know if, between two date (less than 24h difference) i had a "overday" day+1:

i did the following (a bit archaic i admit)

NSDate *startDate = ...
NSDate *endDate = ...

NSDate already formatted by another NSDateFormatter (this one is just for this purpose :)

NSDateFormatter *dayFormater = [[NSDateFormatter alloc]init];
[dayFormater setDateFormat:@"dd"];

int startDateDay = [[dayFormater stringFromDate:startDate]intValue];

int endDateDay = [[dayFormater stringFromDate:dateOn]intValue];

if (endDateDay > startDateDay) {
    NSLog(@"day+1");
} else {
    NSLog(@"same day");
}

maybe something like this already exist, but didn't find it

Tim

Tim
  • 129
  • 1
  • 8
1

Do you mean calendar days or 24-hour periods? i.e. is Tuesday at 9PM a day before Wednesday at 6AM, or less than one day?

If you mean the former, it's a bit complicated and you'll have to resort to manipulations via NSCalendar and NSDateComponent which I don't recall off the top of my head.

If you mean the latter, just get the dates' time intervals since the reference date, subtract one from the other, and divide by 24 hours (24 * 60 * 60) to get the approximate interval, leap seconds not included.

Jonathan Grynspan
  • 43,286
  • 8
  • 74
  • 104
1

Why not just:

int days = [date1 timeIntervalSinceDate:date2]/24/60/60;
Kampai
  • 22,848
  • 21
  • 95
  • 95
MyCSharpCorner
  • 1,313
  • 11
  • 15
  • 6
    Because this does not take into account daylight savings or timezone differences, for instance. You should do this math using a NSCalendar. – leolobato Jun 13 '13 at 13:54
0

I have published an open-source class/library to do just this.

Have a look at RelativeDateDescriptor, which can be used to obtain the time difference as follows...

RelativeDateDescriptor *descriptor = [[RelativeDateDescriptor alloc] initWithPriorDateDescriptionFormat:@"%@ ago" postDateDescriptionFormat:@"in %@"];

// date1: 1st January 2000, 00:00:00
// date2: 6th January 2000, 00:00:00
[descriptor describeDate:date2 relativeTo:date1]; // Returns '5 days ago'
[descriptor describeDate:date1 relativeTo:date2]; // Returns 'in 5 days'
mmccomb
  • 13,516
  • 5
  • 39
  • 46
0

The solution I found was:

+(NSInteger)getDaysDifferenceBetween:(NSDate *)dateA and:(NSDate *)dateB {

  if ([dateA isEqualToDate:dateB]) 
    return 0;

  NSCalendar * gregorian = 
        [[NSCalendar alloc] initWithCalendarIdentifier:NSGregorianCalendar];



  NSDate * dateToRound = [dateA earlierDate:dateB];
  int flags = (NSYearCalendarUnit | NSMonthCalendarUnit |  NSDayCalendarUnit);
  NSDateComponents * dateComponents = 
         [gregorian components:flags fromDate:dateToRound];


  NSDate * roundedDate = [gregorian dateFromComponents:dateComponents];

  NSDate * otherDate = (dateToRound == dateA) ? dateB : dateA ;

  NSInteger diff = abs([roundedDate timeIntervalSinceDate:otherDate]);

  NSInteger daysDifference = floor(diff/(24 * 60 * 60));

  return daysDifference;
}

Here I am effectively rounding the first date to start from the beginning of the day and then calculating the difference as Jonathan is suggesting above...

-1

Why note use the following NSDate method:

- (NSTimeInterval)timeIntervalSinceDate:(NSDate *)anotherDate

This will return the number of seconds between your two dates and you can divide by 86,400 to get the number of days !!

Erwan
  • 3,733
  • 30
  • 25