swift - 为什么我的简单日期计算在 Swift 3.1 中有时会失败?

标签 swift unit-testing nsdate nscalendar

我有一个看起来像这样的单元测试:

  func testManyYearsAgo() {
    for year in 2...77 {
      let earlierTime = calendar.date(byAdding: .year, value: 0 - year, to: now)
//      print(year)
//      print(dateDifference.itWasEstimate(baseDate: now, earlierDate: earlierTime!))
      XCTAssertEqual(dateDifference.itWasEstimate(baseDate: now, earlierDate: earlierTime!), "\(year) years ago")
    }
  }

now 被定义为 Date() calendarCalendar.current

它正在测试一个看起来像这样的类:

class DateDifference {
  func itWasEstimate(baseDate: Date, earlierDate: Date) -> String {

    let calendar = Calendar.current
    let requestedComponent: Set<Calendar.Component> = [ .year, .month, .day, .hour, .minute, .second]
    let timeDifference = calendar.dateComponents(requestedComponent, from: baseDate, to: earlierDate)

    if timeDifference.year! < 0 {
      if timeDifference.year! == -1 {
        return "Last year"
      } else {
        return "\(abs(timeDifference.year!)) years ago"
      }
    }

    return ""
  }
}

当我运行单元测试时,我通常(但不总是)得到如下错误:

XCTAssertEqual failed: ("30 years ago") is not equal to ("31 years ago")

这些错误通常在年份值超过 12 之后开始。

如果我取消注释打印语句,无论我运行代码多少次,它都可以正常工作。

这让我相信也许有一些奇怪的异步事情正在发生,但我肯定不能通过观察来判断。我对快速开发还比较陌生,所以可能缺少一些基本的东西。

最佳答案

这是一个独立的可重现示例,展示了 问题:

var calendar = Calendar(identifier: .gregorian)
calendar.locale = Locale(identifier: "en_US_POSIX")
calendar.timeZone = TimeZone(secondsFromGMT: 0)!

let formatter = DateFormatter()
formatter.calendar = calendar
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss.SSS"

let d1 = DateComponents(calendar: calendar, year: 2017, month: 1, day: 1, hour: 0,
                        minute: 0, second: 0, nanosecond: 456 * Int(NSEC_PER_MSEC)).date!
print("d1:", formatter.string(from: d1))

let d2 = calendar.date(byAdding: .year, value: -20, to: d1)!
print("d2:", formatter.string(from: d2))

let comps: Set<Calendar.Component> = [ .year, .month, .day, .hour, .minute, .second, .nanosecond]
let diff = calendar.dateComponents(comps, from: d1, to: d2)
print(diff)
print("difference in years:", diff.year!)

输出

d1: 2017-01-01 01:00:00.456
d2: 1997-01-01 01:00:00.456
year: -19 month: -11 day: -30 hour: -23 minute: -59 second: -59 nanosecond: -999999756 isLeapMonth: false 
difference in years: -19

Due to rounding errors (Date uses a binary floating point number as internal representation), the difference is computed as a tiny bit less than 20 years, and the years component of the difference comes out as -19 instead of the expected -20.

As a workaround, you can round the dates to full seconds, that seems to fix the issue:

    let baseDate = Date(timeIntervalSinceReferenceDate: baseDate
        .timeIntervalSinceReferenceDate.rounded())
    let earlierDate = Date(timeIntervalSinceReferenceDate: earlierDate
        .timeIntervalSinceReferenceDate.rounded())

您也可以考虑向 Apple 提交错误报告。

关于swift - 为什么我的简单日期计算在 Swift 3.1 中有时会失败?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43797129/

相关文章:

java - 当前最佳的单元测试 EJB3/JPA 框架

python - 模拟类的构造

ios - 如何添加当前时间(HH :MM:SS) to NSDate?

iphone - 使用 NSDateComponents 时如何不丢失 '0' 数字。(iphone)

ios - 在呈现的 View Controller 被关闭后调用主 Controller 的函数

swift - 如何将 Int 转换为枚举?

swift - 暂停和恢复 SKScene

ios swift - UITextField.tag 不起作用

python - 使用 unittest 测试作业失败

ios - 如何确定 NSDate 是否是今天?