获取单个NSDateComponent的2个日期之间的确切差异

时间:2016-05-15 20:19:13

标签: ios swift nsdate nsdatecomponents

如何获得NSDate的2个值之间的精确差异(十进制)。

EG。 2016年1月15日 2017年7月15日 = 1。5年

我可以使用类似的东西: NSCalendar.currentCalendar().components(NSCalendarUnit.CalendarUnitYear, fromDate: date1, toDate: date1, options: nil).year
但这给了我绝对值。即,对于上面的例子,它会给我 1年。是否有可能将精确值正确至少几位小数?

4 个答案:

答案 0 :(得分:5)

您在此使用的条款具有误导性。当你说"绝对"你的意思是"积分。"当你说"确切"你的意思是"在一定的精度范围内。"

让我们说你想要的精度是2位小数,所以我们需要测量一年到1%。这比一天大,所以追踪天数就足够了。如果你需要更高的精度,那么你可以扩展这种技术,但是如果你把它推得太远,"年"变得更加棘手,你必须在一年之后开始询问你的意思。"

尽可能避免问这个问题。这里的许多答案都说像#34;一年有365.25天。"但是尝试添加" 365.25 * 24小时"到现在""看看你明年是否得到了相同的日期和时间。"虽然看起来可能是正确的,但平均而言,#34;对于日历日期,它实际上是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

答案 1 :(得分:3)

由于您提到您的目标是可以向用户显示的内容,作为两个日期之间时间的有意义指示,您可能会发现使用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年”之类的结果,但it doesn't seem to work right now。 (即使您将allowedUnits限制为仅.Year,您也不会获得分数年份。我要向Apple提交错误...) 。您可以调整allowedUnits以获得您喜欢的任何粒度,并使用includesApproximationPhrase让类将“About ...”的本地化版本添加到结果字符串中(如果它不准确)。如果您在最终格式中有一定的灵活性,这将是一个非常好的解决方案。

答案 2 :(得分:2)

这个问题没有一个完美的答案。不同年份的长度略有不同。你必须做出一些假设。

如果您假设每年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年,并且会忽略更长/更短的月份。这是正确的做法吗?取决于你的目标和你的假设。)

答案 3 :(得分:1)

根据NASA,平均每年有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

显然,这取决于你的假设。如果您想计算fromDatetoDate之间的闰日,则会更复杂。

一些示例输出:

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