Categories
Programming

How to work with dates and times in Swift 5, part 4: Adding Swift syntactic magic

In this article, we’ll expand on material covered in the three previous articles in this series:

A more readable way to work with Dates and DateComponents

Suppose we want to find out what the date and time will be 2 months, 3 days, 4 hours, 5 minutes, and 6 seconds from now will be.

If you recall what we covered in the last installment in this series, you’d probably use code like this:

In the code above, we did the following:

  • We created an instance of a DateComponents struct.
  • We set its properties so that it would represent a time interval of 2 months, 3 days, 4 hours, 5 minutes, and 6 seconds.
  • We then used Calendar‘s date(byAdding:to:) method to add the time interval to a Date.

This code wouldn’t look out of place in a lot of other programming languages, but we can do better in Swift. What if I told you that by defining a few helper functions, you can turn the code above into the code below?

Or this code?

I’d much rather write the code above. This article will cover the code necessary to make this kind of syntactic magic possible.

Overloading + and - so that we can add and subtract DateComponents

First, let’s write some code that allows us to add and subtract DateComponents. Start a new playground and enter the following code into it:

In the code above, we’ve overloaded the + and - operators so that we can add and subtract DateComponents. I derived these functions from Axel Schlueter’s SwiftDateTimeExtensions library. He wrote them when Swift was still in beta; I updated them so that they compile with the current version and added a couple of tweaks of my own.

The addition and subtraction operations are so similar and so tedious, which is a sign that there’s an opportunity to DRY up the code. I factored out the duplicate code from both the + and - overloads and put it into its own method, combineComponents, which does the actual DateComponents addition and subtraction.

You may have noticed a lot of ?? operators in the code for combineComponents. ?? is referred to as the nil coalescing operator, and it’s a clever bit of syntactic shorthand. For the expression below:

let finalValue = someOptionalValue ?? fallbackValue

  • If someOptionalValue is not nil, finalValue is set to someOptionalValue‘s value.
  • If someOptionalValue is nil, finalValue is set to fallbackValue‘s value.

Let’s confirm that our new operator overloads work. Add the following to the playground and run it:

You should see the following output:

1 day, 5 hours, and 10 minutes + 3 days, 10 hours, and 30 minutes equals:
4 days, 15 hours, and 40 minutes.
1 day, 5 hours, and 10 minutes – 3 days, 10 hours, and 30 minutes equals:
2 days, 5 hours, and 20 minutes.

Overloading - so that we can negate DateComponents

Now that we can add and subtract DateComponents, let’s overload the unary minus so that we can negate DateComponents:

With this overload defined, we can now use the unary minus to negate DateComponents. Add the following to the playground and run it:

You should see the following output:

Negating 1 day, 5 hours, and 10 minutes turns it into:
-1 days, -5 hours, and -10 minutes.

Overloading + and - so that we can add Dates and DateComponents and subtract DateComponents from Dates

With the unary minus defined, we can now define the following operations:

  • Date + DateComponents, which makes it easier to do date arithmetic.
  • DateComponents + Date, which should be possible because addition is commutative (which is just a fancy way of saying that a + b and b + a should give you the same result).
  • Date - DateComponents, which once again makes it easier to do date arithmetic.

Note that we didn’t define an overload for calculating Date - DateComponents — such an operation doesn’t make any sense.

With these overloads defined, a lot of Date/DateComponents arithmetic in Swift becomes much easier to enter and read. Add the following to the playground and run it:

On my computer, the output looked like this:

Date() + oneDayFiveHoursTenMinutes = Friday, May 29, 2020 at 3:20:54 PM Eastern Daylight Time
oneDayFiveHoursTenMinutes + Date() = Friday, May 29, 2020 at 3:20:54 PM Eastern Daylight Time
Date() – threeDaysTenHoursThirtyMinutes = Sunday, May 24, 2020 at 11:40:54 PM Eastern Daylight Time

Extending Date so that creating dates and debugging are simpler

Creating Dates in Swift is a roundabout process. Usually, you end up creating them in one of two ways:

  • Instantiating a DateComponents struct and then using it to create a Date using Calendar‘s date(from:) method, or
  • Creating a String representation of the Date and then using it to create a Date using DateFormatter‘s date(from:) method.

Let’s simplify things by extending the Date struct with a couple of convenient init method overloads. Let’s also make it easier to print out the value of a Date for debugging.

Add the following to the playground:

With these methods, initializing Dates is a lot more simple. Add the following to the playground and run it:

On my computer, the output looked like this:

iPhoneStevenoteDate: Tuesday, January 9, 2007 at 1:00:00 PM Eastern Standard Time
iPhoneReleaseDate: Tuesday, June 26, 2007 at 8:00:00 PM Eastern Daylight Time
iPadStevenoteDate: Wednesday, January 27, 2010 at 1:00:00 PM Eastern Standard Time

Overloading - so that we can use it to find the difference between two Dates

When we’re trying to determine the time between two given Dates, what we’re doing is finding the difference between them. Wouldn’t it be nice if we could use the - operator to find the difference between Dates, just as we can use it to find the difference between numbers?

Let’s code an overload to do just that. Add the following to the playground:

Let’s test it in action. Add the following to the playground and run it:

On my computer, the output looked like this:

The first iPhone users had to wait this long:
0 years, 5 months, 2 weeks, 3 days, 7 hours, and 0 minutes.
It’s been this long since the first moon landing:
50 years, 10 months, 1 weeks, 0 days, 18 hours, and 22 minutes.

Extending Int to add some syntactic magic to date components

We’ve already got some syntactic niceties, but the real Swift magic happens when we add this code to the mix. Add the following to the playground:

This additions to Int allow us to convert Ints to DateComponents in an easy-to-read way, and with our overloads to add and subtract DateComponents to and from each other, and to add Dates to DateComponents, we can now perform all sorts of syntactic magic like this (add the following to the playground and run it):

On my computer, the output looked like this:

One hour from now is: Thursday, May 28, 2020 at 11:57:49 AM Eastern Daylight Time
One day from now is: Friday, May 29, 2020 at 10:57:49 AM Eastern Daylight Time
One week from now is: Thursday, June 4, 2020 at 10:57:49 AM Eastern Daylight Time
One month from now is: Sunday, June 28, 2020 at 10:57:49 AM Eastern Daylight Time
One year from now is: Friday, May 28, 2021 at 10:57:49 AM Eastern Daylight Time
10 years, 9 months, 8 days, 7 hours, and 6 minutes ago, it was: Thursday, August 20, 2009 at 3:51:49 AM Eastern Daylight Time

Extending DateComponents to add even more syntactic magic: fromNow and ago

And finally, a couple of additions to the DateComponents struct to make Date/DateComponent calculations even more concise and readable. Add these to the playground:

Let’s try them out! Add these to the playground and run them:

On my computer, the output looked like this:

2.weeks.fromNow: Thursday, June 11, 2020 at 11:03:36 AM Eastern Daylight Time
3.months.fromNow: Friday, August 28, 2020 at 11:03:36 AM Eastern Daylight Time
futureDate3: Friday, July 31, 2020 at 3:08:42 PM Eastern Daylight Time
pastDate2: Wednesday, March 25, 2020 at 6:58:30 AM Eastern Daylight Time

Wrapping it all up

Here’s the playground containing all the code we just worked with:

You can download the playground here (4KB, zipped Xcode playground file).

The How to work with dates and times in Swift 5 series

Here are the articles in this series:

Leave a Reply

Your email address will not be published. Required fields are marked *