Swift days between two NSDates

130,215

Solution 1

You have to consider the time difference as well. For example if you compare the dates 2015-01-01 10:00 and 2015-01-02 09:00, days between those dates will return as 0 (zero) since the difference between those dates is less than 24 hours (it's 23 hours).

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
// ...

let calendar = 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.Day
let components = calendar.components(flags, fromDate: date1, toDate: date2, options: [])

components.day  // This will return the number of day(s) between dates

Swift 3 and Swift 4 Version

let calendar = Calendar.current

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

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

Solution 2

Here is my answer for Swift 2:

func daysBetweenDates(startDate: NSDate, endDate: NSDate) -> Int
{
    let calendar = NSCalendar.currentCalendar()

    let components = calendar.components([.Day], fromDate: startDate, toDate: endDate, options: [])

    return components.day
}

Solution 3

I see a couple Swift3 answers so I'll add my own:

public static func daysBetween(start: Date, end: Date) -> Int {
   Calendar.current.dateComponents([.day], from: start, to: end).day!
}

The naming feels more Swifty, it's one line, and using the latest dateComponents() method.

Solution 4

Here is very nice, Date extension to get difference between dates in years, months, days, hours, minutes, seconds

extension Date {

    func years(sinceDate: Date) -> Int? {
        return Calendar.current.dateComponents([.year], from: sinceDate, to: self).year
    }

    func months(sinceDate: Date) -> Int? {
        return Calendar.current.dateComponents([.month], from: sinceDate, to: self).month
    }

    func days(sinceDate: Date) -> Int? {
        return Calendar.current.dateComponents([.day], from: sinceDate, to: self).day
    }

    func hours(sinceDate: Date) -> Int? {
        return Calendar.current.dateComponents([.hour], from: sinceDate, to: self).hour
    }

    func minutes(sinceDate: Date) -> Int? {
        return Calendar.current.dateComponents([.minute], from: sinceDate, to: self).minute
    }

    func seconds(sinceDate: Date) -> Int? {
        return Calendar.current.dateComponents([.second], from: sinceDate, to: self).second
    }

}

Solution 5

I translated my Objective-C answer

let start = "2010-09-01"
let end = "2010-09-05"

let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"

let startDate:NSDate = dateFormatter.dateFromString(start)
let endDate:NSDate = dateFormatter.dateFromString(end)

let cal = NSCalendar.currentCalendar()


let unit:NSCalendarUnit = .Day

let components = cal.components(unit, fromDate: startDate, toDate: endDate, options: nil)


println(components)

result

<NSDateComponents: 0x10280a8a0>
     Day: 4

The hardest part was that the autocompletion insists fromDate and toDate would be NSDate?, but indeed they must be NSDate! as shown in the reference.

I don't see how a good solution with an operator would look like, as you want to specify the unit differently in each case. You could return the time interval, but than won't you gain much.

Share:
130,215
Linus
Author by

Linus

Updated on July 22, 2022

Comments

  • Linus
    Linus almost 2 years

    I'm wondering if there is some new and awesome possibility to get the amount of days between two NSDates in Swift / the "new" Cocoa?

    E.g. like in Ruby I would do:

    (end_date - start_date).to_i
    
  • Martin R
    Martin R almost 10 years
    Don't use (24*60*60) for the length of a day. This does not take daylight saving time transitions into account.
  • Daniel Schlaug
    Daniel Schlaug almost 10 years
    I think NSDate would adjust for that since it always uses GMT and daylight saving is just a formatting or localisation upon that. For sure it gets trickier for months, years or anything of really variable length though.
  • Anton
    Anton almost 10 years
    seconds / (24*60*60) it's wrong for days calculation. you are not counter a leap year with this way
  • Martin R
    Martin R almost 10 years
    @DanielSchlaug: Your first solution (using date components) is fine. But from the time interval (which is just a number of seconds) you cannot compute the days, month, ... correctly anymore.
  • Daniel Schlaug
    Daniel Schlaug almost 10 years
    @MartinR true, I looked into it a bit more and it would seem my solution here is indeed very much inferior to rubys. Indeed a decent solution would require a lot more code. But I do maintain my position that a day is defined well enough at 86400s (±1 leap second happening approximately once per year) that one may calculate a day interval for all practical uses. But I should not have used intuition for time calculation so the criticism is valid.
  • Martin R
    Martin R almost 10 years
    @DanielSchlaug: Leap seconds are not the problem, the Unix time does not count them. But in regions with daylight saving time, the clocks are adjusted one hour forward in the spring, and adjusted backward in the autumn. That means that there are days with 23 hours or 25 hours.
  • Daniel Schlaug
    Daniel Schlaug almost 10 years
    @MartinR I had to try it to believe it but indeed, now that I did I also saw that wikipedia mentions this. You are correct. Thanks for being stubborn with me.
  • Daniel Schlaug
    Daniel Schlaug almost 10 years
    There, edited to be correct. But the flashiness kind of went away.
  • vikingosegundo
    vikingosegundo almost 10 years
    You had to check wikipedia to learn about daylight saving time? But you realized that Sweden changes the time twice a year.
  • Daniel Schlaug
    Daniel Schlaug almost 10 years
    Well, while I'm always very confused every time DST changes I wasn't so ignorant as to not know about it. I had to check wikipedia to confirm that DST actually makes the definition of the length of a day dependant on location and point in time. My intuition would say that even during a DST change if the time between March 30 12:00 and April 1 12:00 is 23 hours it's actually a little less than a full day. I was wrong.
  • vikingosegundo
    vikingosegundo almost 10 years
    it is defined by location, point of time and calendar system. the hebrew calendar has a leap month. there is a great wwdc video: performing calendar calculation — a must-see for every cocoa coder.
  • TaylorAllred
    TaylorAllred about 9 years
    Looks like .DayCalendarUnit is deprecated. I believe now you should use .CalendarUnitDay instead.
  • Lucas van Dongen
    Lucas van Dongen over 8 years
    options is now an expected parameter
  • brandonscript
    brandonscript over 8 years
    You may actually want to check for 12pm (noon) instead of startOfDayForDate -- should be less likely to bork due to adjusting timezones and DST.
  • Andrej
    Andrej over 8 years
    Running Swift 2 this works for me: let components = cal.components(.Day, fromDate: startDate, toDate: endDate, options: [])
  • Delete My Account
    Delete My Account about 8 years
    I successfully used this with components of @vikingosegundo post above. It returns an integer representing the correct number of days between two dates. <thumbs up>
  • William GP
    William GP about 8 years
    @TaylorAllred just .Day now
  • mbonness
    mbonness about 8 years
    I like it but the function name should be "daysBetweenDates"
  • Peter Johnson
    Peter Johnson almost 8 years
    that measures the difference between the number portion of the dates- I.e. 21st January to 22nd of February will give 1 day, not 32 days as it should
  • Fattie
    Fattie over 7 years
    it would definitely be better to move them both to noon, rather than 00:00 to avoid many problems.
  • MonsieurDart
    MonsieurDart about 7 years
    Setting the dates to noon can be done like this: calendar.date(bySettingHour: 12, minute: 00, second: 00, of: calendar.startOfDay(for: firstDate))
  • tawheed
    tawheed over 6 years
    This returns 0 if we are comparing today and tomorrow
  • TheTiger
    TheTiger about 6 years
    date should be sinceDate in function parameters.
  • Krunal
    Krunal about 6 years
    @TheTiger - Thank you very much for highlighting the biggest mistake of this answer.. I'll practically test and update answer soon.
  • TheTiger
    TheTiger about 6 years
    My pleasure! I have tested it for days and it works fine.
  • Rob
    Rob over 5 years
    Good answer. I’d only suggest func years(since date: Date) -> Int? { return Calendar.current.dateComponents[.year], from: date, to: self).years }, and you could the call it as let y = date1.years(since: date2). That might be more consistent with modern naming conventions.
  • jamix
    jamix about 4 years
    Shorter version for setting noon (startOfDay() seems to be unnecessary): calendar.date(bySettingHour: 12, minute: 0, second: 0, of: firstDate).
  • Leo Dabus
    Leo Dabus almost 4 years
    You should use Swift native Calendar (drop the NS). The use of guard when setting the hour to 12pm is pointless. It will never fail.
  • Leo Dabus
    Leo Dabus almost 4 years
    calling startOfDay before setting the hour to noon it is pointless as well.
  • ramzesenok
    ramzesenok over 3 years
    this is the copy of another answer with just added date parsing, which was never asked for
  • Petar
    Petar over 3 years
    Can someone please explain me what happens when Calendar.current's timeZone doesn't match the time zone that firstDate/secondDate were initialized with (e.g. from a time formatter) ? What should we do if we want to find the distance in days between a date that we know is in a given time zone (different from current) and the current date in the current time zone - how exactly should we convert the Calendar ?
  • Petar
    Petar over 3 years
    This can be a computed variable instead of a function.
  • Petar
    Petar over 3 years
    Also note that this won't work correctly - e.g. firstDate = 2021-01-13 22:00:00 +0000, startOfDay = 2021-01-13 22:00:00 +0000, secondDate = 2021-01-14 12:54:46 +0000, startOfDay = 2021-01-13 22:00:00 +0000 . Even though the secondDate is considered in the next day, using startOfDay will make both dates equal. For this reason I think it is better to use add the hour value / 24 to the day value and round up.
  • Lumii
    Lumii over 2 years
    The accepted answer is incorrect. To know why, please look at the posted question - stackoverflow.com/questions/70014759/… However, I do not have a good answer on this.
  • Emin Bugra Saral
    Emin Bugra Saral over 2 years
    Timezone difference would have no effect since the absolute distance is the same. The shift will be on both sides. a - b = c == (a + 2) - (b + 2)
  • Admin
    Admin over 2 years
    Your answer could be improved with additional supporting information. Please edit to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers in the help center.