【问题标题】:Get the exact difference between 2 dates for a single NSDateComponent获取单个 NSDateComponent 的 2 个日期之间的确切差异
【发布时间】:2016-09-11 14:42:42
【问题描述】:

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

例如。 2016 年 1 月 15 日2017 年 7 月 15 日 = 1.5 年

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

【问题讨论】:

  • 您需要定义小数年的含义。是基于两个日期之间的天数的一年中的小数部分(这两个日期相隔 366 天(2016 年是闰年)。或者它是否意味着正好 6 个月(并且 6 个月的各种组有不同的天数)。
  • 啊,好吧。我假设由于指定了 fromto 日期,Swift 知道哪一年是闰年,哪些月份是 28、30、31 天等。如果情况并非如此,那么我将不得不根据假设实施我自己的解决方案。所以基本上 Swift 不会给我精确的十进制值,对吧?
  • 当然所有这些信息都是已知的。你错过了我的观点。您首先需要为小数年得出所需的定义。计算部分年份的方法不止一种。
  • 我想我有一些阅读要做。不熟悉部分年份/小数年份的概念。你能给我一些阅读链接或例子吗?
  • 我想我明白你的意思了。因此,如果我得到 365 天的差异,那么要获得转换,我需要定义 365 天 = 1 年还是 366 天 = 1 年。

标签: ios swift nsdate nsdatecomponents


【解决方案1】:

您在此处使用的术语具有误导性。当您说“绝对”时,您的意思是“整体”。当您说“精确”时,您的意思是“在某个所需的精度范围内”。

假设您想要的精度是小数点后 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

【讨论】:

    【解决方案2】:

    由于您提到您的目标是可以向用户显示的东西,作为两个日期之间时间的有意义的指示,您可能会发现使用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 让类将“关于...”的本地化版本添加到结果字符串中(如果它不精确)。如果您的最终格式具有一定的灵活性,这将是一个非常好的解决方案。

    【讨论】:

    • 是的,感谢您的帮助!将值拆分为不同的组件是直截了当的 :) 我只是想显示一个特定组件的确切值。不过谢谢!
    • 更新了我的问题标题以避免混淆!
    【解决方案3】:

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

    如果您假设一年有 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 个月的差异总是会产生 0.25 年,并且会忽略更长/更短的月份。这是正确的做法吗?取决于您的目标和假设。)

    【讨论】:

      【解决方案4】:

      根据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
      

      【讨论】:

      • 在计算中包含月份似乎会不必要地引入错误。月份的长度从 28 天到 31 天不等。为什么不询问两个日期之间的年数和天数并完全跳过几个月?
      • @DuncanC 不幸的是,这不是人类思维的运作方式。比如Apr 1Jul 1之间有91天; Jul 1Oct 1 之间的 92 天。但是,大多数人会说它们都是3个月的差异。我们甚至有一个名称:四分之一,或一年的0.25。计算日期很复杂。
      • 同意它很复杂。这取决于目标,真的。如果您想要尽可能准确地估计日期之间的小数差异,则省略月份会更好。不过,您关于期望的观点是一个很好的观点。我们预计从 2 月 1 日到 3 月 1 日的差异是一年的 1/12,而 3 月 1 日和 4 月 1 日之间的差异也是一年的 1/12,尽管第一个范围是 28/29 天并且第二个范围是 31 天。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-05-31
      • 1970-01-01
      • 2020-03-22
      • 2017-03-27
      • 2016-03-20
      • 2011-07-30
      相关资源
      最近更新 更多