到目前为止,添加秒数的结果是关闭一分钟。解决方法

时间:2019-01-14 18:41:21

标签: swift date foundation

我要为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))
  }
}

我想知道这一点:

  • 为什么会发生此错误?
  • 我做错什么了吗?
  • 我的解决方法是否有问题?

1 个答案:

答案 0 :(得分:3)

  

为什么会发生此错误?

我会说这是Core Foundation(CF)中的错误。

Calendar.date(byAdding:to:wrappingComponents:)调用内部Core Foundation函数_CFCalendarAddComponentsV,该函数依次使用ICU Calendar C API。 ICU将时间表示为自Unix纪元以来的毫秒浮点数,而CF使用自NeXT参考日期以来的秒数的浮点数。因此,CF必须在调用ICU之前将其表示转换为ICU的表示,然后转换回以将结果返回给您。

这是它从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);

因此它返回-62544967200 + -0.9000015258789062 = -62544967200.9,恰好比输入时间早59秒。

  

我做错什么了吗?

不,该错误在CF中,而不在您的代码中。

  

我的解决方法有问题吗?

如果您不需要亚秒精度,则解决方法应该没问题。

  

我可以用最近的日期重现它,但到目前为止只能用负的参考日期,例如日期(timeIntervalSinceReferenceDate:-1008899941.9),即1969-01-11T22:00:58Z。

每分钟间隔的最后一秒任何否定timeIntervalSinceReferenceDate都会引起问题。该错误有效地使时间0之前的第一轮整分钟的范围从-60.99999999999999到-1.0,但是范围应该从-60.0到-5e324。所有负分钟的每分钟间隔都类似地偏移。