Swift Tool Belt, Part 2: Extending Date

The second item in my Swift Tool Belt is an extension on the Date class. The interface to the Date class in iOS is very powerful, but in my opinion, it is not very readable. Simple operations to add/remove time to a date can be several lines long. Many of my extensions help with the readability of the class.

I’ve created a Swift Playground to illustrate the extensions. You can download it and play with the date extension yourself.

The first part of the Playground sets up a date formatter, so we can specify a couple of starting dates to start modifying. The great thing about Playgrounds is that you can see the value of of every line of code that you type without having to print it out to a console.

Playground of Date Extensions

The Extension

Here you can see the code in the extension. I am not a big fan of the DateComponents interface on Date. It is very powerful–allowing you to modify any aspect of a date/time value–but it is not very readable.

Many of the functions have an inverse. For instance, addDays(5) has an inverse of daysAgo(5), which can come in handy depending on whether you want to add or remove days from a date.


extension Date {
    var firstDayOfWeek: Date {
        var beginningOfWeek = Date()
        var interval = TimeInterval()
        
        _ = Calendar.current.dateInterval(of: .weekOfYear, start: &beginningOfWeek, interval: &interval, for: self)
        return beginningOfWeek
    }
    
    func addWeeks(_ numWeeks: Int) -> Date {
        var components = DateComponents()
        components.weekOfYear = numWeeks
        
        return Calendar.current.date(byAdding: components, to: self)!
    }
    
    func weeksAgo(_ numWeeks: Int) -> Date {
        return addWeeks(-numWeeks)
    }
    
    func addDays(_ numDays: Int) -> Date {
        var components = DateComponents()
        components.day = numDays
        
        return Calendar.current.date(byAdding: components, to: self)!
    }
    
    func daysAgo(_ numDays: Int) -> Date {
        return addDays(-numDays)
    }
    
    func addHours(_ numHours: Int) -> Date {
        var components = DateComponents()
        components.hour = numHours
        
        return Calendar.current.date(byAdding: components, to: self)!
    }
    
    func hoursAgo(_ numHours: Int) -> Date {
        return addHours(-numHours)
    }
    
    func addMinutes(_ numMinutes: Double) -> Date {
        return self.addingTimeInterval(60 * numMinutes)
    }
    
    func minutesAgo(_ numMinutes: Double) -> Date {
        return addMinutes(-numMinutes)
    }
    
    var startOfDay: Date {
        return Calendar.current.startOfDay(for: self)
    }
    
    var endOfDay: Date {
        let cal = Calendar.current
        var components = DateComponents()
        components.day = 1
        return cal.date(byAdding: components, to: self.startOfDay)!.addingTimeInterval(-1)
    }
    
    var zeroBasedDayOfWeek: Int? {
        let comp = Calendar.current.component(.weekday, from: self)
        return comp - 1
    }
    
    func hoursFrom(_ date: Date) -> Double {
        return Double(Calendar.current.dateComponents([.hour], from: date, to: self).hour!)
    }
    
    func daysBetween(_ date: Date) -> Int {
        let calendar = Calendar.current
        let components = calendar.dateComponents([.day], from: self.startOfDay, to: date.startOfDay)
        
        return components.day!
    }
    
    var percentageOfDay: Double {
        let totalSeconds = self.endOfDay.timeIntervalSince(self.startOfDay) + 1
        let seconds = self.timeIntervalSince(self.startOfDay)
        let percentage = seconds / totalSeconds
        return max(min(percentage, 1.0), 0.0)
    }
    
    var numberOfWeeksInMonth: Int {
        let calendar = Calendar.current
        let weekRange = (calendar as NSCalendar).range(of: NSCalendar.Unit.weekOfYear, in: NSCalendar.Unit.month, for: self)
        
        return weekRange.length
    }
}

These are the Date extension methods that I usually start with, but by the end of a project, there are usually quite a few more, and every project is different.


Swift Tool Belt Series

  1. Adding a Border, Corner Radius, and Shadow to a UIView
  2. Extending Date
  3. Extending UILabel
  4. Extending UITableViewController
  5. Adding a Gradient UIButton
  6. Extending UIFont
  7. Extending UIBarButtonItem
  8. Extending UIButton with Background Color for State