Swift中的DateComponents返回奇怪的值。为什么?

时间:2016-10-29 16:18:57

标签: ios swift nsdate nsdatecomponents

我正在计算两个日期之间的时差。我有一段代码:

func timeAgoSinceDate(_ date:Date, numericDates:Bool) -> String {
    let calendar = Calendar.current
    let now = Date()
    let components:DateComponents = (calendar as NSCalendar).components([NSCalendar.Unit.minute , NSCalendar.Unit.hour , NSCalendar.Unit.day , NSCalendar.Unit.weekOfYear , NSCalendar.Unit.month , NSCalendar.Unit.year , NSCalendar.Unit.second], from: now, to: date, options: NSCalendar.Options())
    print(now)
    print(date)
    print(components.month)

这些是我在控制台中看到的一些例子:

2016-10-29 16:12:34 +0000
2017-03-29 16:03:13 +0000
Optional(4) //I think it should return 5?


2016-10-29 16:12:34 +0000
2017-01-29 17:03:13 +0000
Optional(2) //again, why 2 instead of 3?

2016-10-29 16:12:34 +0000
2015-09-30 15:54:20 +0000
Optional(0) //why 0 since it's almost whole year?


2016-10-29 16:12:34 +0000
2016-05-29 14:09:31 +0000
Optional(-5) //this seems to be fine

依此类推......这里有什么问题?

我遇到的问题是日期计算。在我的代码中我正在做:

if (components.month! >= 2) {
        return "in \(components.month!) months"
    } else if (components.month! >= 1){
        if (numericDates){
            return "in 1 month"
        } else {
            return "in one month"
        }
    }

    else if (components.month! <= -2) {
        return "\(components.month!) months ago"
    } else if (components.month! <= -1){
        if (numericDates){
            return "1 month ago"
        } else {
            return "one month ago"
        }
    }

然后,由于进行了我的计算,目前当一个日期为2016-10-29 16:32:54 +0000而另一个日期为2017-01-29 17:03:13 +0000时,用户会看到in 2 months。这是因为整个print(components)返回:

year: 0 month: 2 day: 2 hour: 23 minute: 30 second: 18 weekOfYear: 4 isLeapMonth: false 

但在现实生活中它应该返回3,因为这是10月和1月之间的差异。有没有比一堆if语句更好地计算这种差异的方法?

3 个答案:

答案 0 :(得分:3)

2016-10-29 16:12:34 +0000
2017-03-29 16:03:13 +0000
Optional(4) //I think it should return 5?

自16:03起&lt; 16:12,你需要再增加9分21秒才能返回5

2016-10-29 16:12:34 +0000
2017-01-29 17:03:13 +0000
Optional(2) //again, why 2 instead of 3?

夏令时是2016年11月6日。您将失去一小时。我很确定这就是为什么它认为只有2个月的时间。

添加一小时:

2016-10-29 16:12:34 +0000
2017-01-29 18:03:13 +0000
Optional(3)

你得到&#39; 3&#39;

更改月份但保留原始小时数:

2016-7-29 16:12:34 +0000
2016-10-29 17:03:13 +0000
Optional(3)

你也得到了&#39; 3&#39;

2016-10-29 16:12:34 +0000
2015-09-30 15:54:20 +0000
Optional(0) //why 0 since it's almost whole year?

日期组件返回组件定义中包含的最高单位的差异。在您的情况下,您已包含NSCalendar.Unit.year。因此components.month将返回月份单位,而不是月份的总差异。所以这个计算中的月份单位是0个月。如果你要将25年零5个月添加到某个日期,它将返回&#39; 5&#39;。但是,如果您要删除NSCalendar.Unit.year,那么它将使用月份作为最高单位并返回&#39; 305&#39;

你可以像这样测试一些日期:

let startString = "2016-7-30 16:12:34 +0000"
let endString = "2016-10-30 17:03:13 +0000"

let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss Z"
let now = formatter.date(from: startString)!
let date = formatter.date(from: endString)!

答案 1 :(得分:0)

从2016-10-29 16:12:34到2017-03-29 16:03:13还有4个月,28天,23个小时,50分钟和39秒 这就是为什么components.month在第一个示例中评估为4的原因。

如果您打算计算2016年10月之后的差异(月份) 到2017年3月,你必须计算之间的差异 两个日期的月份开始

let startOfFirstMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: firstDate))!
let startOfSecondMonth = calendar.date(from: calendar.dateComponents([.year, .month], from: secondDate))!

let comps = calendar.dateComponents([.month], from: startOfFirstMonth, to: startOfSecondMonth)
print(comps.month)

答案 2 :(得分:0)

这个答案旨在成为Frankie答案的补充信息。

func calendarTest(_ from: String, _ to: String) {
    var calendar = Calendar.current
    calendar.timeZone = TimeZone(identifier: "Europe/Paris")! //<- GMT+1
    let inFormatter = DateFormatter()
    inFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
    inFormatter.timeZone = TimeZone(abbreviation: "UTC")!
    guard
        let now = inFormatter.date(from: from),
        let date = inFormatter.date(from: to)
    else {return}
    //Removing "weeks" for clarity
    let components = (calendar as NSCalendar).components([.minute, .hour, .day/*, .weekOfYear*/, .month, .year, .second], from: now, to: date, options: [])
    let outFormatter = DateFormatter()
    outFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss"
    outFormatter.timeZone = calendar.timeZone
    print("UTC time                  Localtime")
    print(now, outFormatter.string(from: now))
    print(date, outFormatter.string(from: date))
    print(components)
}

使用上面的代码:

calendarTest("2016-10-29 16:12:34","2017-03-29 16:03:13")
calendarTest("2016-10-29 16:12:34","2017-03-29 16:12:34")

输出:

UTC time                  Localtime
2016-10-29 16:12:34 +0000 2016-10-29 18:12:34
2017-03-29 16:03:13 +0000 2017-03-29 18:03:13
year: 0 month: 4 day: 28 hour: 23 minute: 50 second: 39 isLeapMonth: false 
UTC time                  Localtime
2016-10-29 16:12:34 +0000 2016-10-29 18:12:34
2017-03-29 16:12:34 +0000 2017-03-29 18:12:34
year: 0 month: 5 day: 0 hour: 0 minute: 0 second: 0 isLeapMonth: false 
calendarTest("2016-10-29 16:12:34","2017-01-29 17:03:13")
calendarTest("2016-10-29 16:12:34","2017-01-29 17:12:34")

输出:

UTC time                  Localtime
2016-10-29 16:12:34 +0000 2016-10-29 18:12:34
2017-01-29 17:03:13 +0000 2017-01-29 18:03:13
year: 0 month: 2 day: 30 hour: 23 minute: 50 second: 39 isLeapMonth: false 
UTC time                  Localtime
2016-10-29 16:12:34 +0000 2016-10-29 18:12:34
2017-01-29 17:12:34 +0000 2017-01-29 18:12:34
year: 0 month: 3 day: 0 hour: 0 minute: 0 second: 0 isLeapMonth: false 
calendarTest("2016-10-29 16:12:34","2015-09-30 15:54:20")

输出:

UTC time                  Localtime
2016-10-29 16:12:34 +0000 2016-10-29 18:12:34
2015-09-30 15:54:20 +0000 2015-09-30 17:54:20
year: -1 month: 0 day: -29 hour: 0 minute: -18 second: -14 isLeapMonth: false 
calendarTest("2016-10-29 16:12:34","2016-05-29 14:09:31")

输出:

UTC time                  Localtime
2016-10-29 16:12:34 +0000 2016-10-29 18:12:34
2016-05-29 14:09:31 +0000 2016-05-29 16:09:31
year: 0 month: -5 day: 0 hour: -2 minute: -3 second: -3 isLeapMonth: false 

请记住Calendar.current使用日期计算月数&amp; 当地时区。

顺便说一句,在两个NSCalendar之间获取DateComponents时,您无需使用Date

let components = calendar.dateComponents([.minute, .hour, .day/*, .weekOfYear*/,.month, .year, .second], from: now, to: date)