61

I've seen many approaches how to compute the difference between two dates in terms of a particular date component, e.g. in days, hours, months etc. (see this answer on Stackoverflow):

Calendar.current.dateComponents([.hour], from: fromDate, to: toDate).hour
Calendar.current.dateComponents([.day], from: fromDate, to: toDate).day
Calendar.current.dateComponents([.month], from: fromDate, to: toDate).month

What I haven't seen is how to make calculations with the actual Date objects. Something like

func computeNewDate(from fromDate: Date, to toDate: Date) -> Date    
     let delta = toDate - fromDate
     let today = Date()
     if delta < 0 {
         return today
     } else {
         return today + delta
     }
}

I have seen the DateInterval type introduced in iOS 10 but according to the documentation

[it] does not support reverse intervals i.e. intervals where the duration is less than 0 and the end date occurs earlier in time than the start date.

That makes it inherently difficult to calculate with dates – especially when you don't know which one is the earlier date.

Is there any clean and neat approach to compute time differences between Dates directly (and adding them to Date instances again) without computing with their timeIntervalSinceReferenceDate?

Mischa
  • 15,816
  • 8
  • 59
  • 117
  • 3
    The [Apple Documentation](https://developer.apple.com/documentation/foundation/nsdate/1409748-compare) describes a compare function that will order the dates for you so you can run your evaluation. – Jake Jun 20 '18 at 14:05
  • 2
    What's the problem with `timeIntervalSinceReferenceDate`? – André Slotta Jun 20 '18 at 14:48
  • @Jake: Sure I can compare dates with that but I cannot compute time intervals between two dates which I can then add to another date. The function I have included in my question is only an example for I made up to show some application of it, admittedly a very simple one that could be implemented in a nicer way. But it's not the question. The question is how to compute date differences easily with an intuitive syntax. – Mischa Jun 22 '18 at 21:53
  • 1
    @AndréSlotta: The problem is that it makes code less readable, less beautiful and less intuitive: `toDate - fromDate` is a lot easier to read than `toDate.timeIntervalSinceReferenceDate - fromDate.timeIntervalSinceReferenceDate`. – Mischa Jun 22 '18 at 21:54
  • 1
    Plus it's not clear to me why Swift wouldn't let me subtract two dates directly. – Mischa Jun 22 '18 at 21:56

6 Answers6

99

I ended up creating a custom operator for Date:

extension Date {

    static func - (lhs: Date, rhs: Date) -> TimeInterval {
        return lhs.timeIntervalSinceReferenceDate - rhs.timeIntervalSinceReferenceDate
    }

}

With this operator I can now compute the difference between two dates on a more abstract level without caring about timeIntervalSinceReferenceDate or what exactly the reference date is – and without losing precision, for example:

let delta = toDate - fromDate

Obviously, I didn't change much, but for me it's a lot more readable and consequent: Swift has the + operator already implemented for a Date and a TimeInterval:

/// Returns a `Date` with a specified amount of time added to it.
public static func + (lhs: Date, rhs: TimeInterval) -> Date

So it's already supporting

Date + TimeInterval = Date

Consequently, it should also support

Date - Date = TimeInterval

in my opinion and that's what I added with the simple implementation of the - operator. Now I can simply write the example function exactly as mentioned in my question:

func computeNewDate(from fromDate: Date, to toDate: Date) -> Date    
     let delta = toDate - fromDate // `Date` - `Date` = `TimeInterval`
     let today = Date()
     if delta < 0 {
         return today
     } else {
         return today + delta // `Date` + `TimeInterval` = `Date`
     }
}

It might very well be that this has some downsides that I'm not aware of at this moment and I'd love to hear your thoughts on this.

Mischa
  • 15,816
  • 8
  • 59
  • 117
51

You can extension with custom operator, and return tuples

extension Date {

    static func -(recent: Date, previous: Date) -> (month: Int?, day: Int?, hour: Int?, minute: Int?, second: Int?) {
        let day = Calendar.current.dateComponents([.day], from: previous, to: recent).day
        let month = Calendar.current.dateComponents([.month], from: previous, to: recent).month
        let hour = Calendar.current.dateComponents([.hour], from: previous, to: recent).hour
        let minute = Calendar.current.dateComponents([.minute], from: previous, to: recent).minute
        let second = Calendar.current.dateComponents([.second], from: previous, to: recent).second

        return (month: month, day: day, hour: hour, minute: minute, second: second)
    }

}

Using:

let interval = Date() - updatedDate
print(interval.day)
print(interval.month)
print(interval.hour)
Mickael Belhassen
  • 2,970
  • 1
  • 25
  • 46
41

You can use :

let delta = fromDate.distance(to: toDate)
joe69mac
  • 411
  • 4
  • 2
  • 2
    This is a super helpful function. Just that, it's available only on iOS 13+ – chakkala Jul 29 '22 at 10:12
  • Please upvote this one! iOS13 is common at the moment, the way higher voted do overvomplicate things if your are developing iOS apps nowadays. – laka Mar 20 '23 at 09:14
35

Simply toDate.timeIntervalSince(fromDate).

To reimplement your function without adding any extension:

func computeNewDate(from fromDate: Date, to toDate: Date) -> Date  {  
     let delta = toDate.timeIntervalSince(fromDate) 
     let today = Date()
     if delta < 0 {
         return today
     } else {
         return today.addingTimeInterval(delta) 
     }
}
cakraww
  • 2,493
  • 28
  • 30
13

I found a builtin solution to calculate the difference between 2 dates.

let delta = toDate.timeIntervalSince(fromDate)

M1ch3l
  • 131
  • 1
  • 2
  • 3
    [This has been suggested](https://stackoverflow.com/a/54751685/2062785) by @cakraww already. – Mischa Apr 15 '20 at 09:36
1

How about something like…

func computeNewDate(from fromDate: Date, to toDate: Date) -> Date {
    let delta = Calendar.current.dateComponents([.second], from: fromDate, to: toDate).second!
    return Calendar.current.date(byAdding: .second, value: delta, to: Date())!
}
Ashley Mills
  • 50,474
  • 16
  • 129
  • 160
  • 3
    That's using the components again, not the inherent dates. `delta` will be an integer representing seconds, so this function loses precision for any date that is not a multiple of a full second. – Mischa Jun 20 '18 at 22:49