Article summary
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.
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.