0

In reference to this old question

I am not sure why I have a remainder of one hour, when subtracting weeks and days from today's date.

dump(Date().xWeeks(-13).xDays(-2).elapsedDescription)

extension Date {

    /// Returns a new date that is 'x' number of days hence the recevier.
    public func xDays(_ x:Int) -> Date {
        return Calendar.current.date(byAdding: .day, value: x, to: self)!
    }

    /// Returns a new date that is 'x' number of weeks (of year) hence the recevier.
    public func xWeeks(_ x:Int) -> Date {
        return Calendar.current.date(byAdding: .weekOfYear, value: x, to: self)!
    }

    /// The count of hours hence the receiver. Today's date is established using the device clock.
    public func elapsedHours(toDate: Date) -> Int{
        return Calendar.current.dateComponents([.hour], from: self, to: toDate).hour!
    }

    /// The count of days hence the receiver. Today's date is established using the device clock.
    public func elapsedDays(toDate: Date) -> Int{
        return Calendar.current.dateComponents([.day], from: self, to: toDate).day!
    }

    /// The count of weeks hence the receiver. Today's date is established using the device clock.
    public func elapsedWeeks(toDate: Date) -> Int{
        return Calendar.current.dateComponents([.weekOfYear], from: self, to: toDate).weekOfYear!
    }

    public var elapsedDescription: String {
        let toDate = Date()
        let weekValue = elapsedWeeks(toDate: toDate) == 1 ? "week" : "weeks"
        if elapsedWeeks(toDate: toDate) > 0 {
            let dayRemainder = elapsedDays(toDate: toDate)-elapsedWeeks(toDate: toDate)*7
            if dayRemainder > 0 {
                let dayValue = dayRemainder == 1 ? "day" : "days"
                let remainingHours = elapsedHours(toDate: toDate)-elapsedWeeks(toDate: toDate)*7*24 - (dayRemainder*24)
                if remainingHours > 0 {
                    let hourValue = remainingHours == 1 ? "hour" : "hours"
                    return "\(elapsedWeeks(toDate: toDate)) \(weekValue), \(dayRemainder) \(dayValue) and \(remainingHours) \(hourValue)"
                } else {
                    return "\(elapsedWeeks(toDate: toDate)) \(weekValue) and \(dayRemainder) \(dayValue)"
                }
            } else {
                return "\(elapsedWeeks(toDate: toDate)) \(weekValue)"
            }
        } else if elapsedHours(toDate: toDate) > 0 {
            let hourValue = elapsedHours(toDate: toDate) == 1 ? "hour" : "hours"
            return "\(elapsedHours(toDate: toDate)) \(hourValue)"
        } else {
            return ""
        }
    }

}
Community
  • 1
  • 1
  • May be a timezone issue, in what timezone do you live? – luk2302 Nov 11 '16 at 15:57
  • ▿ Europe/London (current) - identifier: "Europe/London" - kind: "current" ▿ abbreviation: Optional("GMT") - some: "GMT" - secondsFromGMT: 0 - isDaylightSavingTime: false –  Nov 11 '16 at 15:58
  • Did Daylight Saving Time change in the last 13w2d? – Tim Nov 11 '16 at 15:59
  • I'm just trying to get my head around the reason daylight savings is a thing. This time calculation stuff gets deep, fast. –  Nov 11 '16 at 16:04
  • And timezones are just the tip of the iceberg. http://infiniteundo.com/post/25509354022/more-falsehoods-programmers-believe-about-time – Craig Siemens Nov 11 '16 at 16:18

1 Answers1

0

Your code assumes that a day has 24 hours, but that is not always the case. In regions with daylight saving time, a day can have 23 or 25 hours, when the clocks are adjusted forward or backward.

In London, DST ended on October 30, which means that that day had 25 hours. This explains the additional hour in your output.

The correct solution is simple: Just compute the difference in weeks, days, and hours in a single step:

let comps = Calendar.current.dateComponents([.weekOfYear, .day, .hour],
                                            from: self, to: toDate)

and then create the desired output string from

comps.weekOfYear!, comps.day!, comps.hour!

In your example, dump(comps) shows

▿ day: 6 hour: 0 weekOfYear: 13 isLeapMonth: false 
  - day: 6
  - hour: 0
  - weekOfYear: 13
  - isLeapMonth: false
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382