Categories
Current Events Tampa Bay

What’s happening in the Tampa Bay tech/entrepreneur/nerd scene (Week of Monday, June 1, 2020)

Creative Commons photo by Dhaval Jani. Tap the photo to see the source.

Greetings, Tampa Bay techies, entrepreneurs, and nerds! Welcome to the June 1, 2020 edition of the list! Here’s this week’s list of online-only events for techies, entrepreneurs, and nerds based in an around the Tampa Bay area. Keep an eye on this post; I update it when I hear about new events, it’s always changing. Stay safe, stay connected, and #MakeItTampaBay!

For the time being, I’m limiting this list to online events. I know that there’s a lot of frustration about staying at home and staying six feet apart from people outside your own “house bubble,” but rather than follow the advice of armchair epidemiologists and assorted online “Monday morning quarterbacks”, I’m going with the public health official whom I trust the most: my sister, Dr. Eileen deVilla, Medical Officer for Health for Toronto (here’s an interview with her from Friday, May 29).

Monday, June 1

Tuesday, June 2

Wednesday, June 3

Thursday, June 4

Friday, June 5

Saturday, June 6

Sunday, June 7

There aren’t any tech, entrepreneur, or nerd events scheduled…yet!

Do you have any events or announcements that you’d like to see on this list?

Let me know at joey@joeydevilla.com!

Join the mailing list!

If you’d like to get this list in your email inbox every week, enter your email address below. You’ll only be emailed once a week, and the email will contain this list, plus links to any interesting news, upcoming events, and tech articles.

Join the Tampa Bay Tech Events list and always be informed of what’s coming up in Tampa Bay!


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:

Categories
Programming

Dates and times in Swift 5, part 3: Date arithmetic

abacus with toy clock
You can actually buy this thing on Etsy! Tap the photo for details.

What we’ve covered so far, and what we’ll cover in this installment

Dates and times in Swift 5So far, in this series on programming with dates and times in Swift 5, we’ve looked at:

With this knowledge under our belts, let’s get to this article’s topic: doing date calculations.

Creating a couple of Dates to work with

stevenotes

Let’s create a couple of Dates to work with:

  • The date and time of the Stevenote where the iPhone was introduced: January 9, 2007, 10:00 a.m. Pacific time (UTC-8), and
  • The date and time of the Stevenote where the iPad was introduced: January 27, 2010, 10:00 a.m. Pacific time (UTC-8).

Start with a fresh playground, and paste or enter the following code into it:

In the code above, we’ve created our dates in two different ways:

  • We created iPhoneStevenoteDate by setting up a DateComponents struct and then using the user’s Calendar to convert those DateComponents into a Date.
  • We created iPadStevenoteDate by converting its String representation into a Date using a DateFormatter.

Date comparisons, part 1

A chick looking at an egg.

Now that we have two Dates, let’s compare them. In Swift 5, we can use familiar comparison operators — <<===!=>>== — to tell which Date came first, or if they represent the exact (and I do mean exact) same point in time.

Add the following code to the playground and run it:

print("Did the iPhone Stevenote come BEFORE the iPad Stevenote? " +
      "\(iPhoneStevenoteDate < iPadStevenoteDate)")

print("Did the iPhone Stevenote come AFTER the iPad Stevenote? " +
      "\(iPhoneStevenoteDate > iPadStevenoteDate)")

print("Did the iPad Stevenote come BEFORE the iPhone Stevenote? " +
      "\(iPadStevenoteDate < iPhoneStevenoteDate)")

print("Does the iPad Stevenote come AFTER the iPhone Stevenote? " +
      "\(iPadStevenoteDate > iPhoneStevenoteDate)")

print("Do the iPhone Stevenote and the iPad Stevenote fall on the EXACT SAME date and time? " +
      "\(iPhoneStevenoteDate == iPadStevenoteDate)")

print("Do the iPhone Stevenote and the iPad Stevenote fall on different dates and times? " +
      "\(iPhoneStevenoteDate != iPadStevenoteDate)")

The output should be:

Did the iPhone Stevenote come BEFORE the iPad Stevenote? true
Did the iPhone Stevenote come AFTER the iPad Stevenote? false
Did the iPad Stevenote come BEFORE the iPhone Stevenote? false
Does the iPad Stevenote come AFTER the iPhone Stevenote? true
Do the iPhone Stevenote and the iPad Stevenote fall on the EXACT SAME date and time? false
Do the iPhone Stevenote and the iPad Stevenote fall on different dates and times? true

Note that these are comparisons of Dates, which measure time down to the nearest nanosecond. If you compare two Dates named date1 and date2, where date2 represents a point in time one nanosecond after date1, they will not be equal; date2 will be greater than date1.

A little later on in this article, we’ll look at more “human” ways of comparing Dates.

How far apart are the iPhone and iPad Stevenotes, part 1: In seconds, using Date’s timeIntervalSince() method

Date‘s timeIntervalSince method can give us the difference between two dates and times — in seconds.

Add the following code to the playground and run it:

print("Number of seconds between the iPhone Stevenote and the iPad Stevenote: " +
      "\(iPhoneStevenoteDate.timeIntervalSince(iPadStevenoteDate))")

print("Number of seconds between the iPad Stevenote and the iPhone Stevenote: " +
      "\(iPadStevenoteDate.timeIntervalSince(iPhoneStevenoteDate))")

The output should be:

Number of seconds between the iPhone Stevenote and the iPad Stevenote: -96249600.0
Number of seconds between the iPad Stevenote and the iPhone Stevenote: 96249600.0

The results tell us that there were 96,248,600 seconds between the iPhone Stevenote and the iPad Stevenote.

While there are cases when you’ll want to know how many seconds there are between two given points in time, there are also many cases where you’ll want to find the differences between two points in time using other units, such as days, weeks, months, and years, not to mention hours and minutes. Date‘s timeIntervalSince method isn’t going to work for these cases.

How far apart are the iPhone and iPad Stevenotes, part 2: In days, using Calendar’s dateComponents(_:from:to:) method

Most of the time, when you are calculating how far apart two given Dates are, you’ll be using this method of the Calendar struct:

dateComponents(components, from: startDate, to: endDate)

Here’s a run-down of its parameters:

Parameter Description
components Set (expressed in array notation) of Calendar.Component values specifying the time units you want, which can be:

  • .second
  • .minute
  • .hour
  • .day
  • .month
  • .year
startDate: The start Date of the time period.
endDate: The end Date of the time period.

Let’s use dateComponents(_:from:to:) to find out how many days there were between the iPhone Stevenote and the iPad Stevenote.

Add the following code to the playground and run it:

let daysBetweenStevenotes = userCalendar.dateComponents([.day],
                                                        from: iPhoneStevenoteDate,
                                                        to: iPadStevenoteDate)
print("There were \(daysBetweenStevenotes.day!) days between the iPhone Stevenote of 2007 and the iPad Stevenote of 2010.")

The output should be:

There were 1114 days between the iPhone Stevenote of 2007 and the iPad Stevenote of 2010.

In the code above, we passed dateComponents(_:from:to:) three values:

  • An array containing the Calendar.Component value .day, which specifies that we want the result expressed as the difference between iPadStevenoteDate and iPhoneStevenoteDate in terms of days.
  • The two dates in question, iPhoneStevenoteDate and iPadStevenoteDate.

As the result tells us, there were 1,114 days between the iPhone Stevenote and the iPad Stevenote.

How far apart are the iPhone and iPad Stevenotes, part 3: In weeks

By changing the contents of the array of Calendar.Component values that we provide in the first argument of Calendar’s dateComponents(_:from:to:) method, we can get the result expressed in different time units.

Add the following code to the playground and run it:

let weeksBetweenStevenotes = userCalendar.dateComponents([.weekOfYear],
                                                         from: iPhoneStevenoteDate,
                                                         to: iPadStevenoteDate)
print("There were \(weeksBetweenStevenotes.weekOfYear!) weeks between the iPhone Stevenote of 2007 and the iPad Stevenote of 2010.")

The output should be:

There were 159 weeks between the iPhone Stevenote of 2007 and the iPad Stevenote of 2010.

In the code above, we passed dateComponents(_:from:to:) three values:

  • An array containing the Calendar.Component value .weekOfYear, which specifies that we want the result expressed as the difference between iPadStevenoteDate and iPhoneStevenoteDate in terms of the numbered weeks of the year on which both dates fall. For example, if event1 took place on week 2 of a year and event2 took place on week 5, the difference between the two in .weekOfYear terms would be 3.
  • The two dates in question, iPhoneStevenoteDate and iPadStevenoteDate.

The result indicates that 159 weeks passed between the iPhone Stevenote and the iPad Stevenote.

If you do the math, 159 times 7 days is 1,113 days, but our previous calculation said that the iPhone Stevenote and the iPad Stevenote were 1,114 days apart. That’s because the two events are 159 whole weeks apart, plus an extra day.

How far apart are the iPhone and iPad Stevenotes, part 4: In years, months, and days

We can also put multiple values of Calendar.Component into the array that we provide as the first argument of Calendar’s dateComponents(_:from:to:) method.

Add the following code to the playground and run it:

let yearsMonthsDaysHoursMinutesBetweenStevenotes = userCalendar.dateComponents(
  [.year, .month, .day, .hour, .minute],
  from: iPhoneStevenoteDate,
  to: iPadStevenoteDate
)
let years = yearsMonthsDaysHoursMinutesBetweenStevenotes.year!
let months = yearsMonthsDaysHoursMinutesBetweenStevenotes.month!
let days = yearsMonthsDaysHoursMinutesBetweenStevenotes.day!
let hours = yearsMonthsDaysHoursMinutesBetweenStevenotes.hour!
let minutes = yearsMonthsDaysHoursMinutesBetweenStevenotes.minute!
print("There were \(years) years, \(months) months, \(days) days, \(hours) hours, and \(minutes) minutes between the the iPhone Stevenote of 2007 and the iPad Stevenote of 2010.")

In the code above, we passed dateComponents(_:from:to:) three values:

  • An array containing the Calendar.Component values .year, .month, .day, .hour, .minute, which specifies that we want the result expressed as the difference between iPadStevenoteDate and iPhoneStevenoteDate in terms of years, months, days, hours, and minutes. The method uses the largest applicable component before using smaller ones — for example, it will give results like 1 month and 5 days rather than 35 days.
  • The two dates in question, iPhoneStevenoteDate and iPadStevenoteDate.

The results show that the iPhone Stevenote and the iPad Stevenote were 3 years and 18 days apart.

Date addition, part 1: What’s the last day of a 90-day warranty that starts today?

90-day-warranty

Now that we know how to answer the question “What’s the difference in time between two Dates?”, let’s try answering a different question: “If we add a time interval to a Date, what’s the resulting Date?”

To answer this question, we’ll use this method of Calendar:

date(byAdding: timeInterval, value: numberOfTimeUnits to: startDate)

Here’s a run-down of its parameters:

Parameter Description
timeInterval dateComponents struct whose properties contain values defining the interval of time.
numberOfTimeUnits The number of timeInterval units to be added to the Date in question.
startDate The Date in question.

Let’s start with a simple bit of code that tells us the last day of a 90-day warranty whose term starts right now:

// What's the last day of a 90-day warranty that starts today?
let lastDay = userCalendar.date(byAdding: .day, value: 90, to: Date())!
print("90 days from now is: \(lastDay.description(with: Locale(identifier: "en_US")))")

The result is a Date representing a point in time 90 days from the present. On my computer, the output looked like this:

90 days from now is: Optional(“Tuesday, August 25, 2020 at 10:30:46 PM Eastern Daylight Time”)

Date addition, part 2: What was the date 5 weeks ago?

Just as we can convert addition to subtraction by adding a negative value, we can also do Date subtraction by providing date(byAdding:value:to:) with negative values. Here’s an example of code that returns a date that is an interval of time prior to the date in question:

// What was the date 5 weeks ago?
let fiveWeeksAgo = userCalendar.date(byAdding: .weekOfYear, value: -5, to: Date())!
print("5 weeks ago was: \(fiveWeeksAgo.description(with: Locale(identifier: "en_US")))")

The result is a Date representing a point in time 5 weeks in the past. On my computer, the output looked like this:

5 weeks ago was: Wednesday, April 22, 2020 at 11:12:40 PM Eastern Daylight Time

Date addition, part 3: What time will it be 4 hours and 30 minutes from now, and 4 hours and 30 minutes ago?

The date(byAdding:value:to:) method works when you just want to add one kind of time unit — a minute, hour, day, week, month, or year — to a Date. If you want to add multiple kinds of time units to a Date, such as 4 hours and 30 minutes, you need to use this Calendar method instead:

date(byAdding: timeIntervalComponents, to: startDate)

Here’s a run-down of its parameters:

Parameter Description
timeIntervalComponents dateComponents struct whose properties contain values defining the interval of time.
startDate The Date in question.

Here’s the code that answers the question “What time will it be 4 hours and 30 minutes from now?”

// What time will it be 4 hours and 30 minutes from now?
// First, we need to define a DateComponents struct representing
// a time interval of 4 hours and 30 minutes
var fourHoursThirtyMinutes = DateComponents()
fourHoursThirtyMinutes.hour = 4
fourHoursThirtyMinutes.minute = 30

// Now add the interval to the Date
let fourHoursThirtyMinutesFromNow = userCalendar.date(
  byAdding: fourHoursThirtyMinutes,
  to: Date()
)!
print("4 hours and 30 minutes from now will be: \(fourHoursThirtyMinutesFromNow.description(with: Locale(identifier: "en_US")))")

In the code above, we did the following:

  • First, we defined a DateComponents struct representing a 4-hour, 30-minute span of time,
  • then we added that span of time to the present date and time using the date(byAdding:to:) method.

The result is a Date representing a time 4 hours and 30 seconds in the future.

Let’s find out what the Date was 4 hours and 30 seconds ago:

// What time was it 4 hours and 30 minutes ago?
var minusFourHoursThirtyMinutes = DateComponents()
minusFourHoursThirtyMinutes.hour = -4
minusFourHoursThirtyMinutes.minute = -30
let fourHoursThirtyMinutesAgo = userCalendar.date(
  byAdding: fourHoursThirtyMinutes,
  to: Date()
)!
print("4 hours and 30 minutes ago was: \(fourHoursThirtyMinutesAgo.description(with: Locale(identifier: "en_US")))")

On my computer, the output looked like this:

4 hours and 30 minutes from now will be: Thursday, May 28, 2020 at 3:42:40 AM Eastern Daylight Time
4 hours and 30 minutes ago was: Thursday, May 28, 2020 at 3:42:40 AM Eastern Daylight Time

Date comparisons, part 2: Making Date comparisons a little more “human”

One recurring theme in science fiction (and especially in Star Trek) is the tendency for ultra-smart characters and computers to be overly, needlessly, pointlessly precise. The writers for the original series often did this with Spock, and it seemed that at least a few writers were aware of this annoying trope in later series. Here’s a bit of dialogue from The Next Generation:

Data: 6 days, 13 hours, 47 minutes.
Riker: What, no seconds?
Data: I have discovered, sir, a certain level of impatience when I calculate a lengthy time interval to the nearest second. [beat] However if you wish…
Riker: No. No. Minutes is fine.

Date‘s comparison operators have the same problem with being overly precise.

Consider the following Dates related to the announcement of SwiftUI:

  • The start of the announcement, 2 hours and 8 minutes into the WWDC 2019 keynote: June 3, 2019, 12:08:00 p.m. PDT
  • One second after the start of the announcement: June 3, 2019, 12:09:00 p.m. PDT
  • Five minutes after the start of the announcement: June 3, 2019, 12:13:00 p.m. PDT
  • Three hours after the start of the announcement: June 3, 2019, 03:08:00 p.m. PDT

Date‘s comparison operators think of all these points in time as very different, but depending on your circumstances you may think of them as being practically the same:

  • In most cases, there really isn’t a difference between the time when SwiftUI was announced and one second after.
  • If you’re concerned only with the day when SwiftUI was announced and not the exact time, there’s effectively no difference between any of the Dates listed above.

Calendar‘s compare(_:to:toGranularity) method allows us to perform Date comparisons at different levels of granularity:

compare(firstDate, to: secondDate, toGranularity: granularity)

Here’s a run-down of its parameters:

Parameter Description
firstDate The first Date in the comparison.
secondDate The second Date in the comparison.
granularity The level of precision for the comparison, expressed as an Calendar.Component value, which includes:

  • .second
  • .minute
  • .hour
  • .day
  • .month
  • .year

This is a Cocoa method named “compare”, so you’ve probably guessed that its return type is ComparisonResult. Here’s what it returns:

If… compare returns:
firstDate is earlier than secondDate, when compared at the specified granularity .orderedAscending
firstDate is equal to secondDate, when compared at the specified granularity .orderedSame
firstDate is later than secondDate, when compared at the specified granularity .orderedDescending

It’s easier to show compare(_:to:toGranularity) in action than to explain how it works. Add the following code into the playground:

// Let's define some Dates relative to the SwiftUI announcement
// (June 3, 2019, 12:08 p.m. PDT)
let swiftUIAnnouncementDateComponents = DateComponents(
  timeZone: TimeZone(abbreviation: "PDT"),
  year: 2019,
  month: 6,
  day: 3,
  hour: 12,
  minute: 8
)
let swiftUIAnnouncement = userCalendar.date(from: swiftUIAnnouncementDateComponents)!

let swiftUIAnnouncementPlusOneSecond = userCalendar.date(
  byAdding: .second,
  value: 1,
  to: swiftUIAnnouncement
)!
let swiftUIAnnouncementPlusFiveMinutes = userCalendar.date(
  byAdding: .minute,
  value: 5,
  to: swiftUIAnnouncement
)!
let swiftUIAnnouncementPlusThreeHours = userCalendar.date(
  byAdding: .hour,
  value: 3,
  to: swiftUIAnnouncement
)!

// This returns false, because when measuring time at the granularity of a SECOND,
// swiftUIAnnouncement happens BEFORE swiftUIAnnouncementPlusOneSecond.
let test1 = userCalendar.compare(swiftUIAnnouncement,
                                 to: swiftUIAnnouncementPlusOneSecond,
                                 toGranularity: .second)
  == .orderedSame
print("test1: \(test1)")

// This returns true, because when measuring time at the granularity of a SECOND,
// swiftUIAnnouncement happens BEFORE swiftUIAnnouncementPlusOneSecond.
let test2 = userCalendar.compare(swiftUIAnnouncement,
                                 to: swiftUIAnnouncementPlusOneSecond,
                                 toGranularity: .second)
  == .orderedAscending
print("test2: \(test2)")

// This returns true, because when measuring time at the granularity of a MINUTE,
// swiftUIAnnouncement happens AT THE SAME TIME AS swiftUIAnnouncementPlusOneSecond.
let test3 = userCalendar.compare(swiftUIAnnouncement,
                                 to: swiftUIAnnouncementPlusOneSecond,
                                 toGranularity: .minute)
  == .orderedSame
print("test3: \(test3)")

// This returns true, because when measuring time at the granularity of an HOUR,
// swiftUIAnnouncement happens AT THE SAME TIME AS swiftUIAnnouncementPlusFiveMinutes.
let test4 = userCalendar.compare(swiftUIAnnouncement,
                                 to: swiftUIAnnouncementPlusFiveMinutes,
                                 toGranularity: .hour)
  == .orderedSame
print("test4: \(test4)")

// This returns true, because when measuring time at the granularity of a MINUTE,
// swiftUIAnnouncementPlusFiveMinutes happens AFTER swiftUIAnnouncement.
let test5 = userCalendar.compare(swiftUIAnnouncementPlusFiveMinutes,
                                 to: swiftUIAnnouncement,
                                 toGranularity: .minute)
  == .orderedDescending
print("test5: \(test5)")

// This returns true, because when measuring time at the granularity of a DAY,
// swiftUIAnnouncement happens AT THE SAME TIME AS swiftUIAnnouncementPlusThreeHours.
let test6 = userCalendar.compare(swiftUIAnnouncement,
                                 to: swiftUIAnnouncementPlusThreeHours,
                                 toGranularity: .day)
  == .orderedSame
print("test6: \(test6)")

The output should be:

test1: false
test2: true
test3: true
test4: true
test5: true
test6: true

Wrapping it all up

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

import UIKit


// Creating a couple of Dates to work with
// =======================================

// The user's calendar incorporates the user's locale and
// time zone settings, which means it's the one you'll use
// most often.
let userCalendar = Calendar.current

// Let's create a Date for the start of the Stevenote
// where the iPhone was introduced (January 9, 2007, 10:00:00 Pacific time)
// using DateComponents.
let iPhoneStevenoteDateComponents = DateComponents(
  timeZone: TimeZone(abbreviation: "PST"),
  year: 2007,
  month: 1,
  day: 9,
  hour: 10
)
let iPhoneStevenoteDate = userCalendar.date(from: iPhoneStevenoteDateComponents)!


// Date comparisons, part 1
// ========================

// Let's create a Date for the start of the Stevenote
// where the iPad was introduced (January 27, 2010, 10:00:00 Pacific time)
// using DateFormatter.
var dateMakerFormatter = DateFormatter()
dateMakerFormatter.calendar = userCalendar
dateMakerFormatter.dateFormat = "MMM d, yyyy, hh:mm a zz"
let iPadStevenoteDate = dateMakerFormatter.date(from: "Jan 27, 2010, 10:00 AM PST")!


print("Did the iPhone Stevenote come BEFORE the iPad Stevenote? " +
      "\(iPhoneStevenoteDate < iPadStevenoteDate)")

print("Did the iPhone Stevenote come AFTER the iPad Stevenote? " +
      "\(iPhoneStevenoteDate > iPadStevenoteDate)")

print("Did the iPad Stevenote come BEFORE the iPhone Stevenote? " +
      "\(iPadStevenoteDate < iPhoneStevenoteDate)")

print("Does the iPad Stevenote come AFTER the iPhone Stevenote? " +
      "\(iPadStevenoteDate > iPhoneStevenoteDate)")

print("Do the iPhone Stevenote and the iPad Stevenote fall on the EXACT SAME date and time? " +
      "\(iPhoneStevenoteDate == iPadStevenoteDate)")

print("Do the iPhone Stevenote and the iPad Stevenote fall on different dates and times? " +
      "\(iPhoneStevenoteDate != iPadStevenoteDate)")


// How far apart are the iPhone and iPad Stevenotes, part 1: In seconds,
// using Date’s timeIntervalSince() method
// =====================================================================

print("Number of seconds between the iPhone Stevenote and the iPad Stevenote: " +
      "\(iPhoneStevenoteDate.timeIntervalSince(iPadStevenoteDate))")

print("Number of seconds between the iPad Stevenote and the iPhone Stevenote: " +
      "\(iPadStevenoteDate.timeIntervalSince(iPhoneStevenoteDate))")


// How far apart are the iPhone and iPad Stevenotes, part 2:
// In days, using Calendar’s dateComponents(_:from:to:) method
// ===========================================================

let daysBetweenStevenotes = userCalendar.dateComponents([.day],
                                                        from: iPhoneStevenoteDate,
                                                        to: iPadStevenoteDate)
print("There were \(daysBetweenStevenotes.day!) days between the iPhone Stevenote of 2007 and the iPad Stevenote of 2010.")


// How far apart are the iPhone and iPad Stevenotes, part 3: In weeks
// ==================================================================

let weeksBetweenStevenotes = userCalendar.dateComponents([.weekOfYear],
                                                         from: iPhoneStevenoteDate,
                                                         to: iPadStevenoteDate)
print("There were \(weeksBetweenStevenotes.weekOfYear!) weeks between the iPhone Stevenote of 2007 and the iPad Stevenote of 2010.")


// How far apart are the iPhone and iPad Stevenotes, part 4:
// In years, months, and days
// =========================================================

let yearsMonthsDaysHoursMinutesBetweenStevenotes = userCalendar.dateComponents(
  [.year, .month, .day, .hour, .minute],
  from: iPhoneStevenoteDate,
  to: iPadStevenoteDate
)
let years = yearsMonthsDaysHoursMinutesBetweenStevenotes.year!
let months = yearsMonthsDaysHoursMinutesBetweenStevenotes.month!
let days = yearsMonthsDaysHoursMinutesBetweenStevenotes.day!
let hours = yearsMonthsDaysHoursMinutesBetweenStevenotes.hour!
let minutes = yearsMonthsDaysHoursMinutesBetweenStevenotes.minute!
print("There were \(years) years, \(months) months, \(days) days, \(hours) hours, and \(minutes) minutes between the the iPhone Stevenote of 2007 and the iPad Stevenote of 2010.")


// Date addition, part 1:
// What’s the last day of a 90-day warranty that starts today?
// ===========================================================

let lastDay = userCalendar.date(byAdding: .day, value: 90, to: Date())!
print("90 days from now is: \(lastDay.description(with: Locale(identifier: "en_US")))")


// Date addition, part 2: What was the date 5 weeks ago?
// =====================================================
let fiveWeeksAgo = userCalendar.date(byAdding: .weekOfYear, value: -5, to: Date())!
print("5 weeks ago was: \(fiveWeeksAgo.description(with: Locale(identifier: "en_US")))")


// Date addition, part 3:
// What time will it be 4 hours and 30 minutes from now, and
// 4 hours and 30 minutes ago?
// =========================================================

// What time will it be 4 hours and 30 minutes from now?
// First, we need to define a DateComponents struct representing
// a time interval of 4 hours and 30 minutes
var fourHoursThirtyMinutes = DateComponents()
fourHoursThirtyMinutes.hour = 4
fourHoursThirtyMinutes.minute = 30

// Now add the interval to the Date
let fourHoursThirtyMinutesFromNow = userCalendar.date(
  byAdding: fourHoursThirtyMinutes,
  to: Date()
)!
print("4 hours and 30 minutes from now will be: \(fourHoursThirtyMinutesFromNow.description(with: Locale(identifier: "en_US")))")

// What time was it 4 hours and 30 minutes ago?
var minusFourHoursThirtyMinutes = DateComponents()
minusFourHoursThirtyMinutes.hour = -4
minusFourHoursThirtyMinutes.minute = -30
let fourHoursThirtyMinutesAgo = userCalendar.date(
  byAdding: fourHoursThirtyMinutes,
  to: Date()
)!
print("4 hours and 30 minutes ago was: \(fourHoursThirtyMinutesAgo.description(with: Locale(identifier: "en_US")))")


// Date comparisons, part 2: Making Date comparisons a little more “human”
// =======================================================================

// Let's define some Dates relative to the SwiftUI announcement
// (June 3, 2019, 12:08 p.m. PDT)
let swiftUIAnnouncementDateComponents = DateComponents(
  timeZone: TimeZone(abbreviation: "PDT"),
  year: 2019,
  month: 6,
  day: 3,
  hour: 12,
  minute: 8
)
let swiftUIAnnouncement = userCalendar.date(from: swiftUIAnnouncementDateComponents)!

let swiftUIAnnouncementPlusOneSecond = userCalendar.date(
  byAdding: .second,
  value: 1,
  to: swiftUIAnnouncement
)!
let swiftUIAnnouncementPlusFiveMinutes = userCalendar.date(
  byAdding: .minute,
  value: 5,
  to: swiftUIAnnouncement
)!
let swiftUIAnnouncementPlusThreeHours = userCalendar.date(
  byAdding: .hour,
  value: 3,
  to: swiftUIAnnouncement
)!

// This returns false, because when measuring time at the granularity of a SECOND,
// swiftUIAnnouncement happens BEFORE swiftUIAnnouncementPlusOneSecond.
let test1 = userCalendar.compare(swiftUIAnnouncement,
                                 to: swiftUIAnnouncementPlusOneSecond,
                                 toGranularity: .second)
  == .orderedSame
print("test1: \(test1)")

// This returns true, because when measuring time at the granularity of a SECOND,
// swiftUIAnnouncement happens BEFORE swiftUIAnnouncementPlusOneSecond.
let test2 = userCalendar.compare(swiftUIAnnouncement,
                                 to: swiftUIAnnouncementPlusOneSecond,
                                 toGranularity: .second)
  == .orderedAscending
print("test2: \(test2)")

// This returns true, because when measuring time at the granularity of a MINUTE,
// swiftUIAnnouncement happens AT THE SAME TIME AS swiftUIAnnouncementPlusOneSecond.
let test3 = userCalendar.compare(swiftUIAnnouncement,
                                 to: swiftUIAnnouncementPlusOneSecond,
                                 toGranularity: .minute)
  == .orderedSame
print("test3: \(test3)")

// This returns true, because when measuring time at the granularity of an HOUR,
// swiftUIAnnouncement happens AT THE SAME TIME AS swiftUIAnnouncementPlusFiveMinutes.
let test4 = userCalendar.compare(swiftUIAnnouncement,
                                 to: swiftUIAnnouncementPlusFiveMinutes,
                                 toGranularity: .hour)
  == .orderedSame
print("test4: \(test4)")

// This returns true, because when measuring time at the granularity of a MINUTE,
// swiftUIAnnouncementPlusFiveMinutes happens AFTER swiftUIAnnouncement.
let test5 = userCalendar.compare(swiftUIAnnouncementPlusFiveMinutes,
                                 to: swiftUIAnnouncement,
                                 toGranularity: .minute)
  == .orderedDescending
print("test5: \(test5)")

// This returns true, because when measuring time at the granularity of a DAY,
// swiftUIAnnouncement happens AT THE SAME TIME AS swiftUIAnnouncementPlusThreeHours.
let test6 = userCalendar.compare(swiftUIAnnouncement,
                                 to: swiftUIAnnouncementPlusThreeHours,
                                 toGranularity: .day)
  == .orderedSame
print("test6: \(test6)")

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

In the next installment, we’ll look at making working with dates and times in Swift 5 even better with with some syntactic magic.

The Dates and times in Swift 5 series

Dates and times in Swift 5Here are the articles in this series:

Categories
Tampa Bay

Suncoast Developers Guild marks two great years with their updated student handbook

Tap the photo to see it at full size.

Suncoast Developers Guild — the tech heart and soul of Tampa Bay’s St. Pete side — has been around for two years! Since opening their doors in the summer of 2018, this coding school has graduated 100 students in the tech community, and there are others who attended their previous incarnation as the Tampa Bay branch of The Iron Yard.

I used to be a developer evangelist who travelled across North America in another life, and I can tell you that one of the signs of a healthy tech community in a small- to medium-sized city is a coding school that acts as a social/technical/gathering place. Here in Tampa Bay, Suncoast Developers Guild fills that role, and it does so spectacularly.

In their blog post Two Years in and Still Improving, Campus Director Katherine Trammell writes about one way they’re marking their two-year anniversary: by updating the handbook that they make available to their students, alumni, and even the community at large. It’s a useful piece of documentation that ambitiously tries to cover a wide variety of topics that a professional software developer will need both while in class and later on, in the real world — from the technical topics of HTML, CSS, JavaScript, React, and C# — to what you’ll have to do when it’s time to go out there and land a job. They plan to expand the handbook into other topics, including “Java, Python, Typescript, Go, Angular.” (I’ll take this moment to cast my vote for Dart and Flutter.)

Better still, the community can contribute:

Our Handbook is also open-source; we look forward to having the community contribute. Community contribution is so significant to us that we added a button that sends contributors to a live Github based editor where they can make pull requests to suggest updates and edits. We’ll take pull requests both large (re-explain something) to small (fix our typoes, please!)

Congratulations on two excellent years, SDG, and great job with the handbook!

Want to see their handbook? It’s at handbook.suncoast.io.

Categories
Current Events Tampa Bay

Tampa Bay’s Geek Breakfast happens online tomorrow morning!

Someday, we who attend Tampa Bay’s Geek Breakfast will reconvene at Jimbo’s Pit Bar-B-Q, and I will have my customary stack o’ hotcakes or biscuits and gravy washed down with a half-dozen cups of coffee. Until that happens, Geek Breakfast continues online, and it’s happening tomorrow from 8:00 to 9:30 a.m. See you there!

Here’s the Zoom link.

Categories
Programming

Dates and times in Swift 5, part 2: Formatting and parsing dates and times

clock and calendar

Dates and times in Swift 5In the previous article in this series on working with dates and times in Swift 5, we looked at three key structs for date and time programming in Swift:

  • Date represents a single point in time, using a format that can easily be translated into just about any calendar and time-reckoning system: a number of seconds relative to the start of the Third Millennium (January 1, 2001, 00:00:00 UTC).
  • DateComponents specifies time units like year, month, day, hour, minute, and more to represent either a point in time or a duration of time.
  • Calendar provides a context for Dates, and converts Dates to DateComponents and DateComponents to Dates.

These structs all deal with the internal representation of dates and times in Swift 5.

In this article, we’ll look at the DateFormatter class, which allows us to deal with the  external representation of dates and times in Swift 5 as strings to be presented to the user. We use this class to convert Dates into formatted Strings that match the user’s language and locale, and properly-formatted Strings into Dates.

Tap the image to see it at full size.

We’ve already used the Date struct’s description property and description(with:) method to print its value in a human-readable form, but they’re meant for debugging purposes only, and not for presenting date and time information to the user. When presenting dates to the user in string form, use strings that have been created by DateFormatter.

Let’s convert a Date into a String, part 1: Just the date

Start a new playground and enter the following code, which gives us a Date that we can format — June 2, 2014, the day when the Swift programming language was first released:

// The user's calendar incorporates the user's locale and
// time zone settings, which means it's the one you'll use
// most often.
// 1
let userCalendar = Calendar.current

// The Swift programming language was first released on
// June 2, 2014.
// 2
let swiftDebutDateComponents = DateComponents(
  year: 2014,
  month: 6,
  day: 2
)

// 3
let swiftDebutDate = userCalendar.date(from: swiftDebutDateComponents)!

This code is similar to code we entered in the previous article:

  1. Get the user’s current Calendar.
  2. Create a DateComponents struct, swiftDebutDateComponents, providing the year:month:, and day: parameters that correspond to the date June 2, 2014.
  3. Use the user’s Calendar to create swiftDebutDate using swiftDebutDateComponents.

Let’s try turning this date into a string with a DateFormatter.

Add the following to the playground, then run it:

let myFormatter = DateFormatter()
print("Swift’s debut date, via the DateFormatter: \(myFormatter.string(from: swiftDebutDate))")

The output should look like this:

Swift’s debut date, via the DateFormatter:

You may be surprised that the result is an empty String. That’s because you need to specify a dateStyle, which specifies which pre-defined format should be used for the date. We’ll start with the short style.

Add the following to the playground, then run it:

myFormatter.dateStyle = .short
print("Swift’s debut date, “short” style: \(myFormatter.string(from: swiftDebutDate)).")

The output should look like this:

Swift’s debut date, “short” style: 6/2/14.

Let’s try the other styles: medium, long, full, and none.

Add the following to the playground, then run it:

myFormatter.dateStyle = .medium
print("Swift’s debut date, “medium” style: \(myFormatter.string(from: swiftDebutDate))")

myFormatter.dateStyle = .long
print("Swift’s debut date, “long” style: \(myFormatter.string(from: swiftDebutDate))")

myFormatter.dateStyle = .full
print("Swift’s debut date, “full” style: \(myFormatter.string(from: swiftDebutDate))")

myFormatter.dateStyle = .none
print("Swift’s debut date, “none” style: \(myFormatter.string(from: swiftDebutDate))")

The output should look like this:

Swift’s debut date, “medium” style: Jun 2, 2014.
Swift’s debut date, “long” style: June 2, 2014.
Swift’s debut date, “full” style: Monday, June 2, 2014.
Swift’s debut date, “none” style: .

If turns out that the default dateStyle is none. Why would there be a dateStyle called .none? I’ll explain in the next section.

Let’s convert a Date into a String, part 2: A date and a time

Let’s create a date and approximate known time: When SwiftUI was announced at WWDC 2019. It’s introduced 2 hours and 8 minutes into a keynote that started at 10:00 a.m. Pacific Daylight Time, so we’ll say it debuted at 12:08 p.m. PDT on June 3, 2019.

Add the following to the playground, then run it:

// SwiftUI was introduced at WWDC 2019 on
// June 3, 2019 at 12:08 p.m. Pacific Daylight Time.
let swiftUIDebutDateComponents = DateComponents(
  timeZone: TimeZone(abbreviation: "PDT"),
  year: 2019,
  month: 6,
  day: 3,
  hour: 12,
  minute: 8
)
let swiftUIDebutDate = userCalendar.date(from: swiftUIDebutDateComponents)!
print("The newly-created date: \(swiftUIDebutDate.description(with: Locale(identifier: "en-US"))).")

On my computer, the output looked like this:

The newly-created date: Monday, June 3, 2019 at 3:08:00 PM Eastern Daylight Time.

The date and time you’ll see will be determined your system calendar settings.

Now that we have a date and time, let’s format it using the dateStyle property to style the date part, and timeStyle property to style the time part.

Add the following to the playground, then run it:

myFormatter.dateStyle = .short
myFormatter.timeStyle = .short
print("Swift’s debut date and time, “short” style: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateStyle = .medium
myFormatter.timeStyle = .medium
print("Swift’s debut date and time, “medium” style: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateStyle = .long
myFormatter.timeStyle = .long
print("Swift’s debut date and time, “long” style: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateStyle = .full
myFormatter.timeStyle = .full
print("Swift’s debut date and time, “full” style: \(myFormatter.string(from: swiftUIDebutDate)).")

On my computer, the output looked like this:

Swift’s debut date and time, “short” style: 6/3/19, 3:08 PM.
Swift’s debut date and time, “medium” style: Jun 3, 2019 at 3:08:00 PM.
Swift’s debut date and time, “long” style: June 3, 2019 at 3:08:00 PM EDT.
Swift’s debut date and time, “full” style: Monday, June 3, 2019 at 3:08:00 PM Eastern Daylight Time.

You can mix and match dateStyle and timeStyle settings. Add the following to the playground, then run it:

myFormatter.dateStyle = .full
myFormatter.timeStyle = .short
print("Swift’s debut date and time, with “full” style date and “short” style time: \(myFormatter.string(from: swiftUIDebutDate)).")

On my computer, the output looked like this:

Swift’s debut date and time, with “full” style date and “short” style time: Monday, June 3, 2019 at 3:08 PM.

Now that we’re working with a date and time, I can tell you what the .none style is for: for suppressing the display of the date or time in a formatted date string.

Add the following to the playground, then run it:

// Show only the time
myFormatter.dateStyle = .none
myFormatter.timeStyle = .medium
print("Swift’s debut time: \(myFormatter.string(from: swiftUIDebutDate)).")

// Show only the date
myFormatter.dateStyle = .full
myFormatter.timeStyle = .none
print("Swift’s debut date: \(myFormatter.string(from: swiftUIDebutDate)).")

Remember that the Date struct represents a single point in time, which has both a date and a time. The .none style for DateFormatter‘s dateStyle and timeStyle properties allows us to create a String representation of a Date that shows only its date or time part.

This table summarizes the different dateStyle and timeStyle settings for the US English language setting:

Setting dateStyle timeStyle
.none [ empty string ] [ empty string ]
.short 6/3/19 3:08 PM
.medium Jun 3, 2019 3:08:00 PM
.long June 3, 2019 3:08:00 PM EDT
.full Monday, June 3, 2019 3:08:00 PM Eastern Daylight Time

Let’s convert a Date into a String, part 3: Displaying dates and times in other languages

DateFormatter defaults to the user’s preferred language, as specified in their settings. In my case, that’s US English. By setting the locale property of the DateFormatter, I can specify the language for my formatted date strings. Add the following to the playground, then run it:

// We want to see as much of these languages as possible,
// so let’s set both dateStyle and timeStyle to .full.
myFormatter.dateStyle = .full
myFormatter.timeStyle = .full

myFormatter.locale = Locale(identifier: "fr")
print("International French: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.locale = Locale(identifier: "fr-CA")
print("Canadian French: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.locale = Locale(identifier: "hr")
print("Croatian: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.locale = Locale(identifier: "ko_KR")
print("Korean: \(myFormatter.string(from: swiftUIDebutDate)).")

On my computer, the output looked like this:

International French: lundi 3 juin 2019 à 15:08:00 heure d’été de l’Est.
Canadian French: lundi 3 juin 2019 à 15:08:00 heure avancée de l’Est.
Croatian: ponedjeljak, 3. lipnja 2019. u 15:08:00 (istočno ljetno vrijeme).
Korean: 2019 6 3 월요일 오후 3 8 0 동부 하계 표준시.

Let’s convert a Date into a String, part 4: Custom date/time formats

In addition to the built-in formats for dates, you can tell DateFormatter to use a custom format.

Before we begin working with custom date/time formats, I should point out that if you need to display a Date as a String to the user, it’s best if you use Swift’s built-in dateStyle and timeStyle values. They display dates and times properly, according to the user’s settings, which include country and language. You’d be surprised how date formats differ from culture to culture, and it’s better to let Swift do the formatting work.

However, there are times when you need to format dates and times in a specific way that doesn’t match the styles provided by DateFormatter’s dateStyle and timeStyle properties, such as when dealing with certain APIs. That’s where DateFormatter’s dateFormat property comes in handy.

To be certain that the DateFormatter will use your custom date format, set its locale property to POSIX, then define the custom date format string in dateFormat.

Add the following to the playground, then run it:

// Setting the locale to POSIX ensures that the user's locale
// won't be used to format the Date.
myFormatter.locale = Locale(identifier: "en_US_POSIX")

// DateFormatter's format string uses the date format specifiers
// spelled out in Unicode Technical Standard #35 (located at
// http://www.unicode.org/reports/tr35/tr35-25.html#Date_Format_Patterns)
myFormatter.dateFormat = "y-MM-dd"
print("Swift’s debut date and time, y-MM-dd format: \(myFormatter.string(from: swiftUIDebutDate)).")

You can use the date format specifiers listed in Appendix F of the Unicode Technical Standard #35 to define the formatting String for the dateFormat property. Here are some examples (Add the following to the playground, then run it):

// DateFormatter's format string uses the date format specifiers
// spelled out in Unicode Technical Standard #35 (located at
// http://www.unicode.org/reports/tr35/tr35-25.html#Date_Format_Patterns)
myFormatter.dateFormat = "y-MM-dd"
print("Swift’s debut date and time, y-MM-dd format: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateFormat = "'Year: 'y' Month: 'M' Day: 'd"
print("Swift’s debut date and time, in labeled y M d format: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateFormat = "MM/dd/yy"
print("Swift’s debut date and time, MM/dd/yy format: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateFormat = "MMM dd, yyyy"
print("Swift’s debut date and time, MMM dd, yyyy format: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateFormat = "E MMM dd, yyyy"
print("Swift’s debut date and time, E MMM dd, yyyy format: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateFormat = "EEEE, MMMM dd, yyyy' at 'h:mm a"
print("Swift’s debut date and time, EEEE, MMMM dd, yyyy' at 'h:mm a. format: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateFormat = "EEEE, MMMM dd, yyyy' at 'h:mm a zzzz"
print("Swift’s debut date and time, EEEE, MMMM dd, yyyy' at 'h:mm a zzzz. format: \(myFormatter.string(from: swiftUIDebutDate)).")

Let’s convert a String into a Date

DateFormatter works the other way — just as it can convert Dates to Strings, it can also convert Strings to Dates. By setting its dateFormat to the format of the String it should expect, you can use its date(from:) method to convert a String into a Date.

Once again, use the date format specifiers listed in Appendix F of the Unicode Technical Standard #35 to define the formatting String for the dateFormat property.

Add the following to the playground, then run it:

// Let’s define the format for date strings we want to parse:
myFormatter.dateFormat = "yyyy/MM/dd hh:mm Z"

// Here's a date in the specified format.
// DateFormatter’s date(from:) method will be able to parse it.
let newDate1 = myFormatter.date(from: "2019/06/03 12:08 -0700")
print("newDate1’s value is: \(newDate1?.description ?? "nil").")

// And here's the same date, but in a different format:
let newDate2 = myFormatter.date(from: "Jun 6, 2019, 12:08 PM PDT")
print("newDate2’s value is: \(newDate2?.description ?? "nil").")

On my computer, the output looked like this:

newDate1’s value is: 2019-06-03 07:08:00 +0000.
newDate2’s value is: nil.

Let’s change the dateFormat string and try it again. Add the following to the playground, then run it:

// Let's change the date format strings and try
// date(from:) with the same two strings:
myFormatter.dateFormat = "MMM d, yyyy, hh:mm a zz"

let newDate3 = myFormatter.date(from: "2019/06/03 12:08 -0700")
print("newDate3’s value is: \(newDate3?.description ?? "nil").")

let newDate4 = myFormatter.date(from: "Jun 6, 2019, 12:08 PM PDT")
print("newDate4’s value is: \(newDate4?.description ?? "nil").")

On my computer, the output looked like this:

newDate3’s value is: nil.
newDate4’s value is: 2019-06-06 19:08:00 +0000.

If you’re trying to parse a weird date format, use a weird date format string. Add the following to the playground, then run it:

// Don’t forget that you can get weird if you expect to
// parse dates in weird formats!
// (D is the format string for “day of year”, which can be 1...366)
myFormatter.dateFormat = "y 😍 D"
let weirdEmojiDate = myFormatter.date(from: "2020 😍 333")
print("weirdEmojiDate’s value is: \(weirdEmojiDate?.description ?? "nil").")

On my computer, the output looked like this:

weirdEmojiDate’s value is: 2020-11-28 05:00:00 +0000.

Wrapping it all up

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

import UIKit


// Let’s convert a Date into a String, part 1: Just the date
// =========================================================

// The user's calendar incorporates the user's locale and
// time zone settings, which means it's the one you'll use
// most often.
// 1
let userCalendar = Calendar.current

// The Swift programming language was first released on
// June 2, 2014.
// 2
let swiftDebutDateComponents = DateComponents(
  year: 2014,
  month: 6,
  day: 2
)

// 3
let swiftDebutDate = userCalendar.date(from: swiftDebutDateComponents)!

let myFormatter = DateFormatter()
print("Swift’s debut date, via the DateFormatter: \(myFormatter.string(from: swiftDebutDate))")
// Hey, what gives?

myFormatter.dateStyle = .short
print("Swift’s debut date, “short” style: \(myFormatter.string(from: swiftDebutDate)).")

myFormatter.dateStyle = .medium
print("Swift’s debut date, “medium” style: \(myFormatter.string(from: swiftDebutDate)).")

myFormatter.dateStyle = .long
print("Swift’s debut date, “long” style: \(myFormatter.string(from: swiftDebutDate)).")

myFormatter.dateStyle = .full
print("Swift’s debut date, “full” style: \(myFormatter.string(from: swiftDebutDate)).")

myFormatter.dateStyle = .none
print("Swift’s debut date, “none” style: \(myFormatter.string(from: swiftDebutDate)).")


// Let’s convert a Date into a String, part 2: A date and a time
// =============================================================

// SwiftUI was introduced at WWDC 2019 on
// June 3, 2019 at 12:08 p.m. Pacific Daylight Time.
let swiftUIDebutDateComponents = DateComponents(
  timeZone: TimeZone(identifier: "America/Los_Angeles"),
  year: 2019,
  month: 6,
  day: 3,
  hour: 12,
  minute: 8
)
let swiftUIDebutDate = userCalendar.date(from: swiftUIDebutDateComponents)!
print("The newly-created date: \(swiftUIDebutDate.description(with: Locale(identifier: "en-US"))).")

myFormatter.dateStyle = .short
myFormatter.timeStyle = .short
print("Swift’s debut date and time, “short” style: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateStyle = .medium
myFormatter.timeStyle = .medium
print("Swift’s debut date and time, “medium” style: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateStyle = .long
myFormatter.timeStyle = .long
print("Swift’s debut date and time, “long” style: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateStyle = .full
myFormatter.timeStyle = .full
print("Swift’s debut date and time, “full” style: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateStyle = .full
myFormatter.timeStyle = .short
print("Swift’s debut date and time, with “full” style date and “short” style time: \(myFormatter.string(from: swiftUIDebutDate)).")

// Show only the time
myFormatter.dateStyle = .none
myFormatter.timeStyle = .medium
print("Swift’s debut time: \(myFormatter.string(from: swiftUIDebutDate)).")

// Show only the date
myFormatter.dateStyle = .full
myFormatter.timeStyle = .none
print("Swift’s debut date: \(myFormatter.string(from: swiftUIDebutDate)).")


// Let’s convert a Date into a String, part 3: Displaying dates and times in other languages
// =========================================================================================

// We want to see as much of these languages as possible,
// so let’s set both dateStyle and timeStyle to .full.
myFormatter.dateStyle = .full
myFormatter.timeStyle = .full

myFormatter.locale = Locale(identifier: "fr")
print("International French: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.locale = Locale(identifier: "fr-CA")
print("Canadian French: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.locale = Locale(identifier: "hr")
print("Croatian: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.locale = Locale(identifier: "ko_KR")
print("Korean: \(myFormatter.string(from: swiftUIDebutDate)).")


// Let’s convert a Date into a String, part 4: Custom date/time formats
// ====================================================================

// Setting the locale to POSIX ensures that the user's locale
// won't be used to format the Date.
myFormatter.locale = Locale(identifier: "en_US_POSIX")

// DateFormatter's format string uses the date format specifiers
// spelled out in Unicode Technical Standard #35 (located at
// http://www.unicode.org/reports/tr35/tr35-25.html#Date_Format_Patterns)
print("Custom date and time formats:")

myFormatter.dateFormat = "y-MM-dd"
print("Swift’s debut date and time, y-MM-dd format: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateFormat = "'Year: 'y' Month: 'M' Day: 'd"
print("Year: y Month: M Day: d format: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateFormat = "MM/dd/yy"
print("MM/dd/yy format: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateFormat = "MMM dd, yyyy"
print("MMM dd, yyyy format: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateFormat = "E MMM dd, yyyy"
print("E MMM dd, yyyy format: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateFormat = "EEEE, MMMM dd, yyyy' at 'h:mm a"
print("EEEE, MMMM dd, yyyy' at 'h:mm a. format: \(myFormatter.string(from: swiftUIDebutDate)).")

myFormatter.dateFormat = "EEEE, MMMM dd, yyyy' at 'h:mm a zzzz"
print("EEEE, MMMM dd, yyyy' at 'h:mm a zzzz. format: \(myFormatter.string(from: swiftUIDebutDate)).")


// Let’s convert a String into a Date
// ==================================

// Let’s define the format for date strings we want to parse:
myFormatter.dateFormat = "yyyy/MM/dd hh:mm Z"

// Here's a date in the specified format.
// DateFormatter’s date(from:) method will be able to parse it.
let newDate1 = myFormatter.date(from: "2019/06/03 12:08 -0700")
print("newDate1’s value is: \(newDate1?.description ?? "nil").")

// And here's the same date, but in a different format:
let newDate2 = myFormatter.date(from: "Jun 6, 2019, 12:08 PM PDT")
print("newDate2’s value is: \(newDate2?.description ?? "nil").")

// Let's change the date format strings and try
// date(from:) with the same two strings:
myFormatter.dateFormat = "MMM d, yyyy, hh:mm a zz"

let newDate3 = myFormatter.date(from: "2019/06/03 12:08 -0700")
print("newDate3’s value is: \(newDate3?.description ?? "nil").")

let newDate4 = myFormatter.date(from: "Jun 6, 2019, 12:08 PM PDT")
print("newDate4’s value is: \(newDate4?.description ?? "nil").")

// Don’t forget that you can get weird if you expect to
// parse dates in weird formats!
// (D is the format string for “day of year”, which can be 1...366)
myFormatter.dateFormat = "y 😍 D"
let weirdEmojiDate = myFormatter.date(from: "2020 😍 333")
print("weirdEmojiDate’s value is: \(weirdEmojiDate?.description ?? "nil").")

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

In the next installment on dates and times in Swift 5, we’ll look at date calculations.

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

Dates and times in Swift 5Here are the articles in this series: