Making Date strideable

As a developer, once you start dealing with dates you quickly realise that anyone who is comfortable working with them is truly a master of the Dark Arts. Timezones, formatting, comparing, incrementing, isolating individual components and a whole lot of other headaches abound.

Take comparing dates. A week spans seven days, I am 18 months younger than my older brother, the 100 years war lasted 116 years, somehow. Humans naturally handle these different ways of expressing time spans easily without too much reflection. Computers, not so much.

It would be nice to use the stride function to step from date to date to date, but something like

stride(from: Date(), to: Date().addingTimeInterval(100_000_000), by: 1)

doesn't make any sense. Striding from a beginning date to an end date is fine, but I'm specifying an increment of 1. One what? Nanosecond, day, year? Unfortunately, Date isn't equipped to be something you can stride. Let's change that.

Getting into our stride

Yeah, sorry about that.

In order for a type to conform to the Strideable protocol, it must have a Stride associated type. This Stride must conform to SignedNumber, which is basically the ability to subtract two signed numbers, or invert a number's sign from positive to negative and vice versa.

Initially I wanted like to make Date conform to Strideable and for Calendar.Component to be it's Stride, which would allow me to write

stride(from: Date(), to: Date().addingTimeInterval(100_000_000), by: .month)

This is fine, but there's a major drawback, I can stride by second, minute, hour, day, month etc which gives me a nice granularity in the stride, but I can't say I want to stride from January to December in two month hops. A simple integer stride gives me this option and I want to keep as close to that functionality as possible

stride(from: 0, to: 10, by: 1) // 0, 1, 2, 3, ...
stride(from: 0, to: 10, by: 2) // 0, 2, 4, 6, ...

To do that I'll create a simple enum that will have the number of days, months or years as an associated value, and use this enum as the Stride in my Strideable enabled Date.

I've added a calculated property to convert the stride type to a Calendar.Component, which will come in handy later.

In order for DateStride to be a true stride, it must conform to SignedNumber, which means also conforming to Comparable and ExpressibleByIntegerLiteral. It's mostly boilerplate, so I won't show it here, but all of the code is available as a gist.

With the ability to specify the kind of stride I want in place, now I can extend Date to conform to Strideable.

Again, some details have been left out, they aren't really important for this example, but they can be found on the gist.

The advanced(by:) method from Strideable is all we care about at the moment. The implementation takes the amount to advance the date and converts the DateStride to the Calendar.Component in order to specify which of the date's unit to increase.

This now gives us our strideable date.

let start = Date()
let end = start.addingTimeInterval(100_000_000)

stride(from: start, to: end, by: .day(1))   // Step every day
// Strides:
// 2017-02-25, 2017-02-26, 2017-02-27, 2017-02-28 etc

stride(from: start, to: end, by: .day(7))   // Step every week
// Strides:
// 2017-02-25, 2017-03-04, 2017-03-11, 2017-03-18 etc

stride(from: start, to: end, by: .month(3)) // Step every quarter
// Strides:
// 2017-02-25, 2017-05-25, 2017-08-25, 2017-11-25 etc