Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

216
Visualizações
Result of adding second to date is one minute off; workaround

I'm adding a second to an instance of Foundation's date, but the result is off by an entire minute.

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

Interestingly, one of the following makes the error go away:

  • round time interval since reference date
  • don't add time zone to calendar
  • set wrappingComponents to false (even though it shouldn't wrap in this case)

I don't really need sub-second precision in my code, so I created this extension that allows me to discard it.

extension Date {
  func roundedToSeconds() -> Date {
    return Date(timeIntervalSinceReferenceDate: round(timeIntervalSinceReferenceDate))
  }
}

I want to know this:

  • Why does this error happen?
  • Am I doing something wrong?
  • Is there any issue with my workaround?
over 4 years ago · Santiago Trujillo
1 Respostas
Responde à pergunta

0

Why does this error happen?

I would say this is a bug in Core Foundation (CF).

Calendar.date(byAdding:to:wrappingComponents:) calls down to the internal Core Foundation function _CFCalendarAddComponentsV, which in turn uses the ICU Calendar C API. ICU represents a time as an floating-point number of milliseconds since the Unix epoch, while CF uses a floating-point number of seconds since the NeXT reference date. So CF has to convert its representation to ICU's representation before calling into ICU, and convert back to return the result to you.

Here's how it converts from a CF timestamp to an ICU timestamp:

    double startingInt;
    double startingFrac = modf(*atp, &startingInt);
    UDate udate = (startingInt + kCFAbsoluteTimeIntervalSince1970) * 1000.0;

The modf function splits a floating-point number into its integer and fractional parts. Let's plug in your example date:

var startingInt: Double = 0
var startingFrac: Double = modf(date1.timeIntervalSinceReferenceDate, &startingInt)
print(startingInt, startingFrac)

// Output:
-62544967141.0 -0.9000015258789062

Next, CF calls __CFCalendarAdd to add one second to -62544967141. Note that -62544967141 lies in the round one-minute interval -62544967200 ..< -62544967140.0. So when CF adds one second to -62544967141, it gets -62544967140, which would be in the next round one-minute interval. Since you specified wrapping components, CF isn't allowed to change the minute part of the date, so it wraps back to the beginning of the original round one-minute interval, -62544967200.

Finally, CF converts the ICU time back to a CF time, adding in the fractional part of the original time:

    *atp = (udate / 1000.0) - kCFAbsoluteTimeIntervalSince1970 + startingFrac + (nanosecond * 1.0e-9);

So it returns -62544967200 + -0.9000015258789062 = -62544967200.9, exactly 59 seconds earlier than the input time.

Am I doing something wrong?

No, the bug is in CF, not in your code.

Is there any issue with my workaround?

If you don't need sub-second precision, your workaround should be fine.

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.

Any negative timeIntervalSinceReferenceDate in the last second of a minute interval should cause the problem. The bug effectively makes the first round whole minute prior to time 0 span from -60.99999999999999 through -1.0, but it should span from -60.0 through -5e324. All more-negative round minute intervals are similarly offset.

over 4 years ago · Santiago Trujillo Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda