Swift 3 - find number of calendar days between two dates
The way I did this in Swift 2.3 was:
let currentDate = NSDate()
let currentCalendar = NSCalendar.currentCalendar()
var startDate : NSDate?
var endDate : NSDate?
// The following two lines set the `startDate` and `endDate` to the start of the day
currentCalendar.rangeOfUnit(.Day, startDate: &startDate, interval: nil, forDate: currentDate)
currentCalendar.rangeOfUnit(.Day, startDate: &endDate, interval: nil, forDate: self)
let intervalComps = currentCalendar.components([.Day], fromDate: startDate!, toDate: endDate!, options: [])
print(intervalComps.day)
Now this has all changed with Swift 3. I have to either use NSCalendar
and NSDate
by constantly type casting with as
, or find the Swift 3 way of doing it.
What's the right way to do it in Swift 3?
Solution 1:
In Swift 5 there is a simple one-liner to get the number of days (or any other DateComponent) between two dates:
let diffInDays = Calendar.current.dateComponents([.day], from: dateA, to: dateB).day
Note: As pointed out in the comments, this solution measures the 24h periods and therefore requires at least 24h between dateA
and dateB
.
Solution 2:
Turns out this is much simpler to do in Swift 3:
extension Date {
func interval(ofComponent comp: Calendar.Component, fromDate date: Date) -> Int {
let currentCalendar = Calendar.current
guard let start = currentCalendar.ordinality(of: comp, in: .era, for: date) else { return 0 }
guard let end = currentCalendar.ordinality(of: comp, in: .era, for: self) else { return 0 }
return end - start
}
}
Edit
Comparing the ordinality of the two dates should be within the same era
instead of the same year
, since naturally the two dates may fall in different years.
Usage
let yesterday = Date(timeInterval: -86400, since: Date())
let tomorrow = Date(timeInterval: 86400, since: Date())
let diff = tomorrow.interval(ofComponent: .day, fromDate: yesterday)
// return 2
Solution 3:
Swift 4 Version
let startDate = "2000-11-22"
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
let formatedStartDate = dateFormatter.date(from: startDate)
let currentDate = Date()
let components = Set<Calendar.Component>([.second, .minute, .hour, .day, .month, .year])
let differenceOfDate = Calendar.current.dateComponents(components, from: formatedStartDate!, to: currentDate)
print (differenceOfDate)
Printed - year: 16 month: 10 day: 19 hour: 12 minute: 16 second: 42 isLeapMonth: false
swift4 calendar date
Solution 4:
If any one want to do it more specifically follow Below Steps
1.Add this Date Extension
extension Date {
/// Returns the amount of years from another date
func years(from date: Date) -> Int {
return Calendar.current.dateComponents([.year], from: date, to: self).year ?? 0
}
/// Returns the amount of months from another date
func months(from date: Date) -> Int {
return Calendar.current.dateComponents([.month], from: date, to: self).month ?? 0
}
/// Returns the amount of weeks from another date
func weeks(from date: Date) -> Int {
return Calendar.current.dateComponents([.weekOfMonth], from: date, to: self).weekOfMonth ?? 0
}
/// Returns the amount of days from another date
func days(from date: Date) -> Int {
return Calendar.current.dateComponents([.day], from: date, to: self).day ?? 0
}
/// Returns the amount of hours from another date
func hours(from date: Date) -> Int {
return Calendar.current.dateComponents([.hour], from: date, to: self).hour ?? 0
}
/// Returns the amount of minutes from another date
func minutes(from date: Date) -> Int {
return Calendar.current.dateComponents([.minute], from: date, to: self).minute ?? 0
}
/// Returns the amount of seconds from another date
func seconds(from date: Date) -> Int {
return Calendar.current.dateComponents([.second], from: date, to: self).second ?? 0
}
/// Returns the amount of nanoseconds from another date
func nanoseconds(from date: Date) -> Int {
return Calendar.current.dateComponents([.nanosecond], from: date, to: self).nanosecond ?? 0
}
/// Returns the a custom time interval description from another date
func offset(from date: Date) -> String {
var result: String = ""
if years(from: date) > 0 { return "\(years(from: date))y" }
if months(from: date) > 0 { return "\(months(from: date))M" }
if weeks(from: date) > 0 { return "\(weeks(from: date))w" }
if seconds(from: date) > 0 { return "\(seconds(from: date))" }
if days(from: date) > 0 { result = result + " " + "\(days(from: date)) D" }
if hours(from: date) > 0 { result = result + " " + "\(hours(from: date)) H" }
if minutes(from: date) > 0 { result = result + " " + "\(minutes(from: date)) M" }
if seconds(from: date) > 0 { return "\(seconds(from: date))" }
return ""
}
}
2.Define it in globally
fileprivate var timer: Timer?
3.Call this Method in viewDidLoad or where ever you want
override func viewDidLoad() {
super.viewDidLoad()
self.getRemainingTime()
}
4.Usage
fileprivate func getRemainingTime() {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let startDate = "2018-06-02 10:11:12"
let currentDate = dateFormatter.string(from: Date())
if currentDate != startDate {
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: (#selector(calculateTime)), userInfo: nil, repeats: true)
RunLoop.current.add(timer!, forMode: RunLoopMode.commonModes)
timer?.fire()
}
else {
self.timer?.invalidate()
self.timer = nil
}
}
func calculateTime() {
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
let stdate : String = "2018-06-02 10:11:12"
let startDate = dateFormatter.date(from: stdate)!
let currentDate = Date()
let strTimer : String = startDate.offset(from: currentDate)
if !strTimer.isEmpty {
let stDay: String = "\((Int(strTimer)! % 31536000) / 86400)"
let stHour: String = "\((Int(strTimer)! % 86400) / 3600)"
let stMin: String = "\((Int(strTimer)! % 3600) / 60)"
let stSec: String = "\(Int(strTimer)! % 60)"
yourLabelOutlet.text = "Start In :\(stDay) Days \(stHour) Hours \(stMin) Minutes \(stSec) Seconds"
}
}
Works like Charm You can Use every separate string to your UI Side, Enjoy
Solution 5:
private func calculateDaysBetweenTwoDates(start: Date, end: Date) -> Int {
let currentCalendar = Calendar.current
guard let start = currentCalendar.ordinality(of: .day, in: .era, for: start) else {
return 0
}
guard let end = currentCalendar.ordinality(of: .day, in: .era, for: end) else {
return 0
}
return end - start
}