naoty / Timepiece

Intuitive date handling in Swift
MIT License
2.63k stars 147 forks source link

Shortcut for timeIntervalSince(_:) #84

Open naoty opened 7 years ago

naoty commented 7 years ago
// shortcut for date1.timeIntervalSince(date2)
date1 - date2
basememara commented 7 years ago

Hi @naoty, thanks for the great library and recent update!

I like this enhancement and was going to create a PR, but it was kind of tricky due to months not being a straight forward conversion. It's a WIP but this is what I have so far:

extension Date {

    static func - (left: Date, right: Date) -> DateComponents {
        return Calendar.current.dateComponents([.year, .month, .day, .hour, .minute, .second], from: right, to: left)
    }

    static func - (left: Date, right: Date) -> TimeInterval {
        return left.timeIntervalSince(right)
    }
}

extension TimeInterval {

    func seconds(to component: Calendar.Component) -> Double {
        switch component {
        case .year:
            return self / 31536000
        case .month:
            assertionFailure("Month component is not supported for conversion from seconds.")
            return 0 //Fail
        case .day:
            return self.truncatingRemainder(dividingBy: 31536000) / 86400
        case .hour:
            return self.truncatingRemainder(dividingBy: 86400) / 3600
        case .minute:
            return self.truncatingRemainder(dividingBy: 3600) / 60
        case .second:
            return self
        default:
            assertionFailure("Specified component is not supported for conversion from seconds.")
            return 0 //Fail
        }
    }
}

let date1 = Date()
let date2 = Date(timeIntervalSinceNow: -7200)

let component: DateComponents = date1 - date2
print(component) //year: 0 month: 0 day: 0 hour: 1 minute: 59 second: 59 isLeapMonth: false

let interval: TimeInterval = date1 - date2
print(interval.seconds(to: .year)) //0.000228
print(interval.seconds(to: .day)) //0.0833
print(interval.seconds(to: .hour)) //1.99
print(interval.seconds(to: .minute)) //59.99
print(interval) //7199.99

Any thoughts or suggestions on what to do about months or this approach in general?

basememara commented 7 years ago

Not my favorite API but we can do something like this also:

extension Date {

    func minus(_ date: Date, to component: Calendar.Component) -> TimeInterval? {
        guard component != .month else {
            return Double(Calendar.current.dateComponents([.month], from: date, to: self).month!)
        }

        let seconds = timeIntervalSince(date)

        switch component {
        case .year:
            return seconds / 31536000
        case .day:
            return seconds.truncatingRemainder(dividingBy: 31536000) / 86400
        case .hour:
            return seconds.truncatingRemainder(dividingBy: 86400) / 3600
        case .minute:
            return seconds.truncatingRemainder(dividingBy: 3600) / 60
        case .second:
            return seconds
        default:
            return nil
        }
    }
}

=> date1.minus(date2, to: .month)
naoty commented 7 years ago

@basememara Thanks!

As you said, the conversion between DateComponents and TimeInterval will be possible if we assume that 1 month always has 30 days and DST can be ignored.

I have obsoleted this conversion before at https://github.com/naoty/Timepiece/pull/30. So, I don't want to merge implementation on incorrect assumption.

Timepiece should use Calendar API for calendrical calculation. Timepiece is a thin wrapper of Calendar API. So, it cannot provide any APIs which Calendar doesn't provide.

Your extension for Date is good because this implementations are thin wrappers of Calendar.