Categories
Programming

How to work with dates and times in Swift 5, part 1: Creating and deconstructing dates with the Date, Calendar, and DateComponents structs

If you’re just getting started with date and time programming in Swift and looked at Apple’s documentation, you probably got confused. Let me reassure you that it isn’t your fault. For starters, Apple’s documentation hasn’t been very good lately, and I’m not the only developer who’s noticed this decline in quality.

There’s also the fact that working with dates and times in Swift seems unnecessarily complicated. If you’ve come to Swift from other programming languages, such as JavaScript, which uses a single object type called Date, the idea of having this set of classes just to handle dates and times looks like overkill:

Tap the image to see it at full size.

I promise you that there’s a method to this madness. Swift’s set of date and time structs make for a super-flexible system that will let you keep store, calculate, and display dates and times no matter what time zone, calendar system, or language you use.

For example, with Swift, I can easily schedule an appointment for the third Wednesday of July at 3:30 p.m. Pacific and then display that date as it would appear in the Coptic Calendar system in Melbourne, Australia. If you had to do that in JavaScript, the only easy answer is “go away” (there are, of course, some less polite variations of that answer).

This series, How to work with dates and times in Swift 5, will help you make sense of the set of classes that Swift provides for dealing with timekeeping and time calculations. It will do so with a lot of examples and experimentation. I strongly recommend that you fire up Xcode, open a Swift playground, and follow along.

In this article, I’ll show you the following:

  • The Date struct, which stores date and time values.
  • The Calendar struct, which represents one of 16 different calendar systems, and provides a meaningful context for Dates.
  • The DateComponents struct, which a collection of date and time components, such as years, months, days, hours, minutes, and so on.
  • How to create a Date object representing the current date and time.
  • How to create a Date object representing a given date and time.
  • How to create a Date object based on criteria such as “the first Friday of June 2020.”
  • How to extract the parts of a date, such as the year, month, day, time, and so on from a Date object.

The Date struct

Tap the image to see it at full size.

Swift uses the Date struct represent dates and times, and it’s designed to do so in the most flexible way possible: as a number of seconds relative to the start of the Third Millennium, January 1, 2001, 00:00:00 UTC.

This approach allows Date to be independent of any time zone or any calendar system; you store it as an amount of time before or after the start of the Third Millennium and use other objects (which you’ll see soon) to convert into the appropriate calendar, time zone, and format.

To create a Date object containing the current date and time, simply create an instance of Date using the default, no-argument initializer.

Open a playground in Xcode and add the following:

To see the value stored inside the newly created Date object, use Date’s timeIntervalSinceReferenceDate property, which contains the number of seconds since the reference date.

Add the following to the playground, then run it:

When I ran that line of code (just before 9:00 a.m. EDT on Tuesday, May 26, 2020), I got this output: It’s been 612190714.691352 seconds since the start of the Third Millennium.

To see the Date’s value in a more meaningful form, use Date’s description property to see it in “YYYY-MM-DD HH:MM:SS +HHMM” format, or the description(with:) method with a Locale instance to display its value using a specified locale.

Add the following to the playground, then run it:

To create a Date object containing a date and time that isn’t the current date and time, and without other helper objects, you have to use one of the following initializers:

Date initializer What it does
Date(timeIntervalSinceNow:)

Creates a Date that is the given number of seconds before or after the current date and time.

Parameters:

  • timeIntervalSinceNow:The number of seconds before or after the current date and time, expressed as a TimeInterval (which is just a typealias for Double). Positive values denote seconds after the current date and time, negative values denote seconds before the current date and time.
Date(timeIntervalSinceReferenceDate:)

Creates a Date that is the given number of seconds before or after Swift’s reference Date, January 1, 2001 at 00:00:00 UTC.

Parameters:

  • timeIntervalSinceReferenceDate:The number of seconds before or after the Swift reference date, expressed as a TimeInterval (which is just a typealias for Double). Positive values denote seconds after the reference date, negative values denote seconds before the reference date.
Date(timeIntervalSince1970:)

Creates a Date that is the given number of seconds before or after the Unix Epoch (Unix’s reference date), which is January 1, 1970 at 00:00:00 UTC. This initializer was included for compatibility with Unix systems.

Parameters:

  • timeIntervalSince1970:The number of seconds before or after the Unix reference date, expressed as a TimeInterval (which is just a typealias for Double). Positive values denote seconds after the reference date, negative values denote seconds before the reference date.
Date(timeInterval:since:)

Creates a Date that is the given number of seconds before or after the given reference date.

Parameters:

  • timeInterval:The number of seconds before or after the given reference date, expressed as a TimeInterval (which is just a typealias for Double). Positive values denote seconds after the reference date, negative values denote seconds before the reference date.
  • since: The reference date, expressed as a Date.

Here are some examples showing these initializers in action — add the following to the playground, then run it:

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, and DateComponents let us assemble dates or break dates apart.

The Calendar struct

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 date in a calendar system.

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.

The DateComponents struct

Tap the image to see it at full size.

The DateComponents struct allows us to assemble a point in time or a length of time out of components such as year, month, day, hour, minute, and more. We’ll use DateComponents to construct a Date object using a year, month, day and time, and also deconstruct a Date object into a year, month, day and time.

Consider Swift’s reference date: January 1, 2001 at 00:00:00 UTC. Here are what its components are in various calendar systems:

Calendar Date components for January 1, 2001 00:00:00 UTC
Gregorian
  • Year: 2001
  • Month: 1
  • Day: 1
  • Hour: 0
  • Minute: 0
Hebrew
  • Year: 5761
  • Month: 4
  • Day: 6
  • Hour: 0
  • Minute: 0
Buddhist
  • Year: 2543
  • Month: 1
  • Day: 1
  • Hour: 0
  • Minute: 0

Let’s use Calendar and DateComponents to make it easier to create Dates.

Creating known Dates with Calendar and DateComponents

alexander graham bell

Let’s create a Date based on the first moment in phone history: March 10, 1876, when Alexander Graham Bell made the first phone call. I don’t know what time he made the call on that day, so for this example, I’m going to assume that it happened at 1:00 p.m..

Add the following to the playground, then run it:

In the code, we:

  1. Get the user’s current Calendar.
  2. Create a DateComponents struct, firstLandLineCallDateComponents, providing values for the timeZone:, year:month:, and day:, hour:, minute:, and second: parameters, and nil for all the others.  Bell made the call in his laboratory in Boston, whose time zone is UTC-5. We construct the time zone using the TimeZone(secondsFromGMT:) constructor, which specifies how many seconds the given time zone is ahead or behind UTC (GMT — Greenwich Meridian Time — is the old name for UTC).
  3. Use the user’s Calendar to create firstLandLineCallDate using firstLandLineCallDateComponents.
  4. Print the date of the call using the American English format as well as in terms of seconds since the start of the Third Millennium. Since the call happened over a century before the Third Millennium, this number is negative.

On my computer, the output is:

The first land line phone call happened on Friday, March 10, 1876 at 12:03:58 PM GMT-04:56:02.
That’s -3938655600.0 seconds since the start of the Third Millennium.

Let’s create another date: December 9, 1968 at 3:45 p.m. Pacific, when Douglas Englebart changes the world of computing with his demonstration of the GUI, mouse, chording keyboard, hypertext links, and collaborative document editing — a session that would come to be called “The Mother of All Demos.”

Add the following to the playground, then run it:

In the code, we:

  1. Create a DateComponents struct, motherOfAllDemosDateComponents, providing values for only the parameters that matter t us: timeZone:, year:month:, and day:, hour:, and  minute:. This time, we construct the time zone using the TimeZone(identifier:) constructor, which specifies the time zone by the appropriate TZ database name.
  2. Use the user’s Calendar (userCalendar, which we’ve already declared) to create motherOfAllDemosDate using motherOfAllDemosDateComponents.
  3. Print the date of the call using the American English format as well as in terms of seconds since the start of the Unix Epoch. Since the call happened a little over a year before the Unix Epoch, this number is negative.

On my computer, the output is:

The Mother of All Demos happened on Monday, December 9, 1968 at 6:45:00 PM GMT-05:00.
That’s -33437700.0 seconds since the start of the Unix Epoch.

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.

Add the following to the playground, then run it:

In the code, we:

  1. Create an empty DateComponents struct, firstCellCallDateComponents. Note that we’re using var instead of let to declare it; that’s because we’re going to set its properties after it’s been declared, and you can’t do that to a struct declared with let.
  2. Set the year, month, and day properties of firstCellCallDateComponents to correspond to the date April 3, 1973 in Eastern Standard Time. This time, we construct the time zone using the TimeZone(abbreviation:) constructor, which specifies the time zone by the appropriate time zone abbreviation.
  3. Use the user’s Calendar to create firstCellCallDate using firstCellCallDateComponents.

On my computer, the output is:

Martin Cooper made the first cellular call on Tuesday, April 3, 1973 at 12:00:00 AM Eastern Standard Time.
That’s 136098900.0 seconds since the Mother of All Demos.

Note that in the absence of a specified time, the assumed time is 00:00:00.

Discovering Dates with Calendar and DateComponents

So far, we’ve created Dates based on known dates and times — March 10, 1876, December 9, 1968, and April 3, 1973. How about Dates where we don’t have a specific date, but have enough criteria to specify a date? 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.

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, and it takes place on the first Friday in June. We can find out what date it falls on in 2020 — or any other year — through the judicious use of DateComponents properties.

Add the following to the playground, then run it:

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.

On my computer, the output is:

Donut Day 2020 happens on Friday, June 5, 2020 at 12:00:00 AM Eastern Daylight Time.

Note that in the absence of a specified time zone, the assumed time zone is the system time zone, which in my case is Eastern Daylight Time (UTC-4).

changing stuff and seing what happens

In the spirit of the fake book cover shown above, let’s see what happens when we use Donut Day 2020’s components, except for the month.

Add the following to the playground, then run it:

On my computer, the output is:

Mystery Friday happens on Friday, January 3, 2020 at 12:00:00 AM Eastern Standard Time.

That makes sense: By specifying year 2020, weekday 6 and weekday ordinal 1, we just asked for the first Friday of the year.

Suppose you’re meeting up with a friend in Tokyo some relaxing Suntory times at 9:00 p.m. on the Thursday of the 27th week of 2020. What is that date?

The answer comes from this code:

On my computer, the output is:

Thursday on the 27th week of 2020 at 9:00 p.m. Tokyo time is Thursday, July 2, 2020 at 8:00:00 AM Eastern Daylight Time.

Here’s a simple question: What’s the 234th day of 2020?

Add the following to the playground, then run it:

On my computer, the output is:

The 234th day of 2020 is Friday, August 21, 2020 at 12:00:00 AM Eastern Daylight Time.

The “Ten Thousand Hour Rule”, which states that it takes 10,000 hours of directed practice to become an expert at something, was popularized by Malcolm Gladwell in his novel Blink. There’s been a lot of debate about the truth of the rule, and we’re going to side-step it to ask a related question: If I set out to get 10,000 hours of non-stop practice starting on midnight of January 1, 2020, when would I be done?

Add the following to the playground, then run it:

On my computer, the output is:

Your 10,000 hours would complete on Saturday, February 20, 2021 at 4:00:00 PM Eastern Standard Time.

In other words, 10,000 hours is longer than a year.

Let’s look at the case of overflow. What happens if you try to create a Date using components that would define the nonsense date September 50th, 2020?

Add the following to the playground, then run it:

On my computer, the output is:

September 50, 2020 is actually Tuesday, October 20, 2020 at 12:00:00 AM Eastern Daylight Time.

Swift treats these date components as “the start of September, plus 50 days” — September’s 30 days, plus an additional 20 days into October.

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

Let’s extract the year, month, and day from firstLandPhoneCallDate, which corresponds to the date of Alexander Graham Bell’s historic phone call, March 10, 1876:

When I ran it on my computer, the output was:

The first land line phone call happened 4550860374.044979 seconds ago.
Year: 1876
Month: 3
Day: 10

Let’s extract DateComponents from a Date, part 2

This time, let’s extract the DateComponents from another Date we’d previously defined: The date and time of the “Stevenote” where the original iPhone was first announced:

It happened 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 it fell on
  • What week of the year it fell on

Here’s the code:

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:

When I ran it on my computer, the output was:

The original iPad Stevenote happened 325902655.723246 seconds ago.
Calendar: gregorian
Day: 27
Era: 1
Hour: 10
Minute: 0
Month: 1
Nanosecond: 0
Quarter: 0
Second: 0
Time zone: America/Los_Angeles (fixed)
Weekday: 4
Weekday ordinal: 4
Week of month: 5
Week of year: 5
Year: 2010
Year for week of year: 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 complete code for the playground containing all the code we just worked with:

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

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

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

Here are the articles in this series:

2 replies on “How to work with dates and times in Swift 5, part 1: Creating and deconstructing dates with the Date, Calendar, and DateComponents structs”

Leave a Reply

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