Estoy agregando un segundo a una instancia de la fecha de la Fundación, pero el resultado tiene un error de un minuto completo.
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:59ZCuriosamente, uno de los siguientes hace que el error desaparezca:
Realmente no necesito una precisión de menos de un segundo en mi código, así que creé esta extensión que me permite descartarla.
extension Date { func roundedToSeconds() -> Date { return Date(timeIntervalSinceReferenceDate: round(timeIntervalSinceReferenceDate)) } }quiero saber esto:
¿Por qué ocurre este error?
Yo diría que esto es un error en Core Foundation (CF).
Calendar.date(byAdding:to:wrappingComponents:) llama a la función interna de Core Foundation _CFCalendarAddComponentsV , que a su vez usa la API de ICU Calendar C . ICU representa un tiempo como un número de coma flotante de milisegundos desde la época de Unix, mientras que CF usa un número de segundos de coma flotante desde la fecha de referencia de NeXT. Entonces CF tiene que convertir su representación a la representación de ICU antes de llamar a ICU, y volver a convertir para devolverle el resultado.
Así es como se convierte de una marca de tiempo CF a una marca de tiempo ICU:
double startingInt; double startingFrac = modf(*atp, &startingInt); UDate udate = (startingInt + kCFAbsoluteTimeIntervalSince1970) * 1000.0; La función modf divide un número de punto flotante en sus partes enteras y fraccionarias. Introduzcamos su fecha de ejemplo:
var startingInt: Double = 0 var startingFrac: Double = modf(date1.timeIntervalSinceReferenceDate, &startingInt) print(startingInt, startingFrac) // Output: -62544967141.0 -0.9000015258789062 A continuación, CF llama a __CFCalendarAdd para agregar un segundo a -62544967141. Tenga en cuenta que -62544967141 se encuentra en el intervalo redondo de un minuto -62544967200 ..< -62544967140.0. Entonces, cuando CF agrega un segundo a -62544967141, obtiene -62544967140, que estaría en el intervalo de un minuto de la siguiente ronda. Dado que especificó componentes de ajuste, CF no puede cambiar la parte de los minutos de la fecha, por lo que vuelve al principio del intervalo de un minuto de la ronda original, -62544967200.
Finalmente, CF vuelve a convertir el tiempo de la UCI en un tiempo de CF, agregando la parte fraccionaria del tiempo original:
*atp = (udate / 1000.0) - kCFAbsoluteTimeIntervalSince1970 + startingFrac + (nanosecond * 1.0e-9);Entonces devuelve -62544967200 + -0.9000015258789062 = -62544967200.9, exactamente 59 segundos antes del tiempo de entrada.
¿Estoy haciendo algo mal?
No, el error está en CF, no en su código.
¿Hay algún problema con mi solución alternativa?
Si no necesita una precisión de menos de un segundo, su solución debería estar bien.
Puedo reproducirlo con fechas más recientes, pero hasta ahora solo con fechas de referencia negativas, por ejemplo, Date(timeIntervalSinceReferenceDate: -1008899941.9), que es 1969-01-11T22:00:58Z.
Cualquier timeIntervalSinceReferenceDate negativo en el último segundo de un intervalo de minutos debería causar el problema. El error hace que la primera ronda sea un minuto completo antes del tiempo 0 desde -60,999999999999999 hasta -1,0, pero debería abarcar desde -60,0 hasta -5e324. Todos los intervalos de minutos redondos más negativos se compensan de manera similar.