2017-05-05 76 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。

相關問題