Categories
Uncategorized

How to work with dates and times in Swift 3, part 3: Date arithmetic

abacus with toy clock

You can actually buy this thing on Etsy! Click the photo for details.

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

So far, in this series on date and time programming in Swift 3, 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:

import UIKit

// 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.
var iPhoneStevenoteDateComponents = DateComponents()
iPhoneStevenoteDateComponents.year = 2007
iPhoneStevenoteDateComponents.month = 1
iPhoneStevenoteDateComponents.day = 9
iPhoneStevenoteDateComponents.hour = 10
iPhoneStevenoteDateComponents.timeZone = TimeZone(abbreviation: "PST")
let iPhoneStevenoteDate = userCalendar.date(from: iPhoneStevenoteDateComponents)!

// 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")!

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. You may find that if you need to instantiate a large number of Dates in code, you may want to do so this way, since this approach requires far fewer lines of code than doing so using DateComponents.

Date comparisons, part 1

A chick looking at an egg.

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

Add the following code to the playground:

// (Previous code goes here)

// Does Valentine's Day come BEFORE St. Patrick's Day?
valentinesDay < stPatricksDay   // true

// Does Valentine's Day come AFTER St. Patrick's Day?
valentinesDay > stPatricksDay   // false

// Does St. Patrick's Day come BEFORE Valentine's Day?
stPatricksDay < valentinesDay   // false

// Did the iPhone Stevenote come BEFORE the iPad Stevenote?
iPhoneStevenoteDate < iPadStevenoteDate   // true

// Did the iPhone Stevenote come AFTER the iPad Stevenote?
iPhoneStevenoteDate > iPadStevenoteDate   // false

// Did the iPad Stevenote come BEFORE the iPhone Stevenote?
iPadStevenoteDate < iPhoneStevenoteDate   // false

// Does the iPad Stevenote come AFTER the iPhone Stevenote?
iPadStevenoteDate > iPhoneStevenoteDate   // true

// Do the iPhone Stevenote and the iPad Stevenote fall on DIFFERENT dates?
iPhoneStevenoteDate == iPadStevenoteDate  // false

// Do the iPhone Stevenote and the iPad Stevenote fall on the SAME date?
iPhoneStevenoteDate != iPadStevenoteDate  // true

// And just for kicks, some trivial comparisons.
iPhoneStevenoteDate == iPhoneStevenoteDate  // true
iPadStevenoteDate == iPadStevenoteDate  // 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:

// (Previous code goes here)

// Number of seconds between the iPhone Stevenote and the iPad Stevenote
iPhoneStevenoteDate.timeIntervalSince(iPadStevenoteDate)  // -96249600

// Number of seconds between the iPad Stevenote and the iPhone Stevenote
iPadStevenoteDate.timeIntervalSince(iPhoneStevenoteDate)  // 96249600

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 .orderedSame
endDate .orderedAscending

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:

// (Previous code goes here)

// The result in the sidebar should be:
// day: 1114 isLeapMonth: false
let daysBetweenStevenotes = userCalendar.dateComponents([.day],
                                                        from: iPhoneStevenoteDate,
                                                        to: iPadStevenoteDate)
daysBetweenStevenotes.day!  // 1114

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(components, from: startDate, to: endDate) method, we can get the result expressed in different time units. Add the following code to the playground:

// (Previous code goes here)

// The result in the sidebar should be:
// weekOfYear: 159 isLeapMonth: false
let weeksBetweenStevenotes = userCalendar.dateComponents([.weekOfYear],
                                                         from: iPhoneStevenoteDate,
                                                         to: iPadStevenoteDate)
weeksBetweenStevenotes.weekOfYear!  // 159

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(components, from: startDate, to: endDate) method. Add the following code to the playground:

// (Previous code goes here)

// The result in the sidebar should be:
// year: 3 month: 0 day: 18 hour: 0 minute: 0 isLeapMonth: false
let yearsMonthsDaysHoursMinutesBetweenStevenotes = userCalendar.dateComponents([.year, .month, .day, .hour, .minute],
                                                                               from: iPhoneStevenoteDate,
                                                                               to: iPadStevenoteDate)
yearsMonthsDaysHoursMinutesBetweenStevenotes.year!    // 3
yearsMonthsDaysHoursMinutesBetweenStevenotes.month!   // 0
yearsMonthsDaysHoursMinutesBetweenStevenotes.day!     // 18
yearsMonthsDaysHoursMinutesBetweenStevenotes.hour!    // 0
yearsMonthsDaysHoursMinutesBetweenStevenotes.minute!  // 0

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 the Calendar struct:

date(byAdding: timeUnit, 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:

// (Previous code goes here)

// What's the last day of a 90-day warranty that starts today?
userCalendar.date(byAdding: .day, value: 90, to: Date())  // 90 days from now

The result is a Date representing a point in time 90 days from the present.

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?
userCalendar.date(byAdding: .weekOfYear, value: -5, to: Date())

The result is a Date representing a point in time 5 weeks in the past.

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
timeInterval 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?”

// (Previous code goes here)

// 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()) // 4 hours and 30 minutes from now

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:

// (Previous code goes here)

// 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()) // 4 hours and 30 minutes ago

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:

  • The start of the iPhone Stevenote: January 9, 2007, 10:00:00 a.m. PST
  • One second after the start of the iPhone Stevenote: January 9, 2007, 10:00:01 a.m. PST
  • Five minutes after the start of the iPhone Stevenote: January 9, 2007, 10:05:00 a.m. PST
  • Three hours after the start of the iPhone Stevenote: January 9, 2007, 1:00:00 p.m. PST

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 the iPhone Stevenote and one second after.
  • If you’re concerned only with the day on which the iPhone Stevenote took place 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 level of precision .orderedAscending
firstDate is equal to secondDate, when compared at the specified level of precision .orderedSame
firstDate is later than secondDate, when compared at the specified level of precision .orderedAscending

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

// (Previous code goes here)

// Let's define some Dates relative to iPhoneStevenoteDate
let iPhoneStevenotePlusOneSecond = userCalendar.date(byAdding: .second, value: 1, to: iPhoneStevenoteDate)!
let iPhoneStevenotePlusFiveMinutes = userCalendar.date(byAdding: .minute, value: 5, to: iPhoneStevenoteDate)!
let iPhoneStevenotePlusThreeHours = userCalendar.date(byAdding: .hour, value: 3, to: iPhoneStevenoteDate)!

// This returns false, because iPhoneStevenoteDate and iPhoneStevenotePlusOneSecond
// are NOT both within the same SECOND.
userCalendar.compare(iPhoneStevenoteDate,
                     to: iPhoneStevenotePlusOneSecond,
                     toGranularity: .second)
  == .orderedSame

// This returns true, because iPhoneStevenoteDate comes one second BEFORE
// iPhoneStevenotePlusOneSecond, and we're comparing the two at the one-second
// level of granularity.
userCalendar.compare(iPhoneStevenoteDate,
                     to: iPhoneStevenotePlusOneSecond,
                     toGranularity: .second)
  == .orderedAscending

// This returns true, because iPhoneStevenoteDate and iPhoneStevenotePlusOneSecond
// ARE both within the same MINUTE.
userCalendar.compare(iPhoneStevenoteDate,
                     to: iPhoneStevenotePlusOneSecond,
                     toGranularity: .minute)
  == .orderedSame

// This returns true, because iPhoneStevenoteDate and iPhoneStevenotePlusFiveMinutes
// ARE both within the same HOUR.
userCalendar.compare(iPhoneStevenoteDate,
                     to: iPhoneStevenotePlusFiveMinutes,
                     toGranularity: .hour)
  == .orderedSame

// This returns true, because iPhoneStevenotePlusFiveMinutes comes 5 minutes AFTER
// iPhoneStevenoteDate, and we're comparing the two at the one-minute level 
// of granularity.
userCalendar.compare(iPhoneStevenotePlusFiveMinutes,
                     to: iPhoneStevenoteDate,
                     toGranularity: .minute)
  == .orderedDescending

// This returns true, because iPhoneStevenoteDate and iPhoneStevenotePlusThreeHours
// ARE both within the same DAY.
userCalendar.compare(iPhoneStevenoteDate,
                     to: iPhoneStevenotePlusThreeHours,
                     toGranularity: .day)
  == .orderedSame

Wrapping it all up

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

import UIKit

// 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


// A couple of Dates to work with
// ------------------------------

// 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.
var iPhoneStevenoteDateComponents = DateComponents()
iPhoneStevenoteDateComponents.year = 2007
iPhoneStevenoteDateComponents.month = 1
iPhoneStevenoteDateComponents.day = 9
iPhoneStevenoteDateComponents.hour = 10
iPhoneStevenoteDateComponents.timeZone = TimeZone(abbreviation: "PST")
let iPhoneStevenoteDate = userCalendar.date(from: iPhoneStevenoteDateComponents)!

// Now 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")!


// Date comparisons, part 1
// ------------------------

// Did the iPhone Stevenote come BEFORE the iPad Stevenote?
iPhoneStevenoteDate < iPadStevenoteDate   // true

// Did the iPhone Stevenote come AFTER the iPad Stevenote?
iPhoneStevenoteDate > iPadStevenoteDate   // false

// Did the iPad Stevenote come BEFORE the iPhone Stevenote?
iPadStevenoteDate < iPhoneStevenoteDate   // false

// Does the iPad Stevenote come AFTER the iPhone Stevenote?
iPadStevenoteDate > iPhoneStevenoteDate   // true

// Do the iPhone Stevenote and the iPad Stevenote fall on DIFFERENT dates?
iPhoneStevenoteDate == iPadStevenoteDate  // false

// Do the iPhone Stevenote and the iPad Stevenote fall on the SAME date?
iPhoneStevenoteDate != iPadStevenoteDate  // true

// And just for kicks, some trivial comparisons.
iPhoneStevenoteDate == iPhoneStevenoteDate  // true
iPadStevenoteDate == iPadStevenoteDate  // true


// Date arithmetic, part 1:
// Number of seconds between the iPhone Stevenote and iPad Stevenote
// -----------------------------------------------------------------

// Number of seconds between the iPhone Stevenote and the iPad Stevenote
iPhoneStevenoteDate.timeIntervalSince(iPadStevenoteDate)  // -96249600

// Number of seconds between the iPad Stevenote and the iPhone Stevenote
iPadStevenoteDate.timeIntervalSince(iPhoneStevenoteDate)  // 96249600


// Date arithmetic, part 2:
// Number of days between the iPhone Stevenote and iPad Stevenote
// --------------------------------------------------------------

// The result in the sidebar should be:
// day: 1114 isLeapMonth: false
let daysBetweenStevenotes = userCalendar.dateComponents([.day],
                                                        from: iPhoneStevenoteDate,
                                                        to: iPadStevenoteDate)
daysBetweenStevenotes.day!  // 1114


// Date arithmetic, part 3:
// Number of weeks between the iPhone Stevenote and iPad Stevenote
// ---------------------------------------------------------------

// The result in the sidebar should be:
// weekOfYear: 159 isLeapMonth: false
let weeksBetweenStevenotes = userCalendar.dateComponents([.weekOfYear],
                                                         from: iPhoneStevenoteDate,
                                                         to: iPadStevenoteDate)
weeksBetweenStevenotes.weekOfYear!  // 159


// Date arithmetic, part 4:
// Number of years, months, and days between the
// iPhone Stevenote and iPad Stevenote
// ---------------------------------------------

// The result in the sidebar should be:
// year: 3 month: 0 day: 18 hour: 0 minute: 0 isLeapMonth: false
let yearsMonthsDaysHoursMinutesBetweenStevenotes = userCalendar.dateComponents([.year, .month, .day, .hour, .minute],
                                                                               from: iPhoneStevenoteDate,
                                                                               to: iPadStevenoteDate)
yearsMonthsDaysHoursMinutesBetweenStevenotes.year!    // 3
yearsMonthsDaysHoursMinutesBetweenStevenotes.month!   // 0
yearsMonthsDaysHoursMinutesBetweenStevenotes.day!     // 18
yearsMonthsDaysHoursMinutesBetweenStevenotes.hour!    // 0
yearsMonthsDaysHoursMinutesBetweenStevenotes.minute!  // 0


// Date addition, part 1:
// What's the last day of a 90-day warranty that starts today?
// -----------------------------------------------------------
userCalendar.date(byAdding: .day, value: 90, to: Date())  // 90 days from now


// Date addition, part 2:
// What was the date 5 weeks ago?
// ------------------------------
userCalendar.date(byAdding: .weekOfYear, value: -5, to: Date())


// 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()) // 4 hours and 30 minutes from now

// Now, let's define a DateComponents struct representing
// a time interval of -4 hours and -30 minutes
var minusFourHoursThirtyMinutes = DateComponents()
minusFourHoursThirtyMinutes.hour = -4
minusFourHoursThirtyMinutes.minute = -30

// Now add the interval to the Date
let fourHoursThirtyMinutesAgo = userCalendar.date(byAdding: fourHoursThirtyMinutes, to: Date()) // 4 hours and 30 minutes ago


// Date comparisons, part 2:
// Making Date comparisons a little more "human"
// ---------------------------------------------

// Let's define some Dates relative to iPhoneStevenoteDate
let iPhoneStevenotePlusOneSecond = userCalendar.date(byAdding: .second, value: 1, to: iPhoneStevenoteDate)!
let iPhoneStevenotePlusFiveMinutes = userCalendar.date(byAdding: .minute, value: 5, to: iPhoneStevenoteDate)!
let iPhoneStevenotePlusThreeHours = userCalendar.date(byAdding: .hour, value: 3, to: iPhoneStevenoteDate)!

// This returns false, because iPhoneStevenoteDate and iPhoneStevenotePlusOneSecond
// are NOT both within the same SECOND.
userCalendar.compare(iPhoneStevenoteDate,
                     to: iPhoneStevenotePlusOneSecond,
                     toGranularity: .second)
  == .orderedSame

// This returns true, because iPhoneStevenoteDate comes one second BEFORE
// iPhoneStevenotePlusOneSecond, and we're comparing the two at the one-second
// level of granularity.
userCalendar.compare(iPhoneStevenoteDate,
                     to: iPhoneStevenotePlusOneSecond,
                     toGranularity: .second)
  == .orderedAscending

// This returns true, because iPhoneStevenoteDate and iPhoneStevenotePlusOneSecond
// ARE both within the same MINUTE.
userCalendar.compare(iPhoneStevenoteDate,
                     to: iPhoneStevenotePlusOneSecond,
                     toGranularity: .minute)
  == .orderedSame

// This returns true, because iPhoneStevenoteDate and iPhoneStevenotePlusFiveMinutes
// ARE both within the same HOUR.
userCalendar.compare(iPhoneStevenoteDate,
                     to: iPhoneStevenotePlusFiveMinutes,
                     toGranularity: .hour)
  == .orderedSame

// This returns true, because iPhoneStevenotePlusFiveMinutes comes 5 minutes AFTER
// iPhoneStevenoteDate, and we're comparing the two at the one-minute level 
// of granularity.
userCalendar.compare(iPhoneStevenotePlusFiveMinutes,
                     to: iPhoneStevenoteDate,
                     toGranularity: .minute)
  == .orderedDescending

// This returns true, because iPhoneStevenoteDate and iPhoneStevenotePlusThreeHours
// ARE both within the same DAY.
userCalendar.compare(iPhoneStevenoteDate,
                     to: iPhoneStevenotePlusThreeHours,
                     toGranularity: .day)
  == .orderedSame

In the next installment, we’ll look at making working with dates and times more Swift-like.

Categories
Uncategorized

With Android Nougat, I FINALLY get the emoji I’ve been waiting for…

whisky bacon emoji

…namely, Whisk(e)y and bacon. Now my communications needs have been met — I just have to wait for the over-the-air update to come out for my Moto G4.

Categories
Uncategorized

How to work with dates and times in Swift 3, part 2: DateFormatter

swift dateformatter class

Click the diagram to see it at full size.

In the previous article in this series, 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 this article, we’ll look at the DateFormatter class, which allows us to deal with their external representation — by converting Dates into Strings, and properly-formatted Strings into Dates.

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

alexander graham bell and phone

Start a new playground and enter the following code, which gives us a Date that we can format — the day when Alexander Graham Bell made the very first phone call:

// 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

// On March 10, 1876, Alexander Graham Bell
// made the first land line phone call.
var firstLandPhoneCallDateComponents = DateComponents()
firstLandPhoneCallDateComponents.year = 1876
firstLandPhoneCallDateComponents.month = 3
firstLandPhoneCallDateComponents.day = 10
let firstLandPhoneCallDate = userCalendar.date(from: firstLandPhoneCallDateComponents)!

// The value "Mar 10, 1876, 12:00 AM" should appear in the
// playground sidebar.
firstLandPhoneCallDate

Now let’s try turning this date into a string with a DateFormatter:

let myFormatter = DateFormatter()

myFormatter.string(from: firstLandPhoneCallDate) // What gives?

You may be surprised that the result is an empty String. This can be fixed by specifying a dateStyle:

let myFormatter = DateFormatter()

myFormatter.string(from: firstLandPhoneCallDate) // What gives?

myFormatter.dateStyle = .short
myFormatter.string(from: firstLandPhoneCallDate) // "3/10/76"

Let’s try the other dateStyles:

let myFormatter = DateFormatter()

myFormatter.string(from: firstLandPhoneCallDate) // What gives?

myFormatter.dateStyle = .short
myFormatter.string(from: firstLandPhoneCallDate) // "3/10/76"

myFormatter.dateStyle = .medium
myFormatter.string(from: firstLandPhoneCallDate) // "Mar 10, 1876"

myFormatter.dateStyle = .long
myFormatter.string(from: firstLandPhoneCallDate) // "March 10, 1876"

myFormatter.dateStyle = .full
myFormatter.string(from: firstLandPhoneCallDate) // "Friday, March 10, 1876"

myFormatter.dateStyle = .none
myFormatter.string(from: firstLandPhoneCallDate) // ""

Why would there be a dateStyle called .none? I’ll explain in a little bit.

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

steve jobs and ipad

Let’s work with an event for which we know both the date and time: the “Stevenote” where the iPad was introduced, which started on January 27, 2010, at 10:00 a.m. Pacific Time (UTC-8). We’ll define this as a Date by adding the following code:

// (Previous code goes here)

// The Stevenote where the iPad was introduced took place on
// January 27, 2010 at 10:00 a.m. Pacific time.
var iPadStevenoteDateComponents = DateComponents()
iPadStevenoteDateComponents.year = 2010
iPadStevenoteDateComponents.month = 1
iPadStevenoteDateComponents.day = 27
iPadStevenoteDateComponents.hour = 10
iPadStevenoteDateComponents.minute = 0
iPadStevenoteDateComponents.timeZone = TimeZone(identifier: "America/Los_Angeles")
let iPadStevenoteDate = userCalendar.date(from: iPadStevenoteDateComponents)!

Now that we have a date and time, let’s format it using the dateStyle and timeStyle properties:

// (Previous code goes here)

myFormatter.dateStyle = .short
myFormatter.timeStyle = .short
myFormatter.string(from: iPadStevenoteDate)  // "1/27/10, 1:00 PM"

myFormatter.dateStyle = .medium
myFormatter.timeStyle = .medium
myFormatter.string(from: iPadStevenoteDate)  // "Jan 27, 2010, 1:00:00 PM"

myFormatter.dateStyle = .long
myFormatter.timeStyle = .long
myFormatter.string(from: iPadStevenoteDate)  // "January 27, 2010 at 1:00:00

myFormatter.dateStyle = .full
myFormatter.timeStyle = .full
myFormatter.string(from: iPadStevenoteDate)  // "Wednesday, January 27, 2010 at 1:00:00 PM Eastern Standard Time"

Now that we’re working with a date and time, let’s see what the .none style is for:

// (Previous code goes here)

// Show only the time
myFormatter.dateStyle = .none
myFormatter.timeStyle = .full
myFormatter.string(from: iPadStevenoteDate)  // "1:00:00 PM Eastern Standard Time"

// Show only the date
myFormatter.dateStyle = .full
myFormatter.timeStyle = .none
myFormatter.string(from: iPadStevenoteDate)  // "Wednesday, January 27, 2010"

Remember that in Swift, 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.

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

electronic calendar

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:

// (Previous code goes here)

// 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"
myFormatter.string(from: iPadStevenoteDate)  // "2010-01-27"

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:

// (Previous code goes here)

myFormatter.dateFormat = "'Year: 'y' Month: 'M' Day: 'd"
myFormatter.string(from: iPadStevenoteDate)  // "Year: 2010 Month: 1 Day: 27"

myFormatter.dateFormat = "MM/dd/yy"
myFormatter.string(from: iPadStevenoteDate)  // "01/27/10"

myFormatter.dateFormat = "MMM dd, yyyy"
myFormatter.string(from: iPadStevenoteDate)  // "Jan 27, 2010"

myFormatter.dateFormat = "E MMM dd, yyyy"
myFormatter.string(from: iPadStevenoteDate)  // "Wed Jan 27, 2010"

myFormatter.dateFormat = "EEEE, MMMM dd, yyyy' at 'h:mm a."
myFormatter.string(from: iPadStevenoteDate)  // "Wednesday, January 27, 2010 at 1:00 PM."

myFormatter.dateFormat = "EEEE, MMMM dd, yyyy' at 'h:mm a zzzz."
myFormatter.string(from: iPadStevenoteDate)  // "Wednesday, January 27, 2010 at 1:00 PM Eastern Standard Time."

Let’s convert a String into a Date

clock and calendar

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:

// (Previous code goes here)

myFormatter.dateFormat = "yyyy/MM/dd hh:mm Z"

// Here's a date in the format specified by the string
// assigned to dateFormat:
let newDate1 = myFormatter.date(from: "2015/03/07 11:00 -0500")    // Date - "Mar 7, 2015, 11:00 AM"
// And here's the same date, but in a different format:
let newDate2 = myFormatter.date(from: "Mar 7, 2015, 11:00:00 AM")  // nil

Let’s change the dateFormat string and try it again:

// (Previous code goes here)

// 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"

myFormatter.date(from: "2015/03/07 11:00 -0500")     // nil
myFormatter.date(from: "Mar 7, 2015, 11:00 AM EST")  // "Mar 7, 2015, 11:00 AM"

Wrapping it all up

Here’s a 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.
let userCalendar = Calendar.current

// On March 10, 1876, Alexander Graham Bell
// made the first land line phone call.
var firstLandPhoneCallDateComponents = DateComponents()
firstLandPhoneCallDateComponents.year = 1876
firstLandPhoneCallDateComponents.month = 3
firstLandPhoneCallDateComponents.day = 10
let firstLandPhoneCallDate = userCalendar.date(from: firstLandPhoneCallDateComponents)!

// The value "Mar 10, 1876, 12:00 AM" should appear in the
// playground sidebar.
firstLandPhoneCallDate

let myFormatter = DateFormatter()

myFormatter.string(from: firstLandPhoneCallDate) // What gives?

myFormatter.dateStyle = .short
myFormatter.string(from: firstLandPhoneCallDate) // "3/10/76"

myFormatter.dateStyle = .medium
myFormatter.string(from: firstLandPhoneCallDate) // "Mar 10, 1876"

myFormatter.dateStyle = .long
myFormatter.string(from: firstLandPhoneCallDate) // "March 10, 1876"

myFormatter.dateStyle = .full
myFormatter.string(from: firstLandPhoneCallDate) // "Friday, March 10, 1876"

myFormatter.dateStyle = .none
myFormatter.string(from: firstLandPhoneCallDate) // ""


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

// The Stevenote where the iPad was introduced took place on
// January 27, 2010 at 10:00 a.m. Pacific time.
var iPadStevenoteDateComponents = DateComponents()
iPadStevenoteDateComponents.year = 2010
iPadStevenoteDateComponents.month = 1
iPadStevenoteDateComponents.day = 27
iPadStevenoteDateComponents.hour = 10
iPadStevenoteDateComponents.minute = 0
iPadStevenoteDateComponents.timeZone = TimeZone(identifier: "America/Los_Angeles")
let iPadStevenoteDate = userCalendar.date(from: iPadStevenoteDateComponents)!

myFormatter.dateStyle = .short
myFormatter.timeStyle = .short
myFormatter.string(from: iPadStevenoteDate)  // "1/27/10, 1:00 PM"

myFormatter.dateStyle = .medium
myFormatter.timeStyle = .medium
myFormatter.string(from: iPadStevenoteDate)  // "Jan 27, 2010, 1:00:00 PM"

myFormatter.dateStyle = .long
myFormatter.timeStyle = .long
myFormatter.string(from: iPadStevenoteDate)  // "January 27, 2010 at 1:00:00

myFormatter.dateStyle = .full
myFormatter.timeStyle = .full
myFormatter.string(from: iPadStevenoteDate)  // "Wednesday, January 27, 2010 at 1:00:00 PM Eastern Standard Time"

// Show only the time
myFormatter.dateStyle = .none
myFormatter.timeStyle = .full
myFormatter.string(from: iPadStevenoteDate)  // "1:00:00 PM Eastern Standard Time"

// Show only the date
myFormatter.dateStyle = .full
myFormatter.timeStyle = .none
myFormatter.string(from: iPadStevenoteDate)  // "Wednesday, January 27, 2010"


// Let’s convert a Date into a String, part 3: 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)
myFormatter.dateFormat = "y-MM-dd"
myFormatter.string(from: iPadStevenoteDate)  // "2010-01-27"

myFormatter.dateFormat = "'Year: 'y' Month: 'M' Day: 'd"
myFormatter.string(from: iPadStevenoteDate)  // "Year: 2010 Month: 1 Day: 27"

myFormatter.dateFormat = "MM/dd/yy"
myFormatter.string(from: iPadStevenoteDate)  // "01/27/10"

myFormatter.dateFormat = "MMM dd, yyyy"
myFormatter.string(from: iPadStevenoteDate)  // "Jan 27, 2010"

myFormatter.dateFormat = "E MMM dd, yyyy"
myFormatter.string(from: iPadStevenoteDate)  // "Wed Jan 27, 2010"

myFormatter.dateFormat = "EEEE, MMMM dd, yyyy' at 'h:mm a."
myFormatter.string(from: iPadStevenoteDate)  // "Wednesday, January 27, 2010 at 1:00 PM."

myFormatter.dateFormat = "EEEE, MMMM dd, yyyy' at 'h:mm a zzzz."
myFormatter.string(from: iPadStevenoteDate)  // "Wednesday, January 27, 2010 at 1:00 PM Eastern Standard Time."


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

myFormatter.dateFormat = "yyyy/MM/dd hh:mm Z"

// Here's a date in the format specified by the string
// assigned to dateFormat:
myFormatter.date(from: "2015/03/07 11:00 -0500")     // Date - "Mar 7, 2015, 11:00 AM"
// And here's the same date, but in a different format:
myFormatter.date(from: "Mar 7, 2015, 11:00 AM EST")  // 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"

myFormatter.date(from: "2015/03/07 11:00 -0500")     // nil
myFormatter.date(from: "Mar 7, 2015, 11:00 AM EST")  // "Mar 7, 2015, 11:00 AM"

In the next installment, we’ll look at date calculations.

Categories
Uncategorized

Mobility reading list for August 19, 2016: Pokémon Go, pros and cons of BYOD, and an 11-step guide to BYOD security

Pokémon Go and workplace data security

pokemon go

With over 25 million users playing every day, and downloads and active users surpassing even Twitter and Tinder, Pokémon Go is turning out to be one of the most popular mobile applications of all time. According to Data Security Law Blog, it also highlights the risks in BYOD (Bring Your Own Device) policies.

With a BYOD device that has Pokémon Go installed, the risks come from the security — or more accurately, insecurity — of the Pokémon Go app itself. Earlier versions of the game gave the app full access to the user’s complete Google account profile and related information, including Google-accessible email accounts and search history. While it’s not likely that this now-fixed security hole led to any breaches, it’s a prime example of how a suddenly popular app running on a BYOD device can lead to trouble.

All this is still new territory — the smartphone as we currently know it won’t even turn 10 until next year — and businesses would do well to watch the effects of apps like Pokémon Go, and alter their mobile policies accordingly.

The pros and cons of BYOD

byod

As we’ve observed before, mobile devices are more personal than even “personal” computers. People don’t want to part with them, which is why they prefer to bring them to work — and use them for work. This attachment to our mobile devices, for which we’re finding more and more uses, is why at least two-thirds of employees are using their own mobile devices in the course of doing their jobs.

Tech.co has a brief but useful article that summarizes the big pros and cons of BYOD, explains what the general best practices are, and explains why training employees is key to a successful BYOD implementation. If your organization is considering or just starting with a BYOD program, it would be worth your time to read this article.

The 11-step guide to BYOD security, or how to avoid getting fired

security

In a recent entry on their blog, Heimdal Security provide these BYOD statistics:

In the same article, they list eleven common sense (which is uncommon) steps for BYOD users to secure the personal mobile devices they use for work. As they say, “security is each user’s own business”.

this article also appears in the GSG blog

Categories
Uncategorized

The next Tampa iOS Meetup, and upcoming changes

change

It’s been a couple of months since the last Tampa iOS Meetup, and with summer drawing to a close and iOS 10 coming very soon, it’s time to kick things back into high gear. It’s going to be an interesting fall, what with the coming changes…

bay to bay

angelaThe first change is that Angela, who originally put this Meetup group together, landed a job in San Francisco and moved there. I want to congratulate her on seizing this opportunity — I did the same thing during the dot-com bubble of the late ’90s — and wish her the very best in her adventures in the other Bay Area. I’d also like to thank her for all the work she’s done for this Meetup group, which wouldn’t even exist without the initial spark that she provided.

As for me, I plan to stick around, and will continue in my role as this Meetup group’s organizer, lead speaker, and accordion player.

building imessage sticker packs and messaging apps for ios 10

The next change comes from Apple, in the form of iOS 10, Xcode 8, and Swift 3, and we’ll be talking about what these new versions mean for you as iOS developers over the next couple of meetups.

The date for the next meetup is to be determined, but I’m aiming for early September, and the topic will be Building iMessage sticker packs and messaging apps for iOS 10. iMessage is one of the most-used iOS apps, and a lot of people seem to like adding a little flair to their communications, so the introduction of sticker packs and iMessage apps represents an opportunity for the developer looking to make a splash. Sticker packs have the added bonus of not needed any programming to develop.

samsung galaxy s7 and iphone 6s

The final change is one that I’d like your opinion on. I’m thinking of expanding the subject area of our Meetup group to cover these topics:

  • iOS development, which it already covers,
  • Android development,
  • and IoT (internet of things) development

In other words, I’m thinking about expanding our group’s coverage to while I like calling development for “Tiny shiny platforms”. Would this be something that you’d be interested in, or would you rather stick to just iOS? I’d love to hear what you think — let me know by dropping me a line at joey@joeydevilla.com­!

Categories
Uncategorized

The very essence of becoming a better programmer (or any other maker or doer of things)

changing stuff and seeing what happens

It’s how we learn best, so go forth, experiment, watch what happens, take notes — and if you can, share your knowledge. Happy hacking!

The image above comes from @ThePracticalDev’s Twitter account, and I used it in a recent article: How to work with dates and times in Swift 3, part 1: Dates, Calendars, and DateComponents.

Categories
Uncategorized

How to work with dates and times in Swift 3, part 1: Dates, Calendars, and DateComponents

i just want to use dates

If you’re just getting started with date and time programming in Swift 3, chances are that you probably did some Googling, found something about NSDate and its companion classes in Apple’s documentation and promptly got confused. Let me reassure you that it isn’t your fault.

For starters, a lot of the documentation out there is in Objective-C, which can throw you off if you’re not familiar with its [instance method: parameter2: parameter3:] method-calling syntax. There’s also the fact that Apple recently removed the NS prefix from Cocoa’s class names — NSDate is now just plain ol’ Date, NSCalendar is now Calendar, NSDateComponents is now DateComponents, and so on. And finally, in the move towards protocol-oriented programming, Apple has changed some classes to structs, including many of the classes for working with dates and times.

And finally, if you’re coming to Swift 3 from JavaScript, which makes do with a single object type called Date, the idea of having this set of classes just to handle dates and times looks like overkill:

swift 3 date and time classes

Click the diagram to see it at full size.

In this series of articles, we’ll look at date and time programming in Swift 3. By its end, you’ll have a firm grasp on the topic.

Swift’s Date struct represents dates and times

swift date struct

Click the diagram to see it at full size.

In Swift, dates and times are represented by instances of Date, which is a struct. Date is independent of any time zone, or even any calendar system. It gets this independence through the way it represents time: as a number of seconds relative to the start of the Third Millennium, January 1, 2001, 00:00:00 UTC.

The following playground code shows the various ways of creating Dates:

// To create a Date representing the current date and time,
// simply initialize a new Date.
let now = Date()


// The other initializers expect arguments of type TimeInterval,
// which is a typealias for Double.


// To create a Date a specified number of seconds before or after
// the current date and time, use the "timeIntervalSinceNow" initializer.
let fiveMinutesAgo = Date(timeIntervalSinceNow: -5 * 60)
let fiveMinutesFromNow = Date(timeIntervalSinceNow: 5 * 60)


// To create a Date relative to Swift's reference date and time,
// use the "timeIntervalSinceReferenceDate" initializer.

// The first mobile phone call was made on April 3, 1973.
// We don't know the exact time the call was made, but it happened
// sometime during business hours in New York City, in the U.S. Eastern
// Time Zone. Noon that day was 875,602,800 seconds prior to the
// reference date and time.
let firstMobileCallDate = Date(timeIntervalSinceReferenceDate: -875_602_800)

// The "Stevenote" where the iPhone was introduced started on January 9, 2007,
// 10 a.m. Pacific time, 190,058,400 seconds after the reference date and time.
let iPhoneStevenoteDate = Date(timeIntervalSinceReferenceDate: 190_058_400)


// Unix time (a.k.a. POSIX time or Epoch Time) is the way time is represented
// by Unix, Unix-like, and other operating systems. It defines time as a 
// number of seconds after the Unix Epoch, January 1, 1970, 00:00:00 UTC.
// To create a Date relative to the Unix Epoch, use the 
// "timeIntervalSince1970" initializer.
let oneYear = TimeInterval(60 * 60 * 24 * 365)
let newYears1971 = Date(timeIntervalSince1970: oneYear)
let newYears1969 = Date(timeIntervalSince1970: -oneYear)


// To create a Date relative to another Date, use the
// "timeInterval:Since:" initializer.

// The "Stevenote" where the iPad was introduced started on January 27, 2010,
// 10 a.m. Pacific time, 96,249,600 seconds after the start of the iPhone Stevenote
// three years earlier.
let secondsBetweeniPhoneAndiPadStevenote = TimeInterval(96_249_600)
let iPadStevenoteDate = Date(timeInterval: secondsBetweeniPhoneAndiPadStevenote,
                             since: iPhoneStevenoteDate)

Of course, we don’t think of dates and times in terms of seconds relative to the start of the Third Millennium, or the start of the Unix Epoch, or any other arbitrary date and time. That’s why Swift features a couple of other structs to help us make sense of Dates: Calendar and DateComponents.

Calendars give dates context, DateComponents let us assemble dates or break dates apart

swift calendar struct

Click the diagram to see it at full size.

Think of the Calendar struct as a way to view Dates in a way that makes more sense to us: not as a number of seconds before or after January 1, 2001 00:00:00 UTC, but in terms of a year, month, day, hour, minute, and more.

The Calendar struct supports 16 different calendar systems, including the Gregorian calendar (a.k.a. the Western or Christian calendar), which is likely the one you use the most. Consider Swift’s reference date:

  • In a Date struct, its value is 0.
  • In the Gregorian calendar, this date has the following values:
    • Year: 2001
    • Month: 1
    • Day: 1
    • Hour: 0
    • Minute: 0
  • In the Hebrew calendar, this date has the following values:
    • Year: 5761
    • Month: 4
    • Day: 6
    • Hour: 0
    • Minute: 0
  • In the Buddhist calendar, this date has the following values:
    • Year: 2543
    • Month: 1
    • Day: 1
    • Hour: 0
    • Minute: 0

In most apps, you’ll likely be using the Gregorian calendar, so Calendar‘s real use is to convert Dates into DateComponents, and DateComponents into Dates:

swift datecomponents struct

Click the diagram to see it at full size.

The DateComponents struct is an assembly of properties that make up a date, such as year, month, date, hour, minute, second, and so on. DateComponents instances can be used to represent either a specific point in time, or a duration of time.

Using both Calendar and DateComponents structs, we can perform these key actions:

  • Build Dates using properties such as year, month, day, hour, and minute rather than a number of second relative to a reference date, and
  • extract properties from Dates, such as year, month, day, hour, and minute.

Let’s start creating some Dates with the help of Calendar and DateComponents.

Let’s create a Date given a year, month, and day, part 1

alexander graham bell

Let’s start with the first date in phone history: March 10, 1876, the day when Alexander Graham Bell made the first phone call. Create a new playground and enter or paste the code below:

// 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


// March 10, 1876: The day Alexander Graham Bell
// made the first land line phone call
// ---------------------------------------------
// DateComponents' init method is very thorough, but very long,
// especially when we're providing only 3 pieces of information
let firstLandPhoneCallDateComponents = DateComponents(calendar: nil,
                                                      timeZone: nil,
                                                      era: nil,
                                                      year: 1876,
                                                      month: 3,
                                                      day: 10,
                                                      hour: nil,
                                                      minute: nil,
                                                      second: nil,
                                                      nanosecond: nil,
                                                      weekday: nil,
                                                      weekdayOrdinal: nil,
                                                      quarter: nil,
                                                      weekOfMonth: nil,
                                                      weekOfYear: nil,
                                                      yearForWeekOfYear: nil)

// With a calendar and a year, month, and day defined in
// a DateComponents struct, we can build a date
let firstLandPhoneCallDate = userCalendar.date(from: firstLandPhoneCallDateComponents)!
firstLandPhoneCallDate.timeIntervalSinceReferenceDate

In the code, we:

  • Get the user’s current Calendar.
  • Create an DateComponents struct, firstLandPhoneCallDateComponents, providing values for the year, month, and day parameters, and nil for all the others.
  • Use the user’s Calendar to create firstLandPhoneCallDate using firstLandPhoneCallDateComponents.
  • Get the internal representation of the Date.

Here’s a screenshot of the playground code as seen on my computer, whose time zone is set to “US/Eastern” (UTC-5):

firstlandphonecalldate

Click the screenshot to see it at full size.

Note the results in the sidebar at the lower right-hand corner:

  • firstLandPhoneCallDate‘s value corresponds to the DateComponents properties we set: March 10, 1876, 12:00 a.m..
  • firstLandPhoneCallDate‘s internal value, contained within its timeIntervalSinceReferenceDate property, is -3,938,697,748, which indicates that Alexander Graham Bell’s inaugural phone call was made nearly 4 billion seconds prior to the start of the Third Millennium.

Let’s create a Date given a year, month, and day, part 2

martin cooper

Let’s try creating another momentous date in phone history: the day when Martin Cooper made the first cellular phone call, April 3, 1973. We’ll do it differently this time, by creating a blank DateComponents struct, and then setting its year, month, and day properties. Add the following code to the code above:

// (Previous code goes here)

// April 3, 1973: The day Martin Cooper
// made the first cellular phone call
// ------------------------------------
// This time, we'll create a blank DateComponents struct
// and set its year, month, and day properties.
var firstCellPhoneCallDateComponents = DateComponents()
firstCellPhoneCallDateComponents.year = 1973
firstCellPhoneCallDateComponents.month = 4
firstCellPhoneCallDateComponents.day = 3

let firstCellPhoneCallDate = userCalendar.date(from: firstCellPhoneCallDateComponents)!
firstCellPhoneCallDate.timeIntervalSinceReferenceDate

In the code, we:

  • Create an empty DateComponents struct, firstCellPhoneCallDateComponents.
  • Set the year, month, and day properties of firstCellPhoneCallDateComponents to correspond to the date April 3, 1973.
  • Use the user’s Calendar to create firstCellPhoneCallDate using firstCellPhoneCallDateComponents.
  • Get the internal representation of the Date.

Here’s a screenshot of the results:

firstcellphonecalldate

Click the screenshot to see it at full size.

Let’s create a Date: What date does National Donut Day — the first Friday in June — fall on in 2017?

Having come from Canada, the country with the world’s highest per capita donut shop concentration and the people who eat the most donuts per capita, I can assure you that National Donut Day has been a real thing since 1938. It takes place on the first Friday in June, and we can find out what date it falls on in 2017 — or any other year — through the judicious use of DateComponents properties.

Add the following code to the current playground:

// (Previous code goes here)

// The first Friday in June, 2017:
// National Donut Day
// -------------------------------
var donutDayComponents = DateComponents()
donutDayComponents.year = 2017
donutDayComponents.month = 6
// We're looking for a Friday...
donutDayComponents.weekday = 6
// ...and it needs to be the first Friday of the month
donutDayComponents.weekdayOrdinal = 1

let donutDayDate = userCalendar.date(from: donutDayComponents)!

You should be familiar with the year and month DayComponents properties by now, and we’re using a couple that may be new to you:

  • weekday: Specifies a day of the week. With the Gregorian calendar, valid values are 1 through 7, where 1 is Sunday, 2 is Monday, 3 is Tuesday, and so on. Since we’re looking for a Friday, we’ve set this value to 6.
  • weekdayOrdinal: Specifies the order of the given weekday in the next larger specified calendar unit. Since we set weekday to 6 and set this value to 1, and since the next largest specified calendar unit was month, we’ll get the date of the first Friday of the month.

If you check the value of donutDayDate in the playground’s sidebar, you should see Jun 2, 2017, 12:00 AM. If you look at a calendar, you’ll confirm that it is indeed the first Friday of June 2017.

changing stuff and seing what happens

In the spirit of the fake book cover shown above, let’s see what happens if we don’t specify the month. Comment out the following line from the code you just added…

donutDayComponents.month = 6

…so that the code now looks like this:

// The first Friday in June, 2017:
// National Donut Day
// -------------------------------
var donutDayComponents = DateComponents()
donutDayComponents.year = 2017
//donutDayComponents.month = 6
// We're looking for a Friday...
donutDayComponents.weekday = 6
// ...and it needs to be the first Friday of the month
donutDayComponents.weekdayOrdinal = 1

let donutDayDate = userCalendar.date(from: donutDayComponents)!

When you run the code, you’ll see that the date for donutDayDate is now Jan 6 2017, 12:00 AM. Now that we’re specifying only a year and not a month, Swift interprets the combination of donutDayComponents.weekday = 6 and donutDayComponents.weekdayOrdinal = 1 to mean “the first Friday of the year”. If you look at a calendar, you’ll confirm that January 6, 2017 is indeed the first Friday of the year.

Let’s create one more Date: 5:00.pm. on Thursday of the 18th week of 2017…in Tokyo.

The great thing about Swift’s Calendar class is that it does its best to work with the DateComponents that you give it, and DateComponents gives you all sorts of ways to specify a date. Let’s assume for a moment that you’re in Japan and want to leave work at the ridiculously early hour of 5 p.m. and have some relaxing Suntory times, and it just happens to be the 18th week of 2017. What’s the date?

The answer comes from this code:

// (Previous code goes here)

// Thursday on the 18th week of 2017 in Tokyo
// ------------------------------------------
var thursday5pm18thWeek2017TokyoDateComponents = DateComponents()
thursday5pm18thWeek2017TokyoDateComponents.year = 2017
thursday5pm18thWeek2017TokyoDateComponents.weekOfYear = 18
thursday5pm18thWeek2017TokyoDateComponents.weekday = 5
thursday5pm18thWeek2017TokyoDateComponents.hour = 17
thursday5pm18thWeek2017TokyoDateComponents.timeZone = TimeZone(identifier: "Asia/Tokyo")!

let thursday5pm18thWeek2017TokyoDate = userCalendar.date(from: thursday5pm18thWeek2017TokyoDateComponents)!

On my system, which is in the Eastern daylight time zone (UTC-4) at the time of writing, thursday5pm18thWeek2017TokyoDate displays as May 4, 2017, 4:00 AM in my playground’s sidebar, and looking at a calendar confirms that May 4th is indeed the Thursday of the 18th month of 2017.

Let’s extract DateComponents from a Date, part 1

Now that we’ve created some Dates using DateComponents, let’s do the reverse and extract DateComponents from given Dates. We’ll continue with our playground and use a Date we’ve already created: firstLandPhoneCallDate, which corresponds to the date of Alexander Graham Bell’s historic phone call, March 10, 1876. Here’s code that extracts the year, month, and day from this Date:

// (Previous code goes here)

// We want to extract the year, month, and day from firstLandPhoneCallDate
let alexanderGrahamBellDateComponents = userCalendar.dateComponents([.year, .month, .day],
                                                                    from: firstLandPhoneCallDate)
alexanderGrahamBellDateComponents.year     // 1876
alexanderGrahamBellDateComponents.month    // 3
alexanderGrahamBellDateComponents.day      // 10

Let’s extract DateComponents from a Date, part 2

This time, let’s create a new Date — one that corresponds to this key date in iOS history: the “Stevenote” where the original iPhone was first announced:

If you were to ask Swift when this Stevenote took place, it would reply “190,058,400 seconds after the reference date”. For most of us, this is a meaningless figure, so we’ll extract the following DateComponents from this Date:

  • Year
  • Month
  • Day
  • Hour
  • Minute
  • What day of the week this Date fell on
  • What week of the year this Date fell on

Here’s the code:

// (Previous code goes here)

// The "Stevenote" where the iPhone was announced took place
// 190,058,400 seconds after the start of the Third Millennium.
let iPhoneStevenoteDate = Date(timeIntervalSinceReferenceDate: 190_058_400)

// We want to extract the year, month, day, hour, and minute from this date,
// and we also want to know what day of the week and week of the year
// this date fell on.
let iPhoneStevenoteDateComponents = userCalendar.dateComponents(
      [.year, .month, .day, .hour, .minute, .weekday, .weekOfYear],
      from: iPhoneStevenoteDate)
iPhoneStevenoteDateComponents.year!        // 2007
iPhoneStevenoteDateComponents.month!       // 1
iPhoneStevenoteDateComponents.day!         // 9
iPhoneStevenoteDateComponents.hour!        // 13
iPhoneStevenoteDateComponents.minute!      // 0
iPhoneStevenoteDateComponents.weekday!     // 3 (Tuesday)
iPhoneStevenoteDateComponents.weekOfYear!  // 2 (2nd week of the year)

Let’s extract DateComponents from a Date, part 3

Let’s try it again with another key iOS date — the Stevenote where the original iPad was announced:

This time, if you were to ask Swift when this Stevenote took place, it would reply “286,308,000 seconds after the reference date”. Let’s get all the DateComponents for this date:

// (Previous code goes here)

// The "Stevenote" where the original iPad was announced took place
// 286,308,00 seconds after the start of the Third Millennium.
let iPadSteveNoteDate = Date(timeIntervalSinceReferenceDate: 286_308_000)

// We want to extract ALL the DateComponents.
let iPadSteveNoteDateComponents = userCalendar.dateComponents([.calendar,
                                                               .day,
                                                               .era,
                                                               .hour,
                                                               .minute,
                                                               .month,
                                                               .nanosecond,
                                                               .quarter,
                                                               .second,
                                                               .timeZone,
                                                               .weekday,
                                                               .weekdayOrdinal,
                                                               .weekOfMonth,
                                                               .weekOfYear,
                                                               .year,
                                                               .yearForWeekOfYear],
                                                              from: iPadSteveNoteDate)
iPadSteveNoteDateComponents.calendar?.identifier // gregorian
iPadSteveNoteDateComponents.day!                 // 27
iPadSteveNoteDateComponents.era!                 // 1
iPadSteveNoteDateComponents.hour!                // 13
iPadSteveNoteDateComponents.minute!              // 0
iPadSteveNoteDateComponents.month!               // 1
iPadSteveNoteDateComponents.nanosecond!          // 0
iPadSteveNoteDateComponents.quarter!             // 0
iPadSteveNoteDateComponents.second!              // 0
iPadSteveNoteDateComponents.timeZone!            // Eastern time zone
iPadSteveNoteDateComponents.weekday!             // 4 (Wednesday)
iPadSteveNoteDateComponents.weekdayOrdinal!      // 4 (4th Wednesday in the month)
iPadSteveNoteDateComponents.weekOfMonth!         // 5 (5th week of the month)
iPadSteveNoteDateComponents.weekOfYear!          // 5 (5th week of the year)
iPadSteveNoteDateComponents.year!                // 2010
iPadSteveNoteDateComponents.yearForWeekOfYear!   // 2010

Let’s take a look at each DateComponents property and what it represents:

Property Description
calendar The calendar system for the date represented by this set of DateComponents. We got these DateComponents by converting a Date using a Gregorian Calendar, so in this case, this value is gregorian.
day The day number of this particular date and time. For January 27, 2010, 18:00:00 UTC, this value is 27.
era The era for this particular date, which depends on the date’s calendar system. In this case, we’re using the Gregorian calendar, which has two eras:

  • BCE (a.k.a. BC), represented by the integer value 0
  • CE (a.k.a. AD), represented by the integer value 1
hour The hour number of this particular date and time. For January 27, 2010, 18:00:00 UTC, this value is 13, because in my time zone, 18:00:00 UTC is 13:00:00.
minute The minute number of this particular date and time. For January 27, 2010, 18:00:00 UTC, this value is 0.
month The month number of this particular date and time. For January 27, 2010, 18:00:00 UTC, this value is 1.
nanosecond The nanosecond number of this particular date and time. For January 27, 2010, 18:00:00 UTC, this value is 0.
quarter The quarter number of this particular date and time. January 27, 2010, 18:00:00 UTC, is in the first quarter of the year, so this value is 0.
second The second number of this particular date and time. For January 27, 2010, 18:00:00 UTC, this value is 0.
timeZone The time zone of this particular date and time. I’m in the UTC-5 time zone (US Eastern), so this value is set to that time zone.
weekday The day of the week of this particular date and time. In the Gregorian calendar, Sunday is 1, Monday is 2, Tuesday is 3, and so on. January 27, 2010, was a Wednesday, so this value is 4.
weekdayOrdinal The position of the weekday within the next larger specified calendar unit, which in this case is a month. So this specifies nth weekday of the given month. Jauary 27, 2010 was on the 4th Wednesday of the month, so this value is 4.
weekOfMonth The week of the month of this particular date and time. January 27, 2010 fell on the 5th week of January 2010, so this value is 5.
weekOfYear The week of the year of this particular date and time. January 27, 2010 fell on the 5th week of 2010, so this value is 5.
year The year number of this particular date and time. For January 27, 2010, 18:00:00 UTC, this value is 2010.
yearForWeekOfYear Oh wow, this is so hard to explain that I’ll leave it to Apple’s docs.

Wrapping it all up

Here’s the playground containing all the code we just worked 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


// March 10, 1876: The day Alexander Graham Bell
// made the first land line phone call
// ---------------------------------------------
// DateComponents' init method is very thorough, but very long,
// especially when we're providing only 3 pieces of information
let firstLandPhoneCallDateComponents = DateComponents(calendar: nil,
                                                      timeZone: nil,
                                                      era: nil,
                                                      year: 1876,
                                                      month: 3,
                                                      day: 10,
                                                      hour: nil,
                                                      minute: nil,
                                                      second: nil,
                                                      nanosecond: nil,
                                                      weekday: nil,
                                                      weekdayOrdinal: nil,
                                                      quarter: nil,
                                                      weekOfMonth: nil,
                                                      weekOfYear: nil,
                                                      yearForWeekOfYear: nil)

// With a calendar and a year, month, and day defined in
// a DateComponents struct, we can build a date
let firstLandPhoneCallDate = userCalendar.date(from: firstLandPhoneCallDateComponents)!
firstLandPhoneCallDate.timeIntervalSinceReferenceDate


// April 3, 1973: The day Martin Cooper
// made the first cellular phone call
// ------------------------------------
// This time, we'll create a blank DateComponents struct
// and set its year, month, and day properties.
var firstCellPhoneCallDateComponents = DateComponents()
firstCellPhoneCallDateComponents.year = 1973
firstCellPhoneCallDateComponents.month = 4
firstCellPhoneCallDateComponents.day = 3

let firstCellPhoneCallDate = userCalendar.date(from: firstCellPhoneCallDateComponents)!
firstCellPhoneCallDate.timeIntervalSinceReferenceDate


// The first Friday in June, 2017:
// National Donut Day
// -------------------------------
var donutDayComponents = DateComponents()
donutDayComponents.year = 2017
donutDayComponents.month = 6
// We're looking for a Friday...
donutDayComponents.weekday = 6
// ...and it needs to be the first Friday of the month
donutDayComponents.weekdayOrdinal = 1

let donutDayDate = userCalendar.date(from: donutDayComponents)!


// Thursday on the 18th week of 2017 in Tokyo
// ------------------------------------------
var thursday5pm18thWeek2017TokyoDateComponents = DateComponents()
thursday5pm18thWeek2017TokyoDateComponents.year = 2017
thursday5pm18thWeek2017TokyoDateComponents.weekOfYear = 18
thursday5pm18thWeek2017TokyoDateComponents.weekday = 5
thursday5pm18thWeek2017TokyoDateComponents.hour = 17
thursday5pm18thWeek2017TokyoDateComponents.timeZone = TimeZone(identifier: "Asia/Tokyo")!

let thursday5pm18thWeek2017TokyoDate = userCalendar.date(from: thursday5pm18thWeek2017TokyoDateComponents)!


var tokyoCalendar = Calendar(identifier: .ethiopicAmeteAlem)
tokyoCalendar.timeZone = TimeZone(identifier: "Asia/Tokyo")!


firstLandPhoneCallDate.timeIntervalSinceReferenceDate

// We want to extract the year, month, and day from firstLandPhoneCallDate
let alexanderGrahamBellDateComponents = userCalendar.dateComponents([.year, .month, .day],
                                                                    from: firstLandPhoneCallDate)
alexanderGrahamBellDateComponents.year     // 1876
alexanderGrahamBellDateComponents.month    // 3
alexanderGrahamBellDateComponents.day      // 10


// The "Stevenote" where the original iPhone was announced took place
// 190,058,400 seconds after the start of the Third Millennium.
let iPhoneStevenoteDate = Date(timeIntervalSinceReferenceDate: 190_058_400)

// We want to extract the year, month, day, hour, and minute from this date,
// and we also want to know what day of the week and week of the year
// this date fell on.
let iPhoneStevenoteDateComponents = userCalendar.dateComponents(
      [.year, .month, .day, .hour, .minute, .weekday, .weekOfYear],
      from: iPhoneStevenoteDate)
iPhoneStevenoteDateComponents.year!        // 2007
iPhoneStevenoteDateComponents.month!       // 1
iPhoneStevenoteDateComponents.day!         // 9
iPhoneStevenoteDateComponents.hour!        // 13
iPhoneStevenoteDateComponents.minute!      // 0
iPhoneStevenoteDateComponents.weekday!     // 3 (Tuesday)
iPhoneStevenoteDateComponents.weekOfYear!  // 2 (2nd week of the year)


// The "Stevenote" where the original iPad was announced took place
// 286,308,00 seconds after the start of the Third Millennium.
let iPadSteveNoteDate = Date(timeIntervalSinceReferenceDate: 286_308_000)

// We want to extract ALL the DateComponents.
let iPadSteveNoteDateComponents = userCalendar.dateComponents([.calendar,
                                                               .day,
                                                               .era,
                                                               .hour,
                                                               .minute,
                                                               .month,
                                                               .nanosecond,
                                                               .quarter,
                                                               .second,
                                                               .timeZone,
                                                               .weekday,
                                                               .weekdayOrdinal,
                                                               .weekOfMonth,
                                                               .weekOfYear,
                                                               .year,
                                                               .yearForWeekOfYear],
                                                              from: iPadSteveNoteDate)
iPadSteveNoteDateComponents.calendar?.identifier // gregorian
iPadSteveNoteDateComponents.day!                 // 27
iPadSteveNoteDateComponents.era!                 // 1
iPadSteveNoteDateComponents.hour!                // 13
iPadSteveNoteDateComponents.minute!              // 0
iPadSteveNoteDateComponents.month!               // 1
iPadSteveNoteDateComponents.nanosecond!          // 0
iPadSteveNoteDateComponents.quarter!             // 0
iPadSteveNoteDateComponents.second!              // 0
iPadSteveNoteDateComponents.timeZone!            // Eastern time zone
iPadSteveNoteDateComponents.weekday!             // 4 (Wednesday)
iPadSteveNoteDateComponents.weekdayOrdinal!      // 4 (4th Wednesday in the month)
iPadSteveNoteDateComponents.weekOfMonth!         // 5 (5th week of the month)
iPadSteveNoteDateComponents.weekOfYear!          // 5 (5th week of the year)
iPadSteveNoteDateComponents.year!                // 2010
iPadSteveNoteDateComponents.yearForWeekOfYear!   // 2010

In the next installment, we’ll look at converting Dates to Strings, and vice versa.