将第二个添加到日期的结果是一分钟;解决方法
Result of adding second to date is one minute off; workaround
我正在向 Foundation 的日期实例添加一秒,但结果偏差了整整一分钟。
var calendar = Calendar(identifier: .iso8601)
calendar.locale = Locale(identifier: "en")
calendar.timeZone = TimeZone(identifier: "GMT")!
let date1 = Date(timeIntervalSinceReferenceDate: -62544967141.9)
let date2 = calendar.date(byAdding: DateComponents(second: 1),
to: date1,
wrappingComponents: true)!
ISO8601DateFormatter().string(from: date1) // => 0019-01-11T22:00:58Z
ISO8601DateFormatter().string(from: date2) // => 0019-01-11T21:59:59Z
有趣的是,以下其中一项会使错误消失:
- 自参考日期以来的时间间隔
- 不向日历添加时区
- 将 wrappingComponents 设置为 false(即使在这种情况下不应换行)
我的代码中不需要亚秒级精度,所以我创建了这个允许我放弃它的扩展。
extension Date {
func roundedToSeconds() -> Date {
return Date(timeIntervalSinceReferenceDate: round(timeIntervalSinceReferenceDate))
}
}
我想知道这个:
- 为什么会出现这个错误?
- 我是不是做错了什么?
- 我的解决方法有问题吗?
Why does this error happen?
我会说这是 Core Foundation (CF) 中的错误。
Calendar.date(byAdding:to:wrappingComponents:)
向下调用内部 Core Foundation 函数 _CFCalendarAddComponentsV
, which in turn uses the ICU Calendar C API。 ICU 将时间表示为自 Unix 纪元以来的浮点毫秒数,而 CF 使用自 NeXT 参考日期以来的浮点秒数。所以CF在调用ICU之前必须先把它的representation转换成ICU的representation,再转换回return结果给你
以下是它如何从 CF 时间戳转换为 ICU 时间戳:
double startingInt;
double startingFrac = modf(*atp, &startingInt);
UDate udate = (startingInt + kCFAbsoluteTimeIntervalSince1970) * 1000.0;
modf
函数将浮点数拆分为整数和小数部分。让我们插入您的示例日期:
var startingInt: Double = 0
var startingFrac: Double = modf(date1.timeIntervalSinceReferenceDate, &startingInt)
print(startingInt, startingFrac)
// Output:
-62544967141.0 -0.9000015258789062
接下来,CF调用__CFCalendarAdd
将-62544967141加一秒。请注意,-62544967141 位于一分钟间隔 -62544967200 ..< -62544967140.0 内。所以当CF在-62544967141上加一秒时,它得到-62544967140,这将在下一轮一分钟间隔内。由于您指定了换行组件,因此不允许 CF 更改日期的分钟部分,因此它换行到原始循环一分钟间隔的开始,-62544967200。
最后,CF将ICU时间转换回CF时间,加上原始时间的小数部分:
*atp = (udate / 1000.0) - kCFAbsoluteTimeIntervalSince1970 + startingFrac + (nanosecond * 1.0e-9);
所以returns -62544967200 + -0.9000015258789062 = -62544967200.9,正好比输入时间早59秒
Am I doing something wrong?
不,错误在 CF 中,不在您的代码中。
Is there any issue with my workaround?
如果您不需要亚秒级精度,您的解决方法应该没问题。
I can reproduce it with more recent dates but so far only with negative reference dates, e.g. Date(timeIntervalSinceReferenceDate: -1008899941.9), which is 1969-01-11T22:00:58Z.
在一分钟间隔的最后一秒出现任何负数 timeIntervalSinceReferenceDate
应该会导致问题。该错误有效地使时间 0 之前的第一轮整分钟从 -60.99999999999999 到 -1.0,但它应该从 -60.0 到 -5e324。所有更负的圆分钟间隔都类似地偏移。
我正在向 Foundation 的日期实例添加一秒,但结果偏差了整整一分钟。
var calendar = Calendar(identifier: .iso8601)
calendar.locale = Locale(identifier: "en")
calendar.timeZone = TimeZone(identifier: "GMT")!
let date1 = Date(timeIntervalSinceReferenceDate: -62544967141.9)
let date2 = calendar.date(byAdding: DateComponents(second: 1),
to: date1,
wrappingComponents: true)!
ISO8601DateFormatter().string(from: date1) // => 0019-01-11T22:00:58Z
ISO8601DateFormatter().string(from: date2) // => 0019-01-11T21:59:59Z
有趣的是,以下其中一项会使错误消失:
- 自参考日期以来的时间间隔
- 不向日历添加时区
- 将 wrappingComponents 设置为 false(即使在这种情况下不应换行)
我的代码中不需要亚秒级精度,所以我创建了这个允许我放弃它的扩展。
extension Date {
func roundedToSeconds() -> Date {
return Date(timeIntervalSinceReferenceDate: round(timeIntervalSinceReferenceDate))
}
}
我想知道这个:
- 为什么会出现这个错误?
- 我是不是做错了什么?
- 我的解决方法有问题吗?
Why does this error happen?
我会说这是 Core Foundation (CF) 中的错误。
Calendar.date(byAdding:to:wrappingComponents:)
向下调用内部 Core Foundation 函数 _CFCalendarAddComponentsV
, which in turn uses the ICU Calendar C API。 ICU 将时间表示为自 Unix 纪元以来的浮点毫秒数,而 CF 使用自 NeXT 参考日期以来的浮点秒数。所以CF在调用ICU之前必须先把它的representation转换成ICU的representation,再转换回return结果给你
以下是它如何从 CF 时间戳转换为 ICU 时间戳:
double startingInt;
double startingFrac = modf(*atp, &startingInt);
UDate udate = (startingInt + kCFAbsoluteTimeIntervalSince1970) * 1000.0;
modf
函数将浮点数拆分为整数和小数部分。让我们插入您的示例日期:
var startingInt: Double = 0
var startingFrac: Double = modf(date1.timeIntervalSinceReferenceDate, &startingInt)
print(startingInt, startingFrac)
// Output:
-62544967141.0 -0.9000015258789062
接下来,CF调用__CFCalendarAdd
将-62544967141加一秒。请注意,-62544967141 位于一分钟间隔 -62544967200 ..< -62544967140.0 内。所以当CF在-62544967141上加一秒时,它得到-62544967140,这将在下一轮一分钟间隔内。由于您指定了换行组件,因此不允许 CF 更改日期的分钟部分,因此它换行到原始循环一分钟间隔的开始,-62544967200。
最后,CF将ICU时间转换回CF时间,加上原始时间的小数部分:
*atp = (udate / 1000.0) - kCFAbsoluteTimeIntervalSince1970 + startingFrac + (nanosecond * 1.0e-9);
所以returns -62544967200 + -0.9000015258789062 = -62544967200.9,正好比输入时间早59秒
Am I doing something wrong?
不,错误在 CF 中,不在您的代码中。
Is there any issue with my workaround?
如果您不需要亚秒级精度,您的解决方法应该没问题。
I can reproduce it with more recent dates but so far only with negative reference dates, e.g. Date(timeIntervalSinceReferenceDate: -1008899941.9), which is 1969-01-11T22:00:58Z.
在一分钟间隔的最后一秒出现任何负数 timeIntervalSinceReferenceDate
应该会导致问题。该错误有效地使时间 0 之前的第一轮整分钟从 -60.99999999999999 到 -1.0,但它应该从 -60.0 到 -5e324。所有更负的圆分钟间隔都类似地偏移。