Swift 中的 DateComponents 返回奇怪的值。为啥?
Posted
技术标签:
【中文标题】Swift 中的 DateComponents 返回奇怪的值。为啥?【英文标题】:DateComponents in Swift returns weird values. Why?Swift 中的 DateComponents 返回奇怪的值。为什么? 【发布时间】:2016-10-29 16:18:57 【问题描述】:我正在计算两个日期之间的时间差。我有一段代码:
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
将所有内容四舍五入的语句更好的方法来计算这种差异?
【问题讨论】:
你在哪个时区?日期以 UTC/GMT 打印,但日历计算在您的本地日历/时区完成。 在您的第一个示例中,给定日期之间 小于 5 个月,因此结果 4 是正确的。 hm 但我认为在months
的情况下时区不应该影响计算...无论如何,我在GMT +1
,有什么方法可以独立于时区进行计算?
您可以将日历时区设置为 GMT,但即便如此,在第一个示例中您会得到 4 而不是 5,在第三个示例中会得到 0,因为差异是 less 一年多。试试print(components)
看看实际的区别。
几乎在编程方面是不是;-)
【参考方案1】:
2016-10-29 16:12:34 +0000
2017-03-29 16:03:13 +0000
Optional(4) //I think it should return 5?
由于 16:03 是
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)
你得到'3'
更改月份但保持原来的时间:
2016-7-29 16:12:34 +0000
2016-10-29 17:03:13 +0000
Optional(3)
你也会得到'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?
日期组件返回您在组件定义中包含的最高单位的差值。在您的情况下,您已包含NSCalendar.Unit.year
。因此components.month
将返回月份单位,而不是月份的总差值。所以这个计算中的月份单位是0个月。如果要在日期上加上 25 年零 5 个月,它将返回“5”。但是,如果您要删除 NSCalendar.Unit.year
,那么它将使用月份作为最高单位并返回 '305'
你可以像这样测试一些日期:
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)!
【讨论】:
【参考方案2】:从 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)
【讨论】:
我不同意第一个陈述,因为如果您在不使用 HH:mm:ss 的情况下评估“从 2016-10-29 到 2017-03-29”,那么它实际上评估为 5 个月。但是 OP 的示例包含的确切时间使其接近 5 个月。 (不是我的反对票) @Frankie:你说得对,缩写太多了,我现在已经修好了,谢谢你的反馈! – 然而,建议的解决方案应该适用于所有问题的情况(我从更新中了解到)。【参考方案3】:此答案旨在为 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
使用当地时区的日期和时间计算月份。
顺便说一句,在两个Date
s 之间获取DateComponents
时,您无需使用NSCalendar
。
let components = calendar.dateComponents([.minute, .hour, .day/*, .weekOfYear*/,.month, .year, .second], from: now, to: date)
【讨论】:
以上是关于Swift 中的 DateComponents 返回奇怪的值。为啥?的主要内容,如果未能解决你的问题,请参考以下文章