147

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
Krunal
  • 77,632
  • 48
  • 245
  • 261
Linus
  • 4,643
  • 8
  • 49
  • 74
  • 5
    I think you still have to use NSCalendar and NSDateComponents (for which there must be hundreds of answers on SO). - If you are looking for something *"new and awesome possibility"* then it would be helpful to show your present solution for comparison. – Martin R Jul 13 '14 at 14:15
  • 1
    This is now very easy, and you don't have to use "NS" anything. I typed in an answer for 2017, to copy and paste. – Fattie Jul 20 '17 at 23:00

28 Answers28

297

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)
Ronan Boiteau
  • 9,608
  • 6
  • 34
  • 56
Emin Bugra Saral
  • 3,756
  • 1
  • 18
  • 25
  • 22
    You may actually want to check for 12pm (noon) instead of startOfDayForDate -- should be less likely to bork due to adjusting timezones and DST. – brandonscript Feb 15 '16 at 20:50
  • 18
    Setting the dates to noon can be done like this: `calendar.date(bySettingHour: 12, minute: 00, second: 00, of: calendar.startOfDay(for: firstDate))` – MonsieurDart May 15 '17 at 22:08
  • 6
    Shorter version for setting noon (`startOfDay()` seems to be unnecessary): `calendar.date(bySettingHour: 12, minute: 0, second: 0, of: firstDate)`. – jamix Jun 16 '20 at 21:42
  • 2
    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 Jan 13 '21 at 23:40
  • 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. – Petar Jan 14 '21 at 13:50
  • The accepted answer is incorrect. To know why, please look at the posted question - https://stackoverflow.com/questions/70014759/what-is-reliable-way-to-calculate-day-differences-without-taking-time-into-cons However, I do not have a good answer on this. – Cheok Yan Cheng Nov 18 '21 at 04:57
  • 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) – Emin Bugra Saral Nov 25 '21 at 11:31
55

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
}
iphaaw
  • 6,764
  • 11
  • 58
  • 83
54

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.

Kaunteya
  • 3,107
  • 1
  • 35
  • 66
telkins
  • 10,440
  • 8
  • 52
  • 79
40

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
    }

}
Krunal
  • 77,632
  • 48
  • 245
  • 261
  • 1
    `date` should be `sinceDate` in function parameters. – TheTiger Mar 28 '18 at 06:14
  • @TheTiger - Thank you very much for highlighting the biggest mistake of this answer.. I'll practically test and update answer soon. – Krunal Mar 28 '18 at 07:14
  • 1
    My pleasure! I have tested it for `days` and it works fine. – TheTiger Mar 28 '18 at 07:15
  • 2
    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. – Rob Feb 24 '19 at 07:34
29

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.

Community
  • 1
  • 1
vikingosegundo
  • 52,040
  • 14
  • 137
  • 178
18

Update for Swift 3 iOS 10 Beta 4

func daysBetweenDates(startDate: Date, endDate: Date) -> Int {
    let calendar = Calendar.current
    let components = calendar.dateComponents([Calendar.Component.day], from: startDate, to: endDate)
    return components.day!
}
ChaosSpeeder
  • 3,468
  • 5
  • 30
  • 38
14

Swift 5. Thanks to Emin Buğra Saral above for the startOfDay suggestion.

extension Date {
    
    func daysBetween(date: Date) -> Int {
        return Date.daysBetween(start: self, end: date)
    }
    
    static func daysBetween(start: Date, end: Date) -> Int {
        let calendar = Calendar.current
        
        // Replace the hour (time) of both dates with 00:00
        let date1 = calendar.startOfDay(for: start)
        let date2 = calendar.startOfDay(for: end)
        
        let a = calendar.dateComponents([.day], from: date1, to: date2)
        return a.value(for: .day)!
    }
}

Usage:

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let start = dateFormatter.date(from: "2017-01-01")!
let end = dateFormatter.date(from: "2018-01-01")!

let diff = Date.daysBetween(start: start, end: end) // 365
// or
let diff = start.daysBetween(date: end) // 365
Norman
  • 3,020
  • 22
  • 21
  • 2
    it would definitely be better to ***move them both to noon, rather than 00:00*** to avoid many problems. – Fattie Feb 21 '17 at 15:23
10

Here is the answer for Swift 3 (tested for IOS 10 Beta)

func daysBetweenDates(startDate: Date, endDate: Date) -> Int
{
    let calendar = Calendar.current
    let components = calendar.components([.day], from: startDate, to: endDate, options: [])
    return components.day!
}

Then you can call it like this

let pickedDate: Date = sender.date
let NumOfDays: Int = daysBetweenDates(startDate: pickedDate, endDate: Date())
    print("Num of Days: \(NumOfDays)")
kazantatar
  • 101
  • 1
  • 5
8

Swift 5

Working, you need to set the time to be the same for both days, if you are off by seconds it will be wrong

func daysBetween(start: Date, end: Date) -> Int {
    let start = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: start)!
    let end = Calendar.current.date(bySettingHour: 0, minute: 0, second: 0, of: end)!
    return Calendar.current.dateComponents([.day], from: start, to: end).day ?? 0
}
Mike Zriel
  • 1,575
  • 1
  • 17
  • 28
4

The things built into swift are still very basic. As they should be at this early stage. But you can add your own stuff with the risk that comes with overloading operators and global domain functions. They will be local to your module though.

let now = NSDate()
let seventies = NSDate(timeIntervalSince1970: 0)

// Standard solution still works
let days = NSCalendar.currentCalendar().components(.CalendarUnitDay, 
           fromDate: seventies, toDate: now, options: nil).day

// Flashy swift... maybe...
func -(lhs:NSDate, rhs:NSDate) -> DateRange {
    return DateRange(startDate: rhs, endDate: lhs)
}

class DateRange {
    let startDate:NSDate
    let endDate:NSDate
    var calendar = NSCalendar.currentCalendar()
    var days: Int {
        return calendar.components(.CalendarUnitDay, 
               fromDate: startDate, toDate: endDate, options: nil).day
    }
    var months: Int {
        return calendar.components(.CalendarUnitMonth, 
               fromDate: startDate, toDate: endDate, options: nil).month
    }
    init(startDate:NSDate, endDate:NSDate) {
        self.startDate = startDate
        self.endDate = endDate
    }
}

// Now you can do this...
(now - seventies).months
(now - seventies).days
Daniel Schlaug
  • 1,504
  • 1
  • 13
  • 17
  • 20
    **Don't** use (24*60*60) for the length of a day. This does not take daylight saving time transitions into account. – Martin R Jul 13 '14 at 16:35
  • 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. – Daniel Schlaug Jul 13 '14 at 16:42
  • seconds / (24*60*60) it's wrong for days calculation. you are not counter a leap year with this way – Anton Jul 13 '14 at 17:58
  • @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. – Martin R Jul 13 '14 at 18:28
  • @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. – Daniel Schlaug Jul 13 '14 at 21:06
  • @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. – Martin R Jul 13 '14 at 21:14
  • 1
    @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 Jul 13 '14 at 22:01
  • 1
    There, edited to be correct. But the flashiness kind of went away. – Daniel Schlaug Jul 13 '14 at 22:17
  • You had to check wikipedia to learn about daylight saving time? But you realized that Sweden changes the time twice a year. – vikingosegundo Jul 14 '14 at 03:25
  • 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. – Daniel Schlaug Jul 14 '14 at 08:17
  • 1
    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. – vikingosegundo Jul 14 '14 at 16:17
4

This is an updated version of Emin's answer for Swift 5 that incorporates the suggestion to use noon instead of midnight as the definitive time for comparing days. It also handles the potential failure of various date functions by returning an optional.

///
/// This is an approximation; it does not account for time differences. It will set the time to 1200 (noon) and provide the absolute number
/// of days between now and the given date. If the result is negative, it should be read as "days ago" instead of "days from today."
/// Returns nil if something goes wrong initializing or adjusting dates.
///

func daysFromToday() -> Int?
{
    let calendar = NSCalendar.current

    // Replace the hour (time) of both dates with noon. (Noon is less likely to be affected by DST changes, timezones, etc. than midnight.)
    guard let date1 = calendar.date(bySettingHour: 12, minute: 00, second: 00, of: calendar.startOfDay(for: Date())),
          let date2 = calendar.date(bySettingHour: 12, minute: 00, second: 00, of: calendar.startOfDay(for: self)) else
    {
        return nil
    }
    
    return calendar.dateComponents([.day], from: date1, to: date2).day
}
pkamb
  • 33,281
  • 23
  • 160
  • 191
Bryan
  • 4,628
  • 3
  • 36
  • 62
3

Here is my answer for Swift 3:

func daysBetweenDates(startDate: NSDate, endDate: NSDate, inTimeZone timeZone: TimeZone? = nil) -> Int {
    var calendar = Calendar.current
    if let timeZone = timeZone {
        calendar.timeZone = timeZone
    }
    let dateComponents = calendar.dateComponents([.day], from: startDate.startOfDay, to: endDate.startOfDay)
    return dateComponents.day!
}
Alen Liang
  • 529
  • 5
  • 13
3

You could use the following extension:

public extension Date {
    func daysTo(_ date: Date) -> Int? {
        let calendar = Calendar.current

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

        let components = calendar.dateComponents([.day], from: date1, to: date2)
        return components.day  // This will return the number of day(s) between dates
    }
}

Then, you can call it like this:

startDate.daysTo(endDate)
Mauro García
  • 107
  • 1
  • 2
2

There's hardly any Swift-specific standard library yet; just the lean basic numeric, string, and collection types.

It's perfectly possible to define such shorthands using extensions, but as far as the actual out-of-the-box APIs goes, there is no "new" Cocoa; Swift just maps directly to the same old verbose Cocoa APIs as they already exist.

Wes Campaigne
  • 4,060
  • 3
  • 22
  • 17
2

I'm going to add my version even though this thread is a year old. My code looks like this:

    var name = txtName.stringValue // Get the users name

    // Get the date components from the window controls
    var dateComponents = NSDateComponents()
    dateComponents.day = txtDOBDay.integerValue
    dateComponents.month = txtDOBMonth.integerValue
    dateComponents.year = txtDOBYear.integerValue

    // Make a Gregorian calendar
    let calendar = NSCalendar(identifier: NSCalendarIdentifierGregorian)

    // Get the two dates we need
    var birthdate = calendar?.dateFromComponents(dateComponents)
    let currentDate = NSDate()

    var durationDateComponents = calendar?.components(NSCalendarUnit.CalendarUnitDay, fromDate: birthdate!, toDate: currentDate, options: nil)

    let numberOfDaysAlive = durationDateComponents?.day

    println("\(numberOfDaysAlive!)")

    txtGreeting.stringValue = "Hello \(name), You have been alive for \(numberOfDaysAlive!) days."

I hope it helps someone.

Cheers,

Andrew H
  • 466
  • 10
  • 22
2

Erin's method updated to Swift 3, This shows days from today (disregarding time of day)

func daysBetweenDates( endDate: Date) -> Int 
    let calendar: Calendar = Calendar.current 
    let date1 = calendar.startOfDay(for: Date()) 
    let date2 = calendar.startOfDay(for: secondDate) 
    return calendar.dateComponents([.day], from: date1, to: date2).day! 
}
Peter Johnson
  • 3,764
  • 1
  • 23
  • 27
2

Nice handy one liner :

extension Date {
  var daysFromNow: Int {
    return Calendar.current.dateComponents([.day], from: Date(), to: self).day!
  }
}
Rom.
  • 2,800
  • 2
  • 17
  • 19
2

This returns an absolute difference in days between some Date and today:

extension Date {
  func daysFromToday() -> Int {
    return abs(Calendar.current.dateComponents([.day], from: self, to: Date()).day!)
  }
}

and then use it:

if someDate.daysFromToday() >= 7 {
  // at least a week from today
}
budiDino
  • 13,044
  • 8
  • 95
  • 91
1

easier option would be to create a extension on Date

public extension Date {

        public var currentCalendar: Calendar {
            return Calendar.autoupdatingCurrent
        }

        public func daysBetween(_ date: Date) -> Int {
            let components = currentCalendar.dateComponents([.day], from: self, to: date)
            return components.day!
        }
    }
Suhit Patil
  • 11,748
  • 3
  • 50
  • 60
1

Swift 3.2

extension DateComponentsFormatter {
    func difference(from fromDate: Date, to toDate: Date) -> String? {
        self.allowedUnits = [.year,.month,.weekOfMonth,.day]
        self.maximumUnitCount = 1
        self.unitsStyle = .full
        return self.string(from: fromDate, to: toDate)
    }
}
Adam Smaka
  • 5,977
  • 3
  • 50
  • 55
1

All answer is good. But for Localizations we need calculates a number of decimal days in between two dates. so we can provide the sustainable decimal format.

// This method returns the fractional number of days between to dates
func getFractionalDaysBetweenDates(date1: Date, date2: Date) -> Double {

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

    var decimalDays = Double(components.day!)
    decimalDays += Double(components.hour!) / 24.0

    return decimalDays
}
Durul Dalkanat
  • 7,266
  • 4
  • 35
  • 36
0

Swift 3 - Days from today until date

func daysUntilDate(endDateComponents: DateComponents) -> Int
    {
        let cal = Calendar.current
        var components = cal.dateComponents([.era, .year, .month, .day], from: NSDate() as Date)
        let today = cal.date(from: components)
        let otherDate = cal.date(from: endDateComponents)

        components = cal.dateComponents([Calendar.Component.day], from: (today! as Date), to: otherDate!)
        return components.day!
    }

Call function like this

// Days from today until date
   var examnDate = DateComponents()
   examnDate.year = 2016
   examnDate.month = 12
   examnDate.day = 15
   let daysCount = daysUntilDate(endDateComponents: examnDate)
dianakarenms
  • 2,609
  • 1
  • 22
  • 22
0
func completeOffset(from date:Date) -> String? {
        
   let formatter = DateComponentsFormatter()
   formatter.unitsStyle = .brief
        
   return  formatter.string(from: Calendar.current.dateComponents([.year, .month, .day, .hour, .minute, .second], from: date, to: self))
}

if you need year month days and hours as string use this

var tomorrow = Calendar.current.date(byAdding: .day, value: 1, to: Date())!

let dc = tomorrow.completeOffset(from: Date())
koen
  • 5,383
  • 7
  • 50
  • 89
Akash Shindhe
  • 558
  • 4
  • 16
0
extension Date {
    func daysFromToday() -> Int {
        return Calendar.current.dateComponents([.day], from: self, to: Date()).day!
    }
}

Then use it like

    func dayCount(dateString: String) -> String{
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "MMM dd,yyyy hh:mm a"
        let fetchedDate = dateFormatter.date(from: dateString)


        let day = fetchedDate?.daysFromToday()
        if day! > -1{
            return "\(day!) days passed."
        }else{
        return "\(day! * -1) days left."
        }
    }
Murad Al Wajed
  • 4,180
  • 1
  • 10
  • 11
0
extension Date {
static func - (recent: Date, previous: Date) -> DateComponents {
    var dateComponents = DateComponents()
    dateComponents.year = Calendar.current.dateComponents([.day], from: previous, to: recent).year
    dateComponents.month = Calendar.current.dateComponents([.month], from: previous, to: recent).month
    dateComponents.day = Calendar.current.dateComponents([.day], from: previous, to: recent).day
    dateComponents.hour = Calendar.current.dateComponents([.hour], from: previous, to: recent).hour
    dateComponents.minute = Calendar.current.dateComponents([.minute], from: previous, to: recent).minute
    dateComponents.second = Calendar.current.dateComponents([.second], from: previous, to: recent).second
    return dateComponents
   }
}
raees
  • 1
  • 1
  • 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](/help/how-to-answer). – Community Dec 03 '21 at 17:34
-1

2017 version, copy and paste

func simpleIndex(ofDate: Date) -> Int {
    
    // index here just means today 0, yesterday -1, tomorrow 1 etc.
    
    let c = Calendar.current
    let todayRightNow = Date()
    
    let d = c.date(bySetting: .hour, value: 13, of: ofDate)
    let t = c.date(bySetting: .hour, value: 13, of: todayRightNow)
    
    if d == nil || today == nil {
    
        print("weird problem simpleIndex#ofDate")
        return 0
    }
    
    let r = c.dateComponents([.day], from: today!, to: d!)
    // yesterday is negative one, tomorrow is one
    
    if let o = r.value(for: .day) {
        
        return o
    }
    else {
    
        print("another weird problem simpleIndex#ofDate")
        return 0
    }
}
Community
  • 1
  • 1
Fattie
  • 27,874
  • 70
  • 431
  • 719
-2
let calendar = NSCalendar.currentCalendar();
let component1 = calendar.component(.Day, fromDate: fromDate)
let component2 = calendar.component(.Day, fromDate: toDate)
let difference  = component1 - component2
Raj Aggrawal
  • 761
  • 1
  • 6
  • 21
  • 1
    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 – Peter Johnson Aug 28 '16 at 14:16
-2

Swift 5.2.4 solution:

import UIKit

let calendar = Calendar.current

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

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

let firstDate = dateFormatter.date(from: start)!
let secondDate = dateFormatter.date(from: end)!

// 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([Calendar.Component.day], from: date1, to: date2)

components.day  // This will return the number of day(s) between dates
mazy
  • 652
  • 1
  • 10
  • 18