2017-05-05 75 views
3

我有一个单元测试,看起来像这样:为什么我的简单日期计算有时在Swift 3.1中失败?

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

如果我取消了打印语句,它工作得很好,无论多少次,我运行的代码。

这使我相信也许有一些奇怪的异步事情正在进行,但我确定无法通过查找来判断。我对快速开发比较陌生,所以我可能会缺少一些基本的东西。

+0

现在已经很晚了,我可能只是忽略了一些显而易见的东西。 –

+0

我将你的代码复制到操场上,我也看到了间歇性结果。有时它可以正常工作,有时会显示一些年份的1年差异。很奇怪。顺便说一句 - 你只需要使用'.year'作为日期组件的列表。 – rmaddy

+0

也可以重现。我*假设*来自日历计算中的四舍五入错误(“Date”在内部存储具有亚秒级分辨率的double)。在进行计算之前,您可以尝试将“now”标准化为整秒。这在苹果公司也可能值得一个错误报告。 –

回答

1

这里是一个自包含的再现的例子展示了 问题:(Date使用二进制浮点数作为 内部表示)

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 

由于舍入误差,差异计算为小于20年的微小位 ,并且差异的年份组分出现为-19,而不是预期的-20。

作为一种变通方法,您可以四舍五入日期满秒, 似乎解决这个问题:

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

您也可以考虑提交苹果一个bug报告。

+0

谢谢!你能否指出我现在正在向Apple报告这种潜在错误的方法? Swift的github上可能有什么? –

+1

日历和日期是来自Foundation框架的NSCalendar/NSDate的包装,因此bugreporter.apple.com似乎对我来说是正确的。 –

1

我做了一些调试,发现有时timeDifference已关闭1天。

我所做的是我把这个线的timeDifference初始化后:

print("\(timeDifference.year!) \(timeDifference.month!) \(timeDifference.day!)") 

预期的输出是这样的

-2 0 0 
-3 0 0 
-4 0 0 
-5 0 0 
-6 0 0 
-7 0 0 
... 

然而,实际的输出包含类似:

-38 0 0 
-38 -11 -30 
-39 -11 -30 
-40 -11 -30 
... 
-55 -11 -30 
-57 0 0 

显然在某些年份,monthday分别变成-11-30

你如何解决这个问题?

不幸的是,我找不到这个问题的根本原因。然而,我已经想出了一个蛮力解决方案:

if timeDifference.year! < 0 { 
    if timeDifference.year! == -1 { 
     return "Last year" 
    } else { 
     if timeDifference.month == -11 && timeDifference.day == -30 { 
      return "\(abs(timeDifference.year!) + 1) years ago" 
     } else { 
      return "\(abs(timeDifference.year!)) years ago" 
     } 
    } 
} 

我检查时差是否关闭了1天。如果是,则在year的abs中加1。

相关问题