两个NSDate之间的Swift天数

147

我想知道在Swift / “新”Cocoa中获取两个NSDate之间天数的新的、棒的方法是否存在?

例如,就像我在Ruby中会这样做:

(end_date - start_date).to_i

5
我认为你仍然需要使用NSCalendar和NSDateComponents(在SO上应该有数百个答案)。- 如果你正在寻找一些“新的、令人惊叹的可能性”,那么展示你现有的解决方案以供比较会很有帮助。 - Martin R
1
现在这很容易,你不必使用任何“NS”东西。我为2017年输入了一个答案,可以复制和粘贴。 - Fattie
28个回答

298

你还必须考虑时差。例如,如果你要比较日期2015-01-01 10:002015-01-02 09:00,那么这两个日期之间的时间跨度不到24小时(只有23小时),所以它们之间的天数差将被计算为0。

如果你的目的是获得两个日期之间的确切天数,你可以像这样解决这个问题:

// 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和Swift 4版本

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)

22
你可能会希望检查中午12点,而不是使用startOfDayForDate -- 这样做更不容易出现由于时区和夏令时调整造成的故障。 - brandonscript
18
将日期设为中午可以像这样实现:calendar.date(bySettingHour: 12, minute: 00, second: 00, of: calendar.startOfDay(for: firstDate)) - MonsieurDart
6
更简短的设置中午时间的方式(startOfDay() 似乎是不必要的):calendar.date(bySettingHour: 12, minute: 0, second: 0, of: firstDate) - jamix
2
有人能解释一下当Calendar.current的时区与firstDate/secondDate初始化时使用的时区(例如来自时间格式化程序)不匹配时会发生什么吗?如果我们想要找到在给定时区(不同于当前时区)中已知的日期与当前时区中的当前日期之间的天数差,我们应该怎么做 - 我们应该如何转换日历? - Petar
时区差异不会产生影响,因为绝对距离相同。这种偏移将同时出现在两侧。a - b = c == (a + 2) - (b + 2) - Emin Bugra Saral
显示剩余2条评论

55

这里是关于 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
}

我成功地使用了@vikingosegundo帖子中的组件。它返回一个整数,表示两个日期之间的正确天数。<点赞> - Delete My Account
我喜欢它,但函数名称应该是“daysBetweenDates”。 - mbonness
7
如果我们比较“今天”和“明天”,则返回0。 - tawheed

54

我看到有几个关于Swift3的答案,所以我会补充我的:

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

命名感觉更Swifty,只需一行代码,使用最新的dateComponents()方法。


40

这里有一个非常好用的Date扩展,可以计算两个日期之间的年、月、日、小时、分钟和秒钟差。

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
    }

}

1
函数参数中的 date 应该改为 sinceDate - TheTiger
@TheTiger - 非常感谢您指出这个答案中最大的错误。我会进行实际测试并尽快更新答案。 - Krunal
1
非常高兴!我已经测试了几天,它运行良好。 - TheTiger
2
不错的回答。我只想建议使用 func years(since date: Date) -> Int? { return Calendar.current.dateComponents([.year], from: date, to: self).years },你可以像这样调用: let y = date1.years(since: date2) 。这样可能更符合现代命名规范。 - Rob

29

我翻译了我的Objective-C答案

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)

结果

<NSDateComponents: 0x10280a8a0>
     Day: 4

最困难的部分在于自动完成坚持认为fromDate和toDate应该是NSDate?,但实际上它们必须像参考文献中所示的那样是NSDate!
我不知道一个带有操作符的好解决方案会是什么样子,因为你想在每种情况下以不同的单位指定时间。你可以返回时间间隔,但这样做并没有太多好处。

看起来.DayCalendarUnit已被弃用。我认为现在应该使用.CalendarUnitDay - TaylorAllred
2
选项现在是一个预期的参数。 - Lucas van Dongen
2
在运行 Swift 2 中,这段代码可以正常工作:let components = cal.components(.Day, fromDate: startDate, toDate: endDate, options: []) - Andrej
@TaylorAllred 现在只有 .Day - William GP

18

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!
}

14

感谢Emin Buğra Saral的建议,Swift 5现在包含了startOfDay功能。

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)!
    }
}

使用方法:

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

2
最好将它们都移动到中午,而不是 00:00,以避免许多问题。 - Fattie

10

以下是适用于 Swift 3(已在 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!
}

然后你可以这样调用它

let pickedDate: Date = sender.date
let NumOfDays: Int = daysBetweenDates(startDate: pickedDate, endDate: Date())
    print("Num of Days: \(NumOfDays)")

8

Swift 5

在工作中,你需要将时间设置为两天相同,如果误差超过几秒钟就会出现错误。

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
}

4

Swift内置的东西仍然非常基础,这在早期阶段是应该的。但是,您可以添加自己的内容,风险就在于过载运算符和全局域函数。不过,它们将仅局限于您的模块。

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

20
不要使用(246060)来表示一天的长度。这种计算方式没有考虑到夏令时转换的影响。 - Martin R
秒数 / (246060) 这种方式计算天数是错误的。这种方法无法考虑闰年。 - Anton
1
@MartinR 我不得不尝试才能相信,但确实如此,现在我也看到维基百科提到了这一点。你是正确的。感谢你对我坚持不懈。 - Daniel Schlaug
1
那里,已经编辑正确了。但华丽的效果有所减弱。 - Daniel Schlaug
1
它由位置、时间点和日历系统定义。希伯来日历有闰月。有一部非常棒的WWDC视频:执行日历计算-每个Cocoa编码者必看。 - vikingosegundo
显示剩余6条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接