为什么我的简单日期计算有时会在 Swift 3.1 中失败?
Why are my simple date calculations sometimes failing in 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()
calendar
是 Calendar.current
它正在测试一个看起来像这样的 class:
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 之后开始。
如果我取消注释打印语句,无论我 运行 代码多少次,它都可以正常工作。
这让我相信可能发生了一些奇怪的异步事情,但我肯定不能通过观察来判断。我是 swift 开发的新手,所以可能缺少一些基本的东西。
我做了一些调试,发现有时 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
显然在某些年份,month
和 day
分别变为 -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
这是一个独立的可重现示例,展示了
问题:
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
由于舍入误差(Date
使用二进制浮点数作为
内部表示),差异被计算为微小的一点
不到 20 年,差异的年份部分显示为 -19 而不是预期的 -20。
作为解决方法,您可以将日期四舍五入为整秒,
这似乎解决了问题:
let baseDate = Date(timeIntervalSinceReferenceDate: baseDate
.timeIntervalSinceReferenceDate.rounded())
let earlierDate = Date(timeIntervalSinceReferenceDate: earlierDate
.timeIntervalSinceReferenceDate.rounded())
您也可以考虑向 Apple 提交错误报告。
我有一个如下所示的单元测试:
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()
calendar
是 Calendar.current
它正在测试一个看起来像这样的 class:
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 之后开始。
如果我取消注释打印语句,无论我 运行 代码多少次,它都可以正常工作。
这让我相信可能发生了一些奇怪的异步事情,但我肯定不能通过观察来判断。我是 swift 开发的新手,所以可能缺少一些基本的东西。
我做了一些调试,发现有时 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
显然在某些年份,month
和 day
分别变为 -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
.
这是一个独立的可重现示例,展示了 问题:
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
由于舍入误差(Date
使用二进制浮点数作为
内部表示),差异被计算为微小的一点
不到 20 年,差异的年份部分显示为 -19 而不是预期的 -20。
作为解决方法,您可以将日期四舍五入为整秒, 这似乎解决了问题:
let baseDate = Date(timeIntervalSinceReferenceDate: baseDate
.timeIntervalSinceReferenceDate.rounded())
let earlierDate = Date(timeIntervalSinceReferenceDate: earlierDate
.timeIntervalSinceReferenceDate.rounded())
您也可以考虑向 Apple 提交错误报告。