Categories
Career Programming

Programmer interview challenge 1, revisited once more: “Anagram” in Ruby and C#

Oh hey — why not one more? In the past three articles, I’ve been writing about a challenge that’s often given to programmers at a tech interview: Write a program that determines if two words are anagrams.

In the first article, I wrote a basic Python solution. In the second article, I refined the Python solution and then based a JavaScript solution on it. In the third article, I showed a couple of Swift implementations — one simple, one hardcore. In this article, I’ll present Ruby and C# solutions.

The Ruby version

I used to work with Ruby and Rails a fair bit, so I really should post a Ruby version:

def sortLetters(word)
   word.downcase.chars.sort.join.strip 
end

def anagram?(word1, word2)
   sortLetters(word1) == sortLetters(word2) 
end

# Let’s try it out
puts anagram?('iceman', 'cinema')     # true
puts anagram?('Florida', 'fail rod')  # true
puts anagram?('ABC', 'xyz')           # false

Some things to note:

  • Ruby doesn’t support nested methods. You can put a method inside another method in Ruby, but that doesn’t make it a nested method. That’s why I’ve defined sortLetters() and anagram?() as two separate methods.
  • I wrote these methods to reflect the preferred “delightfully terse” style that is preferred way in the Ruby world. InsortLetters(), and I skip the return statement (optional in Ruby; in its absence, a method returns the result of its last line), use method chaining, and skip any unnecessary parentheses, hence word.downcase.chars.sort.join.strip, which very clearly states that it does the following:
    • Convert the given string to all lowercase characters.
    • Split the resulting string into an array of characters.
    • Sort the array.
    • Turn the array back into a string.
    • Strip any leading and trailing strings from string. If the original string had any spaces in it, they would be leading spaces in the current string.
  • Following Ruby convention, I added a ? to the “anagram” name to make the name of the method anagram?(), which is the Ruby convention for methods that return boolean values. As with the versions of anagram()that I wrote in JavaScript, Python, and Swift, two words are anagrams of each other if their sorted forms are the same.

If you’re interested in trying the histogram approach, you may want to try out Ruby’s Enumerable#tally method:

'foo'.chars.tally   # results in {"f"=>1, "o"=>2}

The C# version

Here’s the C# version. It uses LINQ, because LINQ often makes life easier:

using System.Linq;

string sortLetters(string word) 
{
  return String.Concat(word.ToLower().OrderBy(c => c)).Trim();
}

static bool anagram(string word1, string word2) 
{
  return sortLetters(word1) == sortLetters(word2);
}

Previously, in the “Programmer interview challenge” series

Categories
Career Programming

Programmer interview challenge 1, revisited again: “Anagram” in Swift, the easy and hardcore way

In the past two articles, I’ve been writing about a challenge that’s often given to programmers at a tech interview: Write a program that determines if two words are anagrams.

In the first article, I wrote a basic Python solution. In the second article, I refined the Python solution and then based a JavaScript solution on it. In this article, I’ll present a couple of Swift solutions.

A simple Swift solution

Here’s a Swift solution that follows the same logic as the Python and JavaScript solutions I presented in the previous article:

func anagram(word1: String, word2: String) -> Bool { 

  func sortLetters(in word: String) -> String { 
    return String(word.lowercased().sorted()).trimmingCharacters(in: .whitespaces)
  } 

  return sortLetters(in: word1) == sortLetters(in: word2) 
}

The Swift version of anagram() takes the same approach as the Python and JavaScript version by making use of a function named sortLetters(in:) that sorts the letters of a string in place. Here’s what it does:

  1. It takes the given word — stored in the parameter word — and converts it to all lowercase letters using String’s lowercased() method.
  2. The all-lowercase word is converted into a sorted array of Characters by String’s sorted() method.
  3. The sorted array of Characters is converted back into a string using String’s initializer.
  4. Any leading whitespace in the string (the result of sorting a String with spaces in it) is removed with String’s trimmingCharacters(in:) method.

Once sortLetters(in:) is defined, all anagram() has to do is apply it to both words and see if the results are the same.

Jessy Catterwaul’s hardcore Swift solution

When something about Swift confuses me (it happens!) my go-to person is Jessy Catterwaul, video course creator at raywenderlich.com (where I’m an author). His approach to the anagram problem was to use histograms and bucketing — or in simpler terms, to use a data structure to store the counts of each letter in both words. If both words have the same number of the same letters, they’re anagrams of each other.

He sent me this code, which defines an extension for Swift’s Dictionary class. It gives Dictionary a new initializer method, init(bucketing:). If you provide this initializer with a string, it creates a dictionary whose keys are the letters in the string, and whose corresponding values are the counts of those letters. For example, if you provide the initializer with the string abbccc, it returns a the dictionary ["a": 1, "b": 2, "c": 3].

Here’s the code:

public extension Dictionary where Value == Int {
  /// Create “buckets” from a sequence of keys,
  /// such as might be used for a hsitogram.
  init<Keys: Sequence>(bucketing unbucketedKeys: Keys)
    where Keys.Element == Key {
      self.init(
        zip(unbucketedKeys, AnyIterator { 1 }),
        uniquingKeysWith: +
      )
    }
}

Once the extension is defined, the following code…

let mississippiLetterCount = Dictionary(bucketing: "Mississippi")
print("Mississippi letter count: \(mississippiLetterCount)")

…gives you this output:

Mississippi letter count: [“s”: 4, “i”: 4, “M”: 1, “p”: 2]

What’s up next

Next week, I’ll showcase another programming challenge that I’ve seen in technical interviews: The dreaded FizzBuzz! I’ll also devote some time to covering the “why” of programming challenges.

Previously, in the “Programmer interview challenge” series

Categories
Career Programming

Programmer interview challenge 1, revisited: Revising “Anagram” in Python and implementing it in JavaScript

In the previous article, I wrote about a challenge that’s often given to programmers at a tech interview: Write a program that determines if two words are anagrams. I posted a solution in Python; check it out here. In this article, we’ll refactor the Python code and write a JavaScript implementation.

Refactoring the Python version

Looking at the code for anagram(), it’s quite clear that it isn’t DRY (Don’t Repeat Yourself), but manifestly the opposite: WET (Write Everything Twice)!

Under the time constraints of a technical interview, you might not always have the time or cognitive bandwidth to keep your code DRY, but if you should try to do so if possible. You may find that it helps convey your algorithmic thinking more effectively to the interviewer, and that’s what you want. After all, your goal throughout the process is to prove that you can actually program.

The repeated code is the part that takes a string, sorts its characters into alphabetical order, and removes the leading space if it exists. Let’s turn that code into its own method:

def sortLetters(word):
  # Returns the given word with its letters sorted
  # into alphabetical order and with any
  # leading space removed.
  word_lowercase = word.lower()
  return ''.join(sorted(word_lowercase)).lstrip()

With this method defined, we can use it in anagram(). In fact, we can nest it within anagram(). Here’s the revision:

def anagram(first_word, second_word):
  # Returns True if the given words are made of the exact same characters,
  # ignoring capitalization and spaces.

  def sortLetters(word):
    # Returns the given word with its letters sorted
    # into alphabetical order and with any
    # leading space removed.
    word_lowercase = word.lower()
    return ''.join(sorted(word_lowercase)).lstrip()

  return sortLetters(first_word) == sortLetters(second_word)

Creating the sortLetters() method doesn’t just DRY up the code, but helps the method better convey what it does. Now, what anagram() does is very clearly conveyed by its return statement: it tells you if the first word with its letters sorted is the same as the second word with its letters sorted.

I confirmed that this refactored code works by running the tests, which show just how useful having tests is.

Implementing anagram() in JavaScript

Here’s anagram() in JavaScript:

function anagram(firstWord, secondWord) {
  
  function sortLetters(word) {
    return word
      .toLowerCase()
      .split('')
      .sort()
      .join('')
      .trim()
  }

  return sortLetters(firstWord) === sortLetters(secondWord)
}

Note that the JavaScript version of sortLetters() is structured slightly differently from the Python version. That’s because JavaScript’s sort() is an array method rather than a general function like Python’s sorted().

In the JavaScript version of sortLetters(), I use method chaining to spell out what happens to the given word step by step:

  1. Convert the word to lower case
  2. Convert that into an array of characters
  3. Sort that array
  4. Convert that array into a string
  5. Remove any trailing or leading whitespace

I could’ve written sortLetters() this way…

function sortLetters(word) {
  return word.toLowerCase().split('').sort().join('').trim()
}

…but I find that “put each method in the chain on its own line” approach more clearly conveys what I’m trying to do:

function sortLetters(word) {
  return word
    .toLowerCase()
    .split('')
    .sort()
    .join('')
    .trim()
}

Next: The Swift version!

Previously, in the “Programmer interview challenge” series

Categories
Career Programming

Programmer interview challenge 1: Anagram

An anagram is a word, phrase, or name that can be formed by rearranging the letters in another word, phrase, or name. For example, iceman is an anagram of cinema, and vice versa. Ignoring spaces and capitalizations, “Florida” is an anagram of “rod fail”.

“Anagram” is a common programming challenge that I’ve seen issued to prospective developers in technical interviews: Write a program or function that can tell if two given words are anagrams of each other. Here’s how you solve it.

The general idea

One solution to the problem is hinted at in the definition of “anagram”. Let’s look at it again:

An anagram is a word, phrase, or name that can be formed by rearranging the letters in another word, phrase, or name.

The word rearranging should be your hint. Somehow, the solution should involve rearranging the letters of both words so that you can compare them. Another word for rearranging is reordering, and when you encounter that word in programming, you should think of sorting. That’s where the solution lies:

If two words are anagrams of each other, sorting each word’s letters into alphabetical order should create two identical words. For example, if you sort the letters in cinema and iceman into alphabetical order, both will be turned into aceimn.

With that in mind, let’s try writing an anagram-detecting function. Given two strings, it should return true if they’re anagrams of each other, and false otherwise.

The Python version

This assumes that you’ve got Python and pytest installed on your system. I recommend installing the Individual Edition of the Anaconda Python distribution, followed by entering pip install pytest at the commend line.

Let’s do this the test-first way. We’ll write the test first, and then write the code. This means that we’ll want to create two files:

  • anagram.py, which will contain the actual code of the solution, and
  • test_anagram.py, which will contain test for that code.

Here’s the code for the test:

import pytest
from anagram import anagram

def test_simple_anagram():
  assert anagram('iceman', 'cinema'), "'cinema' is an anagram of 'iceman'."

…and here’s the code for the initial iteration of the anagram function:

def anagram(first_word, second_word):
  return False

To run the test, enter pytest at the command line. You should see output that looks like this:

Now that we have a failing test, let’s write code to make it pass.

Sorting the letters in a string is Python can be done by using a couple of methods:

  • sorted(), which when given a string, returns an array containing that string’s letters in ascending order. For example, sorted('cinema') returns ['a', 'c', 'e', 'i', 'm', 'n'].
  • join(), which when given a string and an array, returns a string where the elements of the array are joined by the given string. For example, '*'.join(['a', 'b', 'c']) returns 'a*b*c'.

Here’s my code:

def anagram(first_word, second_word):
  first_word_sorted = ''.join(sorted(first_word))
  second_word_sorted = ''.join(sorted(second_word))
  return first_word_sorted == second_word_sorted

Running pytest now gives a passing test:

Let’s now deal with cases with capitalization and spaces. Ideally, the anagram() method should treat “Florida” and “rod fail” as anagrams. We’ll specify this in the test:

import pytest
from anagram import anagram

def test_simple_anagram():
  assert anagram('iceman', 'cinema'), "'cinema' is an anagram of 'iceman'."

def test_complex_anagram():
  assert anagram('Florida', 'rod fail'), "'rod fail', if you ignore spaces and capitalization, is an anagram of 'Florida'."

Running pytest yields these results: 1 failed test and 1 passed test…

We can fix this through the use of another two methods:

  • lower(), which when applied to a string, converts all its letters to lowercase. For example, 'RADAR'.lower() returns 'radar'.
  • lstrip(), which when applied to a string, removes any whitespace characters from the left side. Since the space character has a lower value than any letter in the alphabet, it will always be the leftmost character in a string whose characters have been sorted into ascending order.

Here’s my revised code:

def anagram(first_word, second_word):
  first_word_lowercase = first_word.lower()
  first_word_sorted = ''.join(sorted(first_word_lowercase)).lstrip()
  second_word_lowercase = second_word.lower()
  second_word_sorted = ''.join(sorted(second_word_lowercase)).lstrip()
  return first_word_sorted == second_word_sorted

Running pytest now shows that all the tests pass:

Just to be safe, let’s add a test to make sure than anagram() returns False when given two strings that are not anagrams of each other:

import pytest
from anagram import anagram

def test_simple_anagram():
  assert anagram('iceman', 'cinema'), "'cinema' is an anagram of 'iceman'."

def test_complex_anagram():
  assert anagram('Florida', 'rod fail'), "'rod fail', if you ignore spaces and capitalization, is an anagram of 'Florida'."

def test_non_anagram():
  assert anagram('ABC', 'xyz') == False, "'ABC' and 'xyz' are not anagrams."

All test pass when pytest is run:

And trying all sorts of pairs of strings confirms what the test tells us: anagram() works!

# All of these return True
anagram('Oregon', 'no ogre')
anagram('North Dakota', 'drank a tooth')
anagram('Wisconsin', 'cows in sin')

# All of these return False
anagram('Florida', 'i oil sauna') # Try Louisiana
anagram('New York', 'on my wig') # Try Wyoming
anagram('Georgia', 'navy sin panel') # Try Pennsylvania

…and there you have it!

Next: Implementing anagram() in JavaScript, Swift, and possibly other languages.

Categories
Programming

A couple of handy Python methods that use regular expressions: “Word to initialism” and “Initialism to acronym”

Comic by xkcd. Tap to see the source.

tl;dr: Here’s the code

It’s nothing fancy — a couple of Python one-line methods:

  • word_to_initialism(), which converts a word into an initialism
  • initialism_to_acronym(), which turns an initialism into an acronym
import re

def word_to_initialism(word):
  """Turns every letter in a given word to an uppercase letter followed by a period.
  
  For example, it turns “goat” into “G.O.A.T.”.
  """
  return re.sub('([a-zA-Z])', '\\1.', word).upper()

def initialism_to_acronym(initialism):
  """Removes the period from an initialism, turning it into an acronym.

  For example, it turns “N.A.S.A.” into “NASA”.
  """
  return re.sub('\.', '', initialism)

The project and its dictionary

I’ve been working on a Python project that makes use of a JSON “dictionary” file of words or phrases and their definitions. Here’s a sample of the first few entries in the file, formatted nicely so that they’re a little more readable:

{
   "abandoned industrial site": [
      "Site that cannot be used for any purpose, being contaminated by pollutants."
   ],

   "abandoned vehicle": [
      "A vehicle that has been discarded in the environment, urban or otherwise, often found wrecked, destroyed, damaged or with a major component part stolen or missing."
   ],

   "abiotic factor": [
      "Physical, chemical and other non-living environmental factor."
   ],

   "access road": [
      "Any street or narrow stretch of paved surface that leads to a specific destination, such as a main highway."
   ],

   "access to the sea": [
      "The ability to bring goods to and from a port that is able to harbor sea faring vessels."
   ],

   "accident": [
      "An unexpected, unfortunate mishap, failure or loss with the potential for harming human life, property or the environment.",
      "An event that happens suddenly or by chance without an apparent cause."
   ],

   "accumulator": [
      "A rechargeable device for storing electrical energy in the form of chemical energy, consisting of one or more separate secondary cells.\\n(Source: CED)"
   ],

   "acidification": [
      "Addition of an acid to a solution until the pH falls below 7."
   ],

   "acidity": [
      "The state of being acid that is of being capable of transferring a hydrogen ion in solution."
   ],

   "acidity degree": [
      "The amount of acid present in a solution, often expressed in terms of pH."
   ],

   "acid rain": [
      "Rain having a pH less than 5.6."
   ],

   "acid": [
      "A compound capable of transferring a hydrogen ion in solution.",
      "Being harsh or corrosive in tone.",
      "Having an acid, sharp or tangy taste.",
      "A powerful hallucinogenic drug manufactured from lysergic acid.",
      "Having a pH less than 7, or being sour, or having the strength to neutralize  alkalis, or turning a litmus paper red."
   ],

...

}

The dictionary’s keys are strings that represent the words or phrases, while its values are arrays, where each element in that array is a definition for that word or phrase. To look up the meaning(s) of the word “acid,” you’d use the statement dictionary["acid"].

Dictionary keys are case-sensitive. For most words and phrases in the dictionary, that’s not a problem. Any entry in the dictionary that isn’t for a proper noun (the name of a person, place, organization, or the title of a work) has a completely lowercase key. It’s easy to massage a search term into lowercase with Python’s lower() method for strings.

Any entry in the dictionary that is for a proper noun is titlecased — that is, the first letter in each word is uppercase, and the remaining letters are lowercase. Once again, a search term can be massaged into titlecase in Python; that’s what thetitle()method for strings is for.

When looking up an entry in the dictionary, my application tries a reasonable set of variations on the search term:

  • As entered by the user (stripped of leading and trailing spaces, and sanitized)
  • Converted to lowercase with lower()
  • Converted to titlecase with title()
  • Converted to uppercase with upper()

For example, for the search term “FLorida” (the “FL” capitalization is an intentional typo), the program tries querying the dictionary using dictionary["FLorida"], dictionary["florida"], and dictionary["Florida"].

Looking up words or phrases made out of initials are a little more challenging because people spell them differently:

  • The Latin term for “after noon” — post meridiem — is spelled as pm, p.m., PM, and P.M.
  • Some people write the short form for “United States of America” as USA, while others write it as U.S.A.

To solve this problem, I wrote two short methods:

  • word_to_initialism(), which converts a word into an initialism
  • initialism_to_acronym(), which turns an initialism into an acronym

Here’s the code for both…

import re

def word_to_initialism(word):
  """Turns every letter in a given word to an uppercase letter followed by a period.
  
  For example, it turns “goat” into “G.O.A.T.”.
  """
  return re.sub('([a-zA-Z])', '\\1.', word).upper()

def initialism_to_acronym(initialism):
  """Removes the period from an initialism, turning it into an acronym.

  For example, it turns “N.A.S.A.” into “NASA”.
  """
  return re.sub('\.', '', initialism)

…and here are these methods in action:

# Outputs “G.O.A.T.”
print(f"word_to_initialism(): {word_to_initialism('goat')}")

# Outputs “RADAR”
print(f"initialism_to_acronym(): {initialism_to_acronym('R.A.D.A.R.')}")

Both use regular expressions. Here’s the regular expression statement that drivesword_to_initialism():

re.sub('([a-zA-Z])', '\\1.', word)

re.sub() is Python’s regular expression substitution method, and it takes three arguments:

  • The pattern to look for, which in this case is [a-zA-Z], which means “any alphabetical character in the given string, whether lowercase or uppercase”. Putting this in parentheses puts the pattern in a group.
  • The replacement, which in this case is \\1.. The \\1 specifies that the replacement will start with the contents of the first group, which is the detected alphabetical character. It’s followed by the string literal . (period), which means that a period will be added to the end of every alphabetical character in the given string.
  • The given string, in which the replacement is supposed to take place.

The regular expression behind initialism_to_acronym() is even simpler:

re.sub('\.', '', initialism)

In this method, re.sub() is given these arguments:

  • The pattern to look for, which in this case is \., which means “any period character”.
  • The replacement, which is the empty string.
  • The given string, in which the replacement is supposed to take place.
Categories
Current Events Programming

Android Studio 4 is available right now!

Android Studio 4.0 was released to the stable channel yesterday, which means that everyone — not just developers on the bleeding edge — can get their hands on it! You can download it here.

Want a video overview of what’s new? Here you go:

Among the goodies in this new version of the IDE are:

  • Motion editor: Lets you define your animations between MotionLayout states in a more Flash-like way, instead of having to spec them in XML, which is rather clunky.
  • New layout inspector: A much better way to see how the UI is laid out on your app’s screens.
  • Layout validation: You can now more easily see how your app will look on different screen sizes at different resolutions:
  • “Desugaring” extended to Java language APIs: Just as Android Gradle supported Java 8 language features for all API levels so that you can use lambdas and other modern Java goodies on Android projects that target old SDKs, it now supports Java language APIs. Until now, only the more recent SDKs supported APIs like java.util.stream, java.util.function and java.time.

Download Android Studio 4 here!

Categories
Programming

Dates and times in Swift 5, part 4: Adding Swift syntactic magic

Dates and times in Swift 5In this article, we’ll expand on material covered in the three previous articles in this series on working with dates and times in Swift 5:

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:

var timeInterval = DateComponents(
  month: 2,
  day: 3,
  hour: 4,
  minute: 5,
  second: 6
)
let futureDate = Calendar.current.date(byAdding: timeInterval, to: Date())!
print("2 months, 3 days, 4 hours, 5 minutes, and 6 seconds from now is \(futureDate.description(with: Locale(identifier: "en_US"))).")

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?

let coolerFutureDate = Date() + 2.months + 3.days + 4.hours + 5.minutes + 6.seconds
let coolerPastDate   = Date() - 2.months - 3.days - 4.hours - 5.minutes - 6.seconds

Or this code?

let coolerFutureDate = (2.months + 3.days + 4.hours + 5.minutes + 6.seconds).fromNow
let coolerPastDate   = (2.months + 3.days + 4.hours + 5.minutes + 6.seconds).ago

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:

func +(_ lhs: DateComponents, _ rhs: DateComponents) -> DateComponents {
  return combineComponents(lhs, rhs)
}

func -(_ lhs: DateComponents, _ rhs: DateComponents) -> DateComponents {
  return combineComponents(lhs, rhs, multiplier: -1)
}

func combineComponents(_ lhs: DateComponents,
                       _ rhs: DateComponents,
                       multiplier: Int = 1)
  -> DateComponents {
    var result = DateComponents()
    result.nanosecond = (lhs.nanosecond ?? 0) + (rhs.nanosecond ?? 0) * multiplier
    result.second     = (lhs.second     ?? 0) + (rhs.second     ?? 0) * multiplier
    result.minute     = (lhs.minute     ?? 0) + (rhs.minute     ?? 0) * multiplier
    result.hour       = (lhs.hour       ?? 0) + (rhs.hour       ?? 0) * multiplier
    result.day        = (lhs.day        ?? 0) + (rhs.day        ?? 0) * multiplier
    result.weekOfYear = (lhs.weekOfYear ?? 0) + (rhs.weekOfYear ?? 0) * multiplier
    result.month      = (lhs.month      ?? 0) + (rhs.month      ?? 0) * multiplier
    result.year       = (lhs.year       ?? 0) + (rhs.year       ?? 0) * multiplier
    return result
}

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:

// Let's define a couple of durations of time
// ------------------------------------------

var oneDayFiveHoursTenMinutes = DateComponents(
  day: 1,
  hour: 5,
  minute: 10
)
var threeDaysTenHoursThirtyMinutes = DateComponents(
  day: 3,
  hour: 10,
  minute: 30
)


// Now let's add and subtract them
// -------------------------------

let additionResult = oneDayFiveHoursTenMinutes + threeDaysTenHoursThirtyMinutes
print("1 day, 5 hours, and 10 minutes + 3 days, 10 hours, and 30 minutes equals:")
print("\(additionResult.day!) days, \(additionResult.hour!) hours, and \(additionResult.minute!) minutes.")

let subtractionResult = threeDaysTenHoursThirtyMinutes - oneDayFiveHoursTenMinutes
print("1 day, 5 hours, and 10 minutes - 3 days, 10 hours, and 30 minutes equals:")
print("\(subtractionResult.day!) days, \(subtractionResult.hour!) hours, and \(subtractionResult.minute!) minutes.")

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:

prefix func -(components: DateComponents) -> DateComponents {
  var result = DateComponents()
  if components.nanosecond != nil { result.nanosecond = -components.nanosecond! }
  if components.second     != nil { result.second     = -components.second! }
  if components.minute     != nil { result.minute     = -components.minute! }
  if components.hour       != nil { result.hour       = -components.hour! }
  if components.day        != nil { result.day        = -components.day! }
  if components.weekOfYear != nil { result.weekOfYear = -components.weekOfYear! }
  if components.month      != nil { result.month      = -components.month! }
  if components.year       != nil { result.year       = -components.year! }
  return result
}

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

let negativeTime = -oneDayFiveHoursTenMinutes
print("Negating 1 day, 5 hours, and 10 minutes turns it into:")
print("\(negativeTime.day!) days, \(negativeTime.hour!) hours, and \(negativeTime.minute!) minutes.")

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.
// Date + DateComponents
func +(_ lhs: Date, _ rhs: DateComponents) -> Date
{
  return Calendar.current.date(byAdding: rhs, to: lhs)!
}

// DateComponents + Dates
func +(_ lhs: DateComponents, _ rhs: Date) -> Date
{
  return rhs + lhs
}

// Date - DateComponents
func -(_ lhs: Date, _ rhs: DateComponents) -> Date
{
  return lhs + (-rhs)
}

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:

// What time will it be 1 day, 5 hours, and 10 minutes from now?
// -------------------------------------------------------------

// Here's the standard way of finding out:
let futureDate0 = Calendar.current.date(
  byAdding: oneDayFiveHoursTenMinutes,
  to: Date()
)

// With our overloads and function definitions, we can now do it this way:
let futureDate1 = Date() + oneDayFiveHoursTenMinutes
print("Date() + oneDayFiveHoursTenMinutes = \(futureDate1.description(with: Locale(identifier: "en_US")))")

// This will work as well:
let futureDate2 = oneDayFiveHoursTenMinutes + Date()
print("oneDayFiveHoursTenMinutes + Date() = \(futureDate2.description(with: Locale(identifier: "en_US")))")


// What time was it 3 days, 10 hours, and 30 minutes ago?
// ------------------------------------------------------

// Doing it the standard way takes some work
var minus3Days5Hours30minutes = threeDaysTenHoursThirtyMinutes
minus3Days5Hours30minutes.day = -threeDaysTenHoursThirtyMinutes.day!
minus3Days5Hours30minutes.hour = -threeDaysTenHoursThirtyMinutes.hour!
minus3Days5Hours30minutes.minute = -threeDaysTenHoursThirtyMinutes.minute!
let pastDate0 = Calendar.current.date(byAdding: minus3Days5Hours30minutes, to: Date())

// With our overloads and function definitions, it's so much easier:
let pastDate1 = Date() - threeDaysTenHoursThirtyMinutes
print("Date() - threeDaysTenHoursThirtyMinutes = \(pastDate1.description(with: Locale(identifier: "en_US")))")

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:

extension Date {

  init(year: Int,
       month: Int,
       day: Int,
       hour: Int = 0,
       minute: Int = 0,
       second: Int = 0,
       timeZone: TimeZone = TimeZone(abbreviation: "UTC")!) {
    var components = DateComponents()
    components.year = year
    components.month = month
    components.day = day
    components.hour = hour
    components.minute = minute
    components.second = second
    components.timeZone = timeZone
    self = Calendar.current.date(from: components)!
  }

  init(dateString: String) {
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd HH:mm:ss zz"
    self = formatter.date(from: dateString)!
  }

  var desc: String {
    get {
      let PREFERRED_LOCALE = "en_US" // Use whatever locale you prefer!
      return self.description(with: Locale(identifier: PREFERRED_LOCALE))
    }
  }

}

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

// The Stevenote where the original iPhone was announced took place
// on January 9, 2007 at 10:00 a.m. PST
let iPhoneStevenoteDate = Date(year: 2007,
                               month: 1,
                               day: 9,
                               hour: 10,
                               minute: 0,
                               second: 0,
                               timeZone: TimeZone(abbreviation: "PST")!)
print("iPhoneStevenoteDate: \(iPhoneStevenoteDate.desc)")

// The original iPhone went on sale on June 27, 2007
let iPhoneReleaseDate = Date(year: 2007, month: 6, day: 27) // June 27, 2007, 00:00:00 UTC
print("iPhoneReleaseDate: \(iPhoneReleaseDate.desc)")

// The Stevenote where the original iPad was announced took place
// on January 27, 2010 at 10:00 a.m. PST
let iPadStevenoteDate = Date(dateString: "2010-01-27 10:00:00 PST")
print("iPadStevenoteDate: \(iPadStevenoteDate.desc)")

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:

func -(_ lhs: Date, _ rhs: Date) -> DateComponents
{
  return Calendar.current.dateComponents(
    [.year, .month, .weekOfYear, .day, .hour, .minute, .second, .nanosecond],
    from: rhs,
    to: lhs)
}

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

let timeFromAnnouncementToRelease = iPhoneReleaseDate - iPhoneStevenoteDate
timeFromAnnouncementToRelease.year    // 0
timeFromAnnouncementToRelease.month   // 5
timeFromAnnouncementToRelease.day     // 17
timeFromAnnouncementToRelease.hour    // 7
timeFromAnnouncementToRelease.minute  // 0

// How long ago was the first moon landing, which took place
// on July 20, 1969, 20:18 UTC?
Date() - Date(dateString: "1969-07-20 20:18:00 UTC")
// At the time of writing, this value was a Date with the following properties:
// - year: 47 
// - month: 1 
// - day: 9 
// - hour: 22 
// - minute: 14

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:

extension Int {

  var second: DateComponents {
    var components = DateComponents()
    components.second = self;
    return components
  }
  
  var seconds: DateComponents {
    return self.second
  }
  
  var minute: DateComponents {
    var components = DateComponents()
    components.minute = self;
    return components
  }
  
  var minutes: DateComponents {
    return self.minute
  }
  
  var hour: DateComponents {
    var components = DateComponents()
    components.hour = self;
    return components
  }
  
  var hours: DateComponents {
    return self.hour
  }
  
  var day: DateComponents {
    var components = DateComponents()
    components.day = self;
    return components
  }
  
  var days: DateComponents {
    return self.day
  }
  
  var week: DateComponents {
    var components = DateComponents()
    components.weekOfYear = self;
    return components
  }
  
  var weeks: DateComponents {
    return self.week
  }
  
  var month: DateComponents {
    var components = DateComponents()
    components.month = self;
    return components
  }
  
  var months: DateComponents {
    return self.month
  }
  
  var year: DateComponents {
    var components = DateComponents()
    components.year = self;
    return components
  }
  
  var years: DateComponents {
    return self.year
  }
  
}

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):

// A quick test of some future dates
print("One hour from now is: \((Date() + 1.hour).desc)")
print("One day from now is: \((Date() + 1.day).desc)")
print("One week from now is: \((Date() + 1.week).desc)")
print("One month from now is: \((Date() + 1.month).desc)")
print("One year from now is: \((Date() + 1.year).desc)")

// What was the date 10 years, 9 months, 8 days, 7 hours, and 6 minutes ago?
let aLittleWhileBack = Date() - 10.years - 9.months - 8.days - 7.hours - 6.minutes
print("10 years, 9 months, 8 days, 7 hours, and 6 minutes ago, it was: \(aLittleWhileBack.desc)")

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:

extension DateComponents {
  
  var fromNow: Date {
    return Calendar.current.date(byAdding: self,
                                 to: Date())!
  }
  
  var ago: Date {
    return Calendar.current.date(byAdding: -self,
                                 to: Date())!
  }
  
}

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

// We’re now in Serious Syntax Magic Land!
// ---------------------------------------

print("2.weeks.fromNow: \(2.weeks.fromNow.desc)")
print("3.months.fromNow: \(3.months.fromNow.desc)")

let futureDate3 = (2.months + 3.days + 4.hours + 5.minutes + 6.seconds).fromNow
print("futureDate3: \(futureDate3.desc)")

let pastDate2 = (2.months + 3.days + 4.hours + 5.minutes + 6.seconds).ago
print("pastDate2: \(pastDate2.desc)")

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:

import UIKit

var timeInterval = DateComponents(
  month: 2,
  day: 3,
  hour: 4,
  minute: 5,
  second: 6
)
let futureDate = Calendar.current.date(byAdding: timeInterval, to: Date())!
print("2 months, 3 days, 4 hours, 5 minutes, and 6 seconds from now is \(futureDate.description(with: Locale(identifier: "en_US"))).")


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

func +(_ lhs: DateComponents, _ rhs: DateComponents) -> DateComponents {
  return combineComponents(lhs, rhs)
}

func -(_ lhs: DateComponents, _ rhs: DateComponents) -> DateComponents {
  return combineComponents(lhs, rhs, multiplier: -1)
}

func combineComponents(_ lhs: DateComponents,
                       _ rhs: DateComponents,
                       multiplier: Int = 1)
  -> DateComponents {
    var result = DateComponents()
    result.nanosecond = (lhs.nanosecond ?? 0) + (rhs.nanosecond ?? 0) * multiplier
    result.second     = (lhs.second     ?? 0) + (rhs.second     ?? 0) * multiplier
    result.minute     = (lhs.minute     ?? 0) + (rhs.minute     ?? 0) * multiplier
    result.hour       = (lhs.hour       ?? 0) + (rhs.hour       ?? 0) * multiplier
    result.day        = (lhs.day        ?? 0) + (rhs.day        ?? 0) * multiplier
    result.weekOfYear = (lhs.weekOfYear ?? 0) + (rhs.weekOfYear ?? 0) * multiplier
    result.month      = (lhs.month      ?? 0) + (rhs.month      ?? 0) * multiplier
    result.year       = (lhs.year       ?? 0) + (rhs.year       ?? 0) * multiplier
    return result
}


// Let's define a couple of durations of time
// ------------------------------------------

var oneDayFiveHoursTenMinutes = DateComponents(
  day: 1,
  hour: 5,
  minute: 10
)
var threeDaysTenHoursThirtyMinutes = DateComponents(
  day: 3,
  hour: 10,
  minute: 30
)


// Now let's add and subtract them
// -------------------------------

let additionResult = oneDayFiveHoursTenMinutes + threeDaysTenHoursThirtyMinutes
print("1 day, 5 hours, and 10 minutes + 3 days, 10 hours, and 30 minutes equals:")
print("\(additionResult.day!) days, \(additionResult.hour!) hours, and \(additionResult.minute!) minutes.")

let subtractionResult = threeDaysTenHoursThirtyMinutes - oneDayFiveHoursTenMinutes
print("1 day, 5 hours, and 10 minutes - 3 days, 10 hours, and 30 minutes equals:")
print("\(subtractionResult.day!) days, \(subtractionResult.hour!) hours, and \(subtractionResult.minute!) minutes.")


// Overloading - so that we can negate DateComponents
// --------------------------------------------------

// We'll need to overload unary - so we can negate components
prefix func -(components: DateComponents) -> DateComponents {
  var result = DateComponents()
  if components.nanosecond != nil { result.nanosecond = -components.nanosecond! }
  if components.second     != nil { result.second     = -components.second! }
  if components.minute     != nil { result.minute     = -components.minute! }
  if components.hour       != nil { result.hour       = -components.hour! }
  if components.day        != nil { result.day        = -components.day! }
  if components.weekOfYear != nil { result.weekOfYear = -components.weekOfYear! }
  if components.month      != nil { result.month      = -components.month! }
  if components.year       != nil { result.year       = -components.year! }
  return result
}


let negativeTime = -oneDayFiveHoursTenMinutes
print("Negating 1 day, 5 hours, and 10 minutes turns it into:")
print("\(negativeTime.day!) days, \(negativeTime.hour!) hours, and \(negativeTime.minute!) minutes.")


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

// Date + DateComponents
func +(_ lhs: Date, _ rhs: DateComponents) -> Date
{
  return Calendar.current.date(byAdding: rhs, to: lhs)!
}

// DateComponents + Dates
func +(_ lhs: DateComponents, _ rhs: Date) -> Date
{
  return rhs + lhs
}

// Date - DateComponents
func -(_ lhs: Date, _ rhs: DateComponents) -> Date
{
  return lhs + (-rhs)
}


// What time will it be 1 day, 5 hours, and 10 minutes from now?
// -------------------------------------------------------------

// Here's the standard way of finding out:
let futureDate0 = Calendar.current.date(
  byAdding: oneDayFiveHoursTenMinutes,
  to: Date()
)

// With our overloads and function definitions, we can now do it this way:
let futureDate1 = Date() + oneDayFiveHoursTenMinutes
print("Date() + oneDayFiveHoursTenMinutes = \(futureDate1.description(with: Locale(identifier: "en_US")))")

// This will work as well:
let futureDate2 = oneDayFiveHoursTenMinutes + Date()
print("oneDayFiveHoursTenMinutes + Date() = \(futureDate2.description(with: Locale(identifier: "en_US")))")


// What time was it 3 days, 10 hours, and 30 minutes ago?
// ------------------------------------------------------

// Doing it the standard way takes some work
var minus3Days5Hours30minutes = threeDaysTenHoursThirtyMinutes
minus3Days5Hours30minutes.day = -threeDaysTenHoursThirtyMinutes.day!
minus3Days5Hours30minutes.hour = -threeDaysTenHoursThirtyMinutes.hour!
minus3Days5Hours30minutes.minute = -threeDaysTenHoursThirtyMinutes.minute!
let pastDate0 = Calendar.current.date(byAdding: minus3Days5Hours30minutes, to: Date())

// With our overloads and function definitions, it's so much easier:
let pastDate1 = Date() - threeDaysTenHoursThirtyMinutes
print("Date() - threeDaysTenHoursThirtyMinutes = \(pastDate1.description(with: Locale(identifier: "en_US")))")


// Extending Date so that creating dates and debugging are simpler
// ===============================================================

extension Date {

  init(year: Int,
       month: Int,
       day: Int,
       hour: Int = 0,
       minute: Int = 0,
       second: Int = 0,
       timeZone: TimeZone = TimeZone(abbreviation: "UTC")!) {
    var components = DateComponents()
    components.year = year
    components.month = month
    components.day = day
    components.hour = hour
    components.minute = minute
    components.second = second
    components.timeZone = timeZone
    self = Calendar.current.date(from: components)!
  }

  init(dateString: String) {
    let formatter = DateFormatter()
    formatter.dateFormat = "yyyy-MM-dd HH:mm:ss zz"
    self = formatter.date(from: dateString)!
  }

  var desc: String {
    get {
      let PREFERRED_LOCALE = "en_US" // Use whatever locale you prefer!
      return self.description(with: Locale(identifier: PREFERRED_LOCALE))
    }
  }

}


// The Stevenote where the original iPhone was announced took place
// on January 9, 2007 at 10:00 a.m. PST
let iPhoneStevenoteDate = Date(year: 2007,
                               month: 1,
                               day: 9,
                               hour: 10,
                               minute: 0,
                               second: 0,
                               timeZone: TimeZone(abbreviation: "PST")!)
print("iPhoneStevenoteDate: \(iPhoneStevenoteDate.desc)")

// The original iPhone went on sale on June 27, 2007
let iPhoneReleaseDate = Date(year: 2007, month: 6, day: 27) // June 27, 2007, 00:00:00 UTC
print("iPhoneReleaseDate: \(iPhoneReleaseDate.desc)")

// The Stevenote where the original iPad was announced took place
// on January 27, 2010 at 10:00 a.m. PST
let iPadStevenoteDate = Date(dateString: "2010-01-27 10:00:00 PST")
print("iPadStevenoteDate: \(iPadStevenoteDate.desc)")


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

func -(_ lhs: Date, _ rhs: Date) -> DateComponents
{
  return Calendar.current.dateComponents(
    [.year, .month, .weekOfYear, .day, .hour, .minute, .second, .nanosecond],
    from: rhs,
    to: lhs)
}

// How long was it between the announcement of the original iPhone
// and its release in the stores?
let iPhoneWait = iPhoneReleaseDate - iPhoneStevenoteDate
print("The first iPhone users had to wait this long: ")
print("\(iPhoneWait.year!) years, " +
  "\(iPhoneWait.month!) months, " +
  "\(iPhoneWait.weekOfYear!) weeks, " +
  "\(iPhoneWait.day!) days, " +
  "\(iPhoneWait.hour!) hours, and " +
  "\(iPhoneWait.minute!) minutes.")

// How long ago was the first moon landing, which took place
// on July 20, 1969, 20:18 UTC?
let timeSinceMoonLanding = Date() - Date(dateString: "1969-07-20 20:18:00 UTC")
print("It’s been this long since the first moon landing: ")
print("\(timeSinceMoonLanding.year!) years, " +
  "\(timeSinceMoonLanding.month!) months, " +
  "\(timeSinceMoonLanding.weekOfYear!) weeks, " +
  "\(timeSinceMoonLanding.day!) days, " +
  "\(timeSinceMoonLanding.hour!) hours, and " +
  "\(timeSinceMoonLanding.minute!) minutes.")


// Extending Int to add some syntactic magic to date components
// ============================================================

extension Int {

  var second: DateComponents {
    var components = DateComponents()
    components.second = self;
    return components
  }

  var seconds: DateComponents {
    return self.second
  }

  var minute: DateComponents {
    var components = DateComponents()
    components.minute = self;
    return components
  }

  var minutes: DateComponents {
    return self.minute
  }

  var hour: DateComponents {
    var components = DateComponents()
    components.hour = self;
    return components
  }

  var hours: DateComponents {
    return self.hour
  }

  var day: DateComponents {
    var components = DateComponents()
    components.day = self;
    return components
  }

  var days: DateComponents {
    return self.day
  }

  var week: DateComponents {
    var components = DateComponents()
    components.weekOfYear = self;
    return components
  }

  var weeks: DateComponents {
    return self.week
  }

  var month: DateComponents {
    var components = DateComponents()
    components.month = self;
    return components
  }

  var months: DateComponents {
    return self.month
  }

  var year: DateComponents {
    var components = DateComponents()
    components.year = self;
    return components
  }

  var years: DateComponents {
    return self.year
  }

}


// A quick test of some future dates
print("One hour from now is: \((Date() + 1.hour).desc)")
print("One day from now is: \((Date() + 1.day).desc)")
print("One week from now is: \((Date() + 1.week).desc)")
print("One month from now is: \((Date() + 1.month).desc)")
print("One year from now is: \((Date() + 1.year).desc)")

// What was the date 10 years, 9 months, 8 days, 7 hours, and 6 minutes ago?
let aLittleWhileBack = Date() - 10.years - 9.months - 8.days - 7.hours - 6.minutes
print("10 years, 9 months, 8 days, 7 hours, and 6 minutes ago, it was: \(aLittleWhileBack.desc)")


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

extension DateComponents {

  var fromNow: Date {
    return Calendar.current.date(byAdding: self,
                                 to: Date())!
  }

  var ago: Date {
    return Calendar.current.date(byAdding: -self,
                                 to: Date())!
  }

}

// We’re now in Serious Syntax Magic Land!
// ---------------------------------------

print("2.weeks.fromNow: \(2.weeks.fromNow.desc)")
print("3.months.fromNow: \(3.months.fromNow.desc)")

let futureDate3 = (2.months + 3.days + 4.hours + 5.minutes + 6.seconds).fromNow
print("futureDate3: \(futureDate3.desc)")

let pastDate2 = (2.months + 3.days + 4.hours + 5.minutes + 6.seconds).ago
print("pastDate2: \(pastDate2.desc)")

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: