Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

213
Views
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 answers
Answer question

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 Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!