如何获得NSDate
的两个值之间的精确差值(以十进制表示)
例如2016年1月15日至2017年7月15日=1.5年。
我可以使用以下内容:NSCalendar.currentCalendar().components(NSCalendarUnit.CalendarUnitYear, fromDate: date1, toDate: date1, options: nil).year
但这给了我绝对值。即,对于上面的例子,它将给我一年的时间。有可能将精确的值至少精确到小数点后几位吗?
您在这里使用的术语具有误导性。
假设你想要的精度是小数点后2位,所以我们需要测量一年到1%。这比一天还大,所以跟踪天数就足够了。如果你需要更高的精度,那么你可以扩展这项技术,但如果你把它推得太远,"一年"就会变得更棘手,你必须开始问你所说的是什么意思
尽可能避免问这个问题。这里的许多答案都说"一年中有365.25天"。但试着在"现在"后面加上"365.25*24小时",看看你是否能得到"明年的同一日期和时间"。虽然"平均而言"看起来是正确的,但实际上日历日期100%都是错误的。(这是因为它在1%以内,但365、366甚至363也是如此。)
我们通过说"1%已经足够解决这个问题了"来避免这种疯狂
// What calendar do you *really* mean here? The user's current calendar,
// or the Gregorian calendar? The below code should work for any calendar,
// because every calendar's year is made up of some number of days, but it's
// worth considering if you really mean (and are testing) arbitrary calendars.
// If you mean "Gregorian," then use NSCalendar(identifier: NSCalendarIdentifierGregorian)!
let calendar = NSCalendar.currentCalendar()
// Determine how many integral days are between the dates
let diff = calendar.components(.Day, fromDate: date1, toDate: date2, options: [])
// Determine how many days are in a year. If you really meant "Gregorian" above, and
// so used calendarWithIdentifer rather than currentCalendar, you can estimate 365 here.
// Being within one day is inside the noise floor of 1%.
// Yes, this is harder than you'd think. This is based on MartinR's code: http://stackoverflow.com/a/16812482/97337
var startOfYear: NSDate? = nil
var lengthOfYear = NSTimeInterval(0)
calendar.rangeOfUnit(.Year, startDate: &startOfYear, interval: &lengthOfYear, forDate: date1)
let endOfYear = startOfYear!.dateByAddingTimeInterval(lengthOfYear)
let daysInYear = calendar.components(.Day, fromDate: startOfYear!, toDate: endOfYear, options: []).day
// Divide
let fracDiff = Double(diff.day) / Double(daysInYear)
也就是说,在大多数情况下,你不应该这样做。自iOS 8以来,首选工具是NSDateComponentsFormatter
。你不会得到这种精确的格式(即分数年),但你会得到一个很好的本地化结果,它考虑了不同文化中的大多数问题。
let formatter = NSDateComponentsFormatter()
formatter.unitsStyle = .Full
formatter.includesApproximationPhrase = true
formatter.allowedUnits = [.Year, .Month]
formatter.allowsFractionalUnits = true
formatter.stringFromDate(date1, toDate: date2)
// About 1 year, 6 months
由于您提到您的目标是可以向用户显示为两个日期之间的时间的有意义的指示,因此您可能会发现使用NSDateComponentsFormatter
更容易。例如:
let dateStr1 = "Jan 15 2016"
let dateStr2 = "Jul 15 2017"
let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = "MMM dd yyyy"
if let date1 = dateFormatter.dateFromString(dateStr1),
let date2 = dateFormatter.dateFromString(dateStr2) {
let dateComponentsFormatter = NSDateComponentsFormatter()
dateComponentsFormatter.allowedUnits = [.Year, .Month]
dateComponentsFormatter.unitsStyle = .Full
let difference = dateComponentsFormatter.stringFromDate(date1, toDate: date2)
}
这会给你一个字符串,上面写着"1年6个月"。这并不完全是你指定的目标,但它对用户来说是一个明确的指示,避免了很多复杂性。NSDateComponentsFormatter
上有一个名为allowsFractionalUnits
的属性,认为会导致类似"1.5年"的结果,但现在似乎不起作用。(即使您将allowedUnits
限制为仅.Year
,仍然没有分数年。我要向Apple提交一个错误…)。您可以调整allowedUnits
以获得您喜欢的任何粒度,如果不精确,则使用includesApproximationPhrase
让类在生成的字符串中添加本地化版本的"About…"。如果您在最终格式中有一些灵活性,这将是一个非常好的解决方案。
如果你假设每年365.2425天,每天有24小时,那么计算是微不足道的:
let secondsPerYear: NSTimeInterval = NSTimeInterval(365.2425 * 24 * 60 * 60)
let secondsBetweenDates =
date2.timeIntervalSinceReferenceDate - date1.timeIntervalSinceReferenceDate;
let yearsBetweenDates = secondsBetweenDates / secondPerYear
但也有很多边缘案例和怪异之处需要处理。由于闰年,有些年份有365天,有些年份则有366天。然后是闰秒。
如果你在@CodeDifferent的答案中去掉了几个月,那么你会得到一个允许日期之间闰日的答案。
但是,正如Code Different所指出的,他的书面答案实际上给出了看起来更准确的答案,尽管事实并非如此。(3个月的差异总是会产生.25年,并且会忽略更长/更短的月份。这是正确的做法吗?取决于你的目标和假设。)
根据美国国家航空航天局的数据,平均每年有365.2422天。在这里,我将其四舍五入到每年365.25天:
let components = NSCalendar.currentCalendar().components([.Year, .Month, .Day], fromDate: fromDate, toDate: toDate, options: [])
var totalYears = Double(components.year)
totalYears += Double(components.month) / 12.0
totalYears += Double(components.day) / 365.25
显然,这取决于你的假设。如果要计算fromDate
和toDate
之间的闰日,则会更加复杂。
一些样本输出:
From date To date Total Years
------------ ------------ ------------
Jan 15, 2016 Jul 15, 2017 1.5
Jan 15, 2016 Apr 14, 2016 0.25
Jan 15, 2016 Aug 15, 2017 1.5833
Jan 15, 2016 Jan 14, 2018 1.9988