Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add days to a date and computing the difference doesn't lead to expected result

Tags:

I'm not sure what I'm doing wrong here. If you run this code in a Xcode Playground (I'm using 11.1) you will see it failing for random dates that, in my system, happen to be somewhere after 6500 days added to the current date. However those dates aren't fixed.

(1...10000).forEach { days in
    let calendar = Calendar(identifier: .gregorian)
    var dayComponent = DateComponents()
    dayComponent.day = days
    let date = Date()
    let someFutureDate = calendar.date(byAdding: dayComponent, to: date)!

    let difference = calendar.dateComponents([.day], from: date, to: someFutureDate)

    if (days != difference.day!) {
        print("WAT \(date) - \(someFutureDate) \(days) \(difference.day!)")
    }
}

Some output (failed from 130 to 160 times in my tests, output vary):

WAT 2019-10-31 14:01:47 +0000 - 2038-01-21 14:01:47 +0000 6657 6656
WAT 2019-10-31 14:01:47 +0000 - 2038-01-26 14:01:47 +0000 6662 6661
WAT 2019-10-31 14:01:47 +0000 - 2038-02-02 14:01:47 +0000 6669 6668
WAT 2019-10-31 14:01:47 +0000 - 2038-02-05 14:01:47 +0000 6672 6671
WAT 2019-10-31 14:01:47 +0000 - 2038-02-12 14:01:47 +0000 6679 6678
WAT 2019-10-31 14:01:47 +0000 - 2038-02-14 14:01:47 +0000 6681 6680
[snip]
like image 501
The Horny Coder Avatar asked Oct 31 '19 14:10

The Horny Coder


1 Answers

That is due to rounding errors (note that a Date is internally represented as a floating point number holding the number of seconds since January 1, 2001). If you retrieve more components of the difference

let difference = calendar.dateComponents([.day, .hour, .minute, .second, .nanosecond],
                                         from: date, to: someFutureDate)

then you'll notice that it is in the first example something like

day: 6656 hour: 23 minute: 59 second: 59 nanosecond: 999999755 

which is almost the expected 6657 days. A possible fix seems to be to remove the “fractional part” of the date with

let date = calendar.date(bySetting: .nanosecond, value: 0, of: Date())!

or

let date = Date(timeIntervalSinceReferenceDate:
                Date().timeIntervalSinceReferenceDate.rounded())

With this change, I could not observe a difference anymore.

like image 137
Martin R Avatar answered Oct 22 '22 13:10

Martin R