At first glance, working with dates and times in Swift seems unnecessarily complicated. While JavaScript manages to work with the Date
class, Swift has this collection of classes and structs:
Some developers think that Swift’s system for working with dates and times is overengineered, but that’s because they hold mistaken beliefs about time. Swift provides a powerful system for storing, displaying, and performing calculations on dates and times that’s flexible enough to work across date and time formats, time zones, languages, and even different calendar systems.
For example, in a handful of lines of Swift code, you can determine the date for the third Wednesday of July at 3:30 p.m. Pacific Time and then display that date as it would appear in the Coptic calendar system in Melbourne, Australia’s time zone. In fact, the final task in this tutorial will be to write this code. (I don’t want to even think about what it would take to write the equivalent code in JavaScript.)
This tutorial will introduce you to the objects that Swift provides for working with dates and times. You’ll learn how to create date/time objects in Swift, extract information from them, and display their values to your users. It’s an interactive exercise that you’ll perform in a Swift playground so that you can learn by doing.
👀 Look for the 🛠 emoji if you’d like to skim through the content while focusing on the build and execution steps.
💻 You can find an Xcode playground with all the code in this article in this GitHub repository — it’s
intro-dates-times-swift-1.playground
.
Creating Dates: The Basics
Swift uses the Date
struct to represent dates and times. Every Date
instance stores a Double
value representing seconds relative to the start of the Third Millennium — January 1, 2001, 00:00:00 UTC. Positive values represent dates and times after this reference point, while negative values represent dates and times before this reference point.
Creating an object representing the current date and time
Let’s create a Date
instance representing the current date and time.
🛠️ Open Xcode, start a new blank macOS playground (I find them more reliable and less crash-prone than iOS playgrounds), then add and run the following code:
import Cocoa
let momentInTime = Date() // Current date and time
print("momentInTime contains the current date and time!")
print("• momentInTime.description: \(momentInTime.description)")
Here’s how the output looked on my computer when I ran it:
momentInTime contains the current date and time!
• momentInTime.description: 2023-12-13 16:44:32 +0000
Date
’s description
property returns the date and time value as a string specifying the date, time, and time zone stored in the Date
instance. You’ll be running the code on a different day and time, so its output will be different. However, they’ll have the same “date-time-time zone” format. Note that the time zone is +0000
, or 0 hours and 0 minutes apart from Coordinated Universal Time, or UTC for short.
Getting the value of a Date
Date
has very few properties for extracting the date and time, and the values they provide aren’t convenient:
Date property | What it does |
---|---|
timeIntervalSinceNow | Returns the number of seconds between the date and time represented by the Date instance and the current date and time. If the Date instance is in the past, this is a negative number. |
timeIntervalSinceReferenceDate | Returns the number of seconds between the date and time represented by the Date instance and Swift’s reference date, January 1, 2001 at 00:00:00 UTC. If the Date instance represents a date and time before the reference date, this is a negative number. |
timeIntervalSince1970 | Returns the number of seconds between the date and time represented by the Date instance and the start of the Unix epoch, January 1, 1970 at 00:00:00 UTC. Many Unix-based systems and programming languages (such as JavaScript) use this as their reference date. If the Date instance represents a date and time before the start of the Unix epoch, this is a negative number. |
🛠️ Try them out by adding the following to the playground and running it:
let secondsAgo = -momentInTime.timeIntervalSinceNow
print("• momentInTime happened \(secondsAgo) seconds ago,")
let secondsSinceReferenceDate = momentInTime.timeIntervalSinceReferenceDate
print("• \(secondsSinceReferenceDate) seconds since January 1, 2001,")
let secondsSinceUnixEpochDate = momentInTime.timeIntervalSince1970
print("• \(secondsSinceUnixEpochDate) seconds since January 1, 1970.")
The output should tell you that momentInTime
happened:
- a number of seconds since you ran the previous code snippet,
- over 724 million seconds since January 1, 2001, and
- over 1.7 billion seconds since January 1, 1970.
These values, while correct, aren’t meaningful to most people. The exercises in this tutorial will cover better ways of getting the values out of a Date
instance.
Getting localized strings for Date
objects
Date
has a description(with:)
method that returns the date and time value as a string based on the Locale
object that you pass to it. Think of Locale
as a way of describing the language, culture, and conventions that should be used when presenting information to the user.
🛠️ Add this code to the playground and run it:
print("Date descriptions in different locales:")
// US English
print("• en-US: \(momentInTime.description(with: Locale(identifier: "en-US")))")
// UK English
print("• en-GB: \(momentInTime.description(with: Locale(identifier: "en-GB")))")
// Spanish (general)
print("• es: \(momentInTime.description(with: Locale(identifier: "es")))")
// Simplified Chinese
print("• zh-Hans: \(momentInTime.description(with: Locale(identifier: "zh-Hans")))")
You’ll see momentInTime
displayed in four locale’s languages, in the format appropriate for that locale. description(with:)
displays a Date
instance’s date and time using the time zone of the system it’s running on instead of UTC.
Locale
’savailableIdentifiers
property contains the identifiers for all the locales supported by iOS. These identifiers are also listed in this chart.
Creating dates and times the hard way
In addition to the default initializer, Date
also has a set of initializers for creating dates and times by specifying the number of seconds before or after a reference date and time. They’re listed in the table below:
Initializer | What it does |
---|---|
Date | Creates a Date representing the date and time for the given number of seconds before or after the current date and time. |
Date | Creates a Date representing the date and time for the given number of seconds before or after Swift’s reference date, January 1, 2001 at 00:00:00 UTC. |
Date | Creates a Date representing the date and time for the given number of seconds before or after the start of the Unix epoch, January 1, 1970 at 00:00:00 UTC. Many Unix-based systems and programming languages (such as JavaScript) use this as their reference date. |
Date | This one takes two arguments: a number of seconds and a Date instance. It creates a Date representing the date and time for the given number of seconds before or after the date and time represented by the given Date instance. |
Let’s use these initializers to create Date
objects for the following events:
- 5 seconds in the past
- 8 minutes in the future
- The start of Steve Jobs’ keynote (“Stevenote”) where he introduced the first iPhone: January 9, 2007 at 10:00 a.m. Pacific Standard Time (UTC-8)
- The start of Steve Jobs’ keynote where he introduced the first iPad: January 27, 2010 at 10:00 a.m. Pacific Standard Time (UTC-8)
- The start of Tim Cook’s keynote (“Timnote”) where he introduced Apple Silicon: June 22, 2020 at 10:00 a.m. Pacific Daylight Time (UTC-7)
🛠️ Add the following to the playground and run it:
// We’ll use the user’s current locale over and over,
// so let’s put it into a handy variable.
// `Locale.autoupdatingCurrent` always contains the current locale,
// even when the user changes their settings.
var userLocale = Locale.autoupdatingCurrent
// Let’s create some dates by...
// ...specifying a number of seconds before or after the current time:
let fiveSecondsAgo = Date(timeIntervalSinceNow: -5)
let eightMinutesFromNow = Date(timeIntervalSinceNow: 8 * 60)
print("\nCreating dates and times the hard way:")
print("• 5 seconds ago, it was \(fiveSecondsAgo.description(with: userLocale)).")
print("• 8 minutes from now, it will be \(eightMinutesFromNow.description(with: userLocale)).")
// ...specifying the number of seconds before or after
// the start of the Third Millennium:
let iPhoneStevenoteSwiftInterval = 190_058_400.0
// (Swift ignores underscores in numbers, so we’re using them
// to group digits to make the number easier to read.)
var iPhoneStevenoteDate = Date(timeIntervalSinceReferenceDate: iPhoneStevenoteSwiftInterval)
print("• The iPhone Stevenote took place on \(iPhoneStevenoteDate.description(with: userLocale)).")
// ...specifying the number of seconds before or after
// the start of the Unix epoch:
let iPadStevenoteUnixInterval = 1_264_615_200.0
var iPadStevenoteDate = Date(timeIntervalSince1970: iPadStevenoteUnixInterval)
print("• The iPad Stevenote took place on \(iPadStevenoteDate.description(with: userLocale)).")
// ...specifying the number of seconds before or after
// another date. The Apple Silicon Timnote took place
// 328,230,000 seconds after the iPad Stevenote:
let appleSiliconTimnoteIPadStevenoteInterval = 328_230_000.0
var appleSiliconTimnoteDate = Date(
timeInterval: appleSiliconTimnoteIPadStevenoteInterval,
since: iPadStevenoteDate
)
print("• The Apple Silicon Timnote took place on \(appleSiliconTimnoteDate.description(with: userLocale)).")
You should see the dates and times for five seconds ago, eight minutes in the future, and for the keynotes announcing the original iPhone, the original iPad, and Apple Silicon. These dates and times will be expressed in your system’s local time, in a format determined by your system’s Locale
settings.
The code works, but once again, most people don’t tell time in terms of seconds before or after a reference date. Let’s look at a more human-friendly way to define dates and times.
Creating dates and times the simpler way with DateFormatter
The DateFormatter
class works in conjunction with Date
to do two things:
- It converts strings containing various formats of date and time information into
Date
instances, and - it also creates string representations of
Date
instances in various formats.
Let’s use DateFormatter
to create Date
instances using parameters that are much easier to understand. Let’s use the date and time of “The Mother of All Demos,” where computer engineer Doug Englebart demonstrated so many elements of modern computing for the first time. It happened on December 9, 1968, at 3:45 p.m. Pacific Standard Time (UTC-8).
🛠️ Add this code to the playground and run it:
let myDateFormatter = DateFormatter()
// 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)
myDateFormatter.dateFormat = "MMMM d, y 'at' h:mm a, zzzz"
var motherOfAllDemosDate = myDateFormatter.date(from: "December 9, 1968 at 3:45 PM, Pacific Standard Time")!
print("\nCreating dates and times with DateFormatter:")
print("• The Mother of All Demos took place on \(motherOfAllDemosDate.description(with: userLocale)).")
When you run the playground, the last line of its output will be the date and time of the Mother of All Demos, displayed in your local time and system locale’s language and format.
Let’s take a closer look at the first two lines of the code you just entered:
let myDateFormatter = DateFormatter()
myDateFormatter.dateFormat = "MMMM d, y 'at' h:mm a, zzzz"
The first line is pretty straightforward: it creates a new instance of DateFormatter
named myDateFormatter
. I mention it only to point out that there’s some general agreement among Swift developers that creating DateFormatter
objects is computationally expensive. If you need to use a DateFormatter
often, it’s better to create a dedicated instance for each use case and reuse it when needed.
The second line specifies the format of the string that myDateFormatter
should expect to convert into a Date
. Here are the formatting strings used in setting myDateFormatter
’s dateFormat
property:
MMMM
: The name of a month, spelled out in full. For locales whose language is English, this is one of a set of full month names fromJanuary
throughDecember
.d
: The day of the month. A singled
means that this can either be a single digit (e.g.:1
) or a double-digit with a leading zero for single-digit days (e.g.:01
).y
: The year. A singled
means that this can either be a single digit long and up to four digits long. \h
: The hour. A singleh
means that this can either be a single digit (e.g.:1
) or a double-digit with a leading zero for single-digit hours (e.g.:01
).mm
: The minute. The doublem
means single-digit minutes should be zero-padded (e.g.:01
).zzzz
: The time zone name, spelled out in full (you may want to consult this list of time zone abbreviations and full names).
For a full list of the formatting strings for DateFormatter
, consult the Unicode standard for specifying date format patterns.
Let’s look at the last two lines of that code:
var motherOfAllDemosDate = myDateFormatter.date(from: "December 9, 1968 at 3:45 PM, Pacific Standard Time")!
print("The Mother of All Demos took place on \(motherOfAllDemosDate.description(with: userLocale)).")
The first line uses myDateFormatter
’s date(from:)
method to create a Date
based on the argument provided to the from:
parameter. This method’s return type is the optional Date?
, which means it returns a Date
if the provided argument is in the expected format and can be converted into a valid Date
, or nil
otherwise.
The second line prints the date when the Mother of All Demos took place.
If you’re given the task of converting many date strings in some arbitrary format — perhaps your app has to process a CSV file or database table — you’ll find DateFormatter
’s string-to-Date
conversion capability very useful.
Creating dates and times in an even simpler way with ISO8601DateFormatter
Given that there are several different formats for dates and times, we’re fortunate that many applications and services follow the ISO 8601 standard for expressing date and time information in string form.
Information | ISO 8601 format |
---|---|
Date of the Mother of All Demos | 1968-12-09 |
Date and time of the Mother of All Demos in UTC | 1968-12-09T23:45:00Z or 1968-12-09T23:45:00+00:00 |
Date and time of the Mother of All Demos in Pacific Standard Time (UTC-8) | 1968-12-09T15:45:00-08:00 |
ISO8601DateFormatter
is a class specifically for converting date strings in ISO 8601 format into Date
instances and vice versa. Let’s redefine motherOfAllDemosDate
using ISO8601DateFormatter
.
🛠️ Add the following to the playground and run it:
let iso8601DateFormatter = ISO8601DateFormatter()
motherOfAllDemosDate = iso8601DateFormatter.date(from: "1968-12-09T15:45:00-08:00")!
print("\nOnce again, the Mother of All Demos took place on \(motherOfAllDemosDate.description(with: userLocale)).")
Once again, the last line of output will be the date and time of the Mother of All Demos, displayed in your local time and system locale’s language and format.
Creating dates with ISO8601DateFormatter
requires one less line than doing so with DateFormatter
.
Creating Date
s with Calendar
and DateComponents
Given a specific date like “December 9, 1968” or a specific date and time like “December 9, 1968 at 3:45 p.m. Pacific Standard Time,” it’s a fairly straightforward process to create a Date
using a DateFormatter
or ISO8601DateFormatter
.
However, you may be asked to create dates and times based on information that doesn’t include a given month or day in the Gregorian calendar (also known as “the Western calendar”). Here are some examples:
- The 10,000th hour of the year
- The 234th day of the year
- The first Friday of the year
- The first Friday in June (National Donut Day #1)
- Thursday on the 33rd week of the year
- An “overflow” date, such as September 50
For cases like these, we’ll need Calendar
and DateComponents
. Let’s look at these two structs and then build some Date
s with them.
Calendar
We tell time using units such as years, months, days, hours, and minutes, all of which evolved over thousands of years and are based on the relative positions and motions of the sun, earth, and moon. It’s much easier for us to understand “January 9, 2007, 10:00 a.m. Pacific Standard Time” than “190,058,400 seconds after the start of the Third Millennium, UTC.”
The Calendar
struct provides much-needed context for Date
instances. It allows us to convert back and forth between computer-friendly Date
objects and human-friendly units like years, months, days, and so on. Think of it as a ruler or tape measure that you can place beside Date
’s time system. Most of the time, you’ll use the Gregorian calendar; if you picture it alongside the way Date
measures time, it would look like this:
In addition to the Gregorian calendar, Calendar
supports 15 other calendar systems, including the Buddhist and Hebrew calendars, which are shown below alongside the Gregorian calendar and Date
’s time system:
Let’s start with Calendar
by determining what your system’s current calendar is:
🛠️ Add this code to the playground and run it:
print("\nCalendar:")
print("• The current calendar is \(Calendar.current).")
print("• The current calendar’s time zone is \(Calendar.current.timeZone) ")
On my system, the output is:
Calendar:
• The current calendar is gregorian (current).
• The current calendar’s time zone is America/New_York (fixed (equal to current))
Calendar.current
(and by extension, its properties, including Calendar.current.timeZone
) is a static read-only property based on the user’s settings. Your system is probably set to the gregorian
calendar, and its time zone is set to your system’s time zone settings.
Calendar
doesn’t do much on its own. It simply provides a human-friendly set of references to Swift’s system for telling time. It requires a companion class that allows us to make use of the years, months, days, hours, and minutes that a Calendar
provides.
Creating a Date
with Calendar
and DateComponents
The DateComponents
struct allows us to assemble either...
- a point in time, or
- an interval of time
...out of components such as year, month, day, hour, minute, and more. For now, we’ll use DateComponents
to assemble a point in time.
Here’s the complete set of components that DateComponents
uses:
Component | Description |
---|---|
calendar | The calendar system of the date. Its default value is the system’s calendar, which is usually gregorian . |
day | The day number of the date. For January 9, 2007, 18:00 UTC, this value is 9 . |
era | The era of the date, which depends its calendar system. In this case, we’re using the Gregorian calendar, which has two eras:
|
hour | The hour number of the date. For January 9, 2007, 18:00 UTC, this value is 18 . |
minute | The minute number of the date. For January 9, 2007, 18:00 UTC, this value is 0 . |
month | The month number of the date, with month numbers starting at 1 . For January 9, 2007, 18:00 UTC, this value is 1 . |
nanosecond | The nanosecond number of the date. For January 9, 2007, 18:00 UTC, this value is 0 . |
quarter | The quarter number of the date, with quarter numbers starting at 0 . January 9, 2007, 18:00:00 UTC is in the first quarter of the year, so this value is 0 . |
second | The second number of the date. For January 9, 2007, 18:00 UTC, this value is 0 . |
timeZone | The time zone of the date. This will depend on the time zone of the calendar used. |
weekday | The day of the week of the date. In the gregorian calendar, Sunday is 1 , Monday is 2 , Tuesday is 3 , and so on. January 9, 2007, was a Wednesday, so this value for that date is 3 . |
weekdayOrdinal | The position of the weekday within the next larger specified calendar unit.
|
weekOfMonth | The week of the month of the date, where the first week of the month is 1 . January 9, 2007, 18:00 UTC fell on the 2nd week of January 2007, so this value is 2 . |
weekOfYear | The week of the year of the date, where the first week of the year is 1 . January 9, 2007, 18:00 UTC fell on the 2nd week of 2007, so this value is 2 . |
year | The year number of the date. For January 9, 2007, 18:00 UTC, this value is 2007 . |
yearForWeekOfYear | Oh wow, this is so hard to explain that I’ll leave it to Apple’s docs. |
You don’t have to specify all the components in a DateComponents
instance; you have to specify just enough to define a date.
Let’s use DateComponents
to construct a Date
object using a year, month, day, and time. We’ll do this by rebuilding the date and time of the original iPhone Stevenote: January 9, 2007 at 10:00 a.m., Pacific Standard Time (UTC-8).
🛠️ Add the following to the playground and run it:
print("\nCreating a Date with Calendar and DateComponents:")
var gregorianCalendar = Calendar(identifier: .gregorian)
gregorianCalendar.locale = userLocale
let iPhoneStevenoteComponents = DateComponents(
calendar: gregorianCalendar,
timeZone: TimeZone(identifier: "America/Los_Angeles"),
year: 2007,
month: 1,
day: 9,
hour: 10,
minute: 00
)
iPhoneStevenoteDate = gregorianCalendar.date(from: iPhoneStevenoteComponents)!
print("• Once again, the iPhone Stevenote took place on \(iPhoneStevenoteDate.description(with: userLocale)).")
On my system, the code above results in this output:
Once again, the iPhone Stevenote took place on Tuesday, January 9, 2007 at 1:00:00 PM Eastern Standard Time.
The date and time that you’ll see will depend on your time zone, but it will be the local equivalent date and time of January 9, 2007 at 10:00:00 AM UTC-8.
Let’s take a closer look at the newly-added code. Here are the first two lines:
var gregorianCalendar = Calendar(identifier: .gregorian)
gregorianCalendar.locale = userLocale
In the first line, we’re creating a Calendar
instance using the Calendar(identifier:)
initializer. We’re specifying that the calendar’s date system is .gregorian
, which is the shorthand form of the full calendar identifier, Calendar.Identifier.gregorian
.
Newly-instantiated Calendar
instances don’t have a set locale
, so we set it to the user’s current locale in the second line. Doing this sets the Calendar
to the user’s preferred language, which is useful when we want to get a list of weekday and month names.
Here’s the third line:
let iPhoneStevenoteComponents = DateComponents(
timeZone: TimeZone(identifier: "America/Los_Angeles"),
year: 2007,
month: 1,
day: 9,
hour: 10,
minute: 00
)
This line creates a set of date components that represent January 9, 2007 at 10:00 a.m., Pacific Standard Time (UTC-8). Note that I’ve specified the time zone with a geographic identifier — America/Los_Angeles
— instead of a time zone identifier like PST
or a time offset from UTC like -08:00
. I prefer to use geographic identifiers because they let me delegate figuring out if an event happened during Standard Time or Daylight Saving Time to Foundation, which will always get it right.
DateComponents
’ initializer has a lot of optional parameters. In the code above, we used only those parameters we needed to specify the date, time, and time zone of the original iPhone Stevenote. We’ll look at some of the other parameters shortly.
Here are the final two lines of the code:
iPhoneStevenoteDate = gregorianCalendar.date(from: iPhoneStevenoteComponents)!
print("\nOnce again, the iPhone Stevenote took place on \(iPhoneStevenoteDate.description(with: userLocale)).")
The first line uses gregorianCalendar
and the DateComponents
we just created to create a new Date
, which we assigned to iPhoneStevenoteDate
. iPhoneStevenoteComponents
provides the values corresponding to the date and time of the iPhone Stevenote, and gregorianCalendar
provides the context for those values and creates the Date
.
Let’s create a DateComponents
instance in a slightly different way. We’ll do it for the date of the original iPad Stevenote, which took place on January 27, 2010 at 10:00 a.m. Pacific Standard Time.
🛠️ Add this code to the playground and run it:
var iPadStevenoteComponents = DateComponents()
iPadStevenoteComponents.year = 2010
iPadStevenoteComponents.month = 1
iPadStevenoteComponents.day = 27
iPadStevenoteComponents.hour = 10
iPadStevenoteComponents.minute = 0
iPadStevenoteComponents.timeZone = TimeZone(identifier: "America/Los_Angeles")
iPadStevenoteDate = gregorianCalendar.date(from: iPadStevenoteComponents)!
print("• Once again, the iPad Stevenote took place on \(iPadStevenoteDate.description(with: userLocale)).")
The code above takes a different approach to set up a DateComponents
instance by using the default initializer to instantiate DatComponents
, followed by setting its various properties to specify the desired date’s year, month, day, hour, minute, and time zone. The process of creating a Date
from DateComponents
remains the same: using Calendar
’s date(from:)
method.
What will the day and time be 10,000 hours into 2024?
So far, we’ve created Date
s based on known dates and times. How about Dates where we don’t have a specific date but have enough criteria to specify a date? This is where Calendar
and DateComponets
shine. Calendar
does its best to work with the DateComponents
that you give it, and DateComponents
gives you all sorts of ways to specify a date.
You’ve probably heard of the “10,000 Hour Rule,” which states that on average, it takes a person about 10,000 hours of directed practice to become an expert at something. We’re going to ignore the debate over whether it’s true and focus on the 10,000 hours instead. If you could practice for 10,000 hours non-stop, without having to take a break to eat, sleep, or do anything else, and you started on January 1, 2024 at midnight, what would the date and time be when you had completed your practice?
🛠️ Add the following to the playground and run it:
let tenThousandHoursComponents = DateComponents(
year: 2024,
hour: 10000
)
let tenThousandHoursInto2023Date = gregorianCalendar.date(from: tenThousandHoursComponents)!
print("\n• 10,000 hours into 2024, the date and time will be \(tenThousandHoursInto2023Date.description(with: userLocale)).")
print("• In UTC, that’s \(tenThousandHoursInto2023Date.description).")
Here’s what my system displayed when I ran the code:
• 10,000 hours into 2024, the date and time will be Thursday, February 20, 2025 at 4:00:00 PM Eastern Standard Time.
• In UTC, that’s 2025-02-20 21:00:00 +0000.
You may have trouble believing that a 10,000-hour practice session that started at the very start of 2023 would end nearly three months into 2024, but it’s true. 10,000 hours is 8 hours short of 417 days. You can’t practice for that length of time continuously, which is why many people prefer to say it takes 10 years to become an expert — that’s how long it takes to accumulate 10,000 hours by breaking it into sessions of three hours every day.
What will the 234th day of 2024 be?
DateComponents
makes this calculation easy.
🛠️ Add this code to the playground and run it:
let day234Components = DateComponents(
year: 2024,
day: 243
)
let day234Date = gregorianCalendar.date(from: day234Components)!
print("\n• The 234th day of 2024 will be \(day234Date.description(with: userLocale)).")
On my system, this code produces the following output:
• The 234th day of 2024 will be Friday, August 30, 2024 at 12:00:00 AM Eastern Daylight Time.
What date is the first Friday of 2024?
Let’s use a couple of DateComponents
properties to find out what date the first Friday of 2024 will fall on. These properties are:
weekday
: An integer representing a day of the week. The first day of the week, Sunday, is represented by 1, and the last day of the week, Saturday, is represented by 7. We want a Friday, so we’ll set this value to6
.weekdayOrdinal
: The ordinal, or order number, of the given weekday. We want the first Friday, so we’ll set this value to1
.
🛠️ Add the following to the playground and run it:
let firstFriday2024Components = DateComponents(
year: 2024, // We want a date in 2024
weekday: 6, // It’s a Friday
weekdayOrdinal: 1 // The first one
)
let firstFriday2024Date = gregorianCalendar.date(from: firstFriday2024Components)!
print("\n• The first Friday of 2024 will be \(firstFriday2024Date.description(with: userLocale)).")
Here’s what my system displayed when I ran the code:
• The first Friday of 2024 will be Friday, January 5, 2024 at 12:00:00 AM Eastern Standard Time.
What date is the first National Donut Day of 2023?
Apparently, just one National Donut Day isn’t enough. The second one of the year is a stationary day; it always happens on November 5. But the first one of the year is a moving day — it’s the first Friday in June. Let’s let Swift tell us what that date is for 2023.
🛠️ Add this code to the playground and run it:
let firstDonutDay2024Components = DateComponents(
year: 2024, // We want a date in 2024
month: 6, // in June
weekday: 6, // It’s a Friday
weekdayOrdinal: 1 // The first one
)
let firstDonutDay2024Date = gregorianCalendar.date(from: firstDonutDay2024Components)!
print("\n• The first National Donut Day of 2024 will be \(firstDonutDay2024Date.description(with: userLocale)).")
On my system, this code produces the following output:
• The first National Donut Day of 2024 will be Friday, June 7, 2024 at 12:00:00 AM Eastern Daylight Time.
What date is the Thursday of the 33rd week of the year?
We’ll use DateComponents
’ weekOfYear
property to solve this one.
🛠️ Add the following to the playground and run it:
let thursday33rdWeek2024Components = DateComponents(
year: 2024, // We want a date in 2024
weekday: 5, // It’s a Thursday
weekOfYear: 33 // on the 33rd week of the year
)
let thursday33rdWeek2024Date = gregorianCalendar.date(from: thursday33rdWeek2024Components)!
print("\n• The Thursday of the 33rd week of 2024 will be \(thursday33rdWeek2024Date.description(with: userLocale)).")
Here’s what my system displayed when I ran the code:
• The Thursday of the 33rd week of 2024 will be Thursday, August 15, 2024 at 12:00:00 AM Eastern Daylight Time.
What happens if you set DateComponents
to September 50, 2024?
Let’s look at the case of overflow. What happens if you try to create a Date
using components that define the nonsense date “September 50, 2024?”
🛠️ Add this code to the playground and run it:
var sept50DateComponents = DateComponents(
year: 2024,
month: 9,
day: 50)
let sept50Date = gregorianCalendar.date(from: sept50DateComponents)!
print("\n• September 50, 2024 is actually \(sept50Date.description(with: userLocale)).")
On my system, this code produces the following output:
• September 50, 2024 is actually Sunday, October 20, 2024 at 12:00:00 AM Eastern Daylight Time.
These date components we provided are treated as “the start of September, plus 50 days” — September’s 30 days, plus an additional 20 days into October.
Extracting DateComponents
from a Date
with Calendar
Just as we can use a Calendar
and DateComponents
to make a Date
, we can also use Date
and a Calendar
to extract its DateComponents
.
Extracting all the components from a Date
Let’s extract all the components from a Date
we’ve already defined: iPhoneStevenoteDate
, which is January 9, 2007, 10:00 a.m. Pacific Standard Time.
🛠️ Add the following to the playground and run it:
var iPhoneStevenoteDateComponents = gregorianCalendar.dateComponents(
[
.calendar,
.day,
.era,
.hour,
.minute,
.month,
.nanosecond,
.quarter,
.second,
.timeZone,
.weekday,
.weekdayOrdinal,
.weekOfMonth,
.weekOfYear,
.year,
.yearForWeekOfYear
],
from: iPhoneStevenoteDate
)
print("\nThe date components for the iPhone Stevenote date:")
print("• Calendar: \(iPhoneStevenoteDateComponents.calendar!.identifier)")
print("• Day: \(iPhoneStevenoteDateComponents.day!)")
print("• Era: \(iPhoneStevenoteDateComponents.era!)")
print("• Hour: \(iPhoneStevenoteDateComponents.hour!)")
print("• Minute: \(iPhoneStevenoteDateComponents.minute!)")
print("• Month: \(iPhoneStevenoteDateComponents.month!)")
print("• Nanosecond: \(iPhoneStevenoteDateComponents.nanosecond!)")
print("• Quarter: \(iPhoneStevenoteDateComponents.quarter!)")
print("• Second: \(iPhoneStevenoteDateComponents.second!)")
print("• Time zone: \(iPhoneStevenoteDateComponents.timeZone!)")
print("• Weekday: \(iPhoneStevenoteDateComponents.weekday!)")
print("• Weekday ordinal: \(iPhoneStevenoteDateComponents.weekdayOrdinal!)")
print("• Week of month: \(iPhoneStevenoteDateComponents.weekOfMonth!)")
print("• Week of year: \(iPhoneStevenoteDateComponents.weekOfYear!)")
print("• Year: \(iPhoneStevenoteDateComponents.year!)")
print("• Year for week of year: \(iPhoneStevenoteDateComponents.yearForWeekOfYear!)")
Here’s what my system displayed when I ran the code:
The date components for the iPhone Stevenote date:
• Calendar: gregorian
• Day: 9
• Era: 1
• Hour: 13
• Minute: 0
• Month: 1
• Nanosecond: 0
• Quarter: 0
• Second: 0
• Time zone: America/New_York (fixed (equal to current))
• Weekday: 3
• Weekday ordinal: 2
• Week of month: 2
• Week of year: 2
• Year: 2007
• Year for week of year: 2007
The component values that you’ll see are determined by your system’s time zone. If you want to see the system components in the time zone where the Stevenote took place (Steve Jobs gave his keynote in San Francisco, which is in the America/Los_Angeles
time zone), use a Calendar
whose timeZone
property is set to America/Los_Angeles
.
🛠️ Add this code to the playground and run it:
var pacificCalendar = Calendar(identifier: .gregorian)
pacificCalendar.timeZone = TimeZone(identifier: "America/Los_Angeles")!
pacificCalendar.locale = Locale.autoupdatingCurrent
iPhoneStevenoteDateComponents = pacificCalendar.dateComponents(
[
.calendar,
.day,
.era,
.hour,
.minute,
.month,
.nanosecond,
.quarter,
.second,
.timeZone,
.weekday,
.weekdayOrdinal,
.weekOfMonth,
.weekOfYear,
.year,
.yearForWeekOfYear
],
from: iPhoneStevenoteDate
)
print("\nThe date components for the iPhone Stevenote date - for the Pacific Calendar — are:")
print("• Calendar: \(iPhoneStevenoteDateComponents.calendar!.identifier)")
print("• Day: \(iPhoneStevenoteDateComponents.day!)")
print("• Era: \(iPhoneStevenoteDateComponents.era!)")
print("• Hour: \(iPhoneStevenoteDateComponents.hour!)")
print("• Minute: \(iPhoneStevenoteDateComponents.minute!)")
print("• Month: \(iPhoneStevenoteDateComponents.month!)")
print("• Nanosecond: \(iPhoneStevenoteDateComponents.nanosecond!)")
print("• Quarter: \(iPhoneStevenoteDateComponents.quarter!)")
print("• Second: \(iPhoneStevenoteDateComponents.second!)")
print("• Time zone: \(iPhoneStevenoteDateComponents.timeZone!)")
print("• Weekday: \(iPhoneStevenoteDateComponents.weekday!)")
print("• Weekday ordinal: \(iPhoneStevenoteDateComponents.weekdayOrdinal!)")
print("• Week of month: \(iPhoneStevenoteDateComponents.weekOfMonth!)")
print("• Week of year: \(iPhoneStevenoteDateComponents.weekOfYear!)")
print("• Year: \(iPhoneStevenoteDateComponents.year!)")
print("• Year for week of year: \(iPhoneStevenoteDateComponents.yearForWeekOfYear!)")
You’ll see the following output:
The date components for the iPhone Stevenote date - for the Pacific Calendar — are:
• Calendar: gregorian
• Day: 9
• Era: 1
• Hour: 10
• Minute: 0
• Month: 1
• Nanosecond: 0
• Quarter: 0
• Second: 0
• Time zone: America/Los_Angeles (fixed)
• Weekday: 3
• Weekday ordinal: 2
• Week of month: 2
• Week of year: 2
• Year: 2007
• Year for week of year: 2007
This time, the output lists the components for the date of the Stevenote in its local date and time, regardless of your system’s time zone.
What day of the week and week of the year will April Fools’ Day 2024 fall on?
In many parts of the world, April 1 is a day for playing pranks on others. In the English-speaking world, it has a name: April Fools’ Day.
Since it has an assigned date, we already know its what month and day it will fall on. But it takes effort to determine what weekday it will be on a given April 1, or what week of the year it will be. DateComponents
can help us. We can use a DateComponents
instance to construct a Date
for April 1, 2024 and another DateComponents
instance to extract the weekday
and weekOfYear
values for that Date
.
🛠️ Add the following to the playground and run it:
// Create the date
let aprilFoolsDay2024Components = DateComponents(
year: 2024,
month: 4,
day: 1
)
let aprilFoolsDay2024Date = gregorianCalendar.date(from: aprilFoolsDay2024Components)!
// Extract the weekday and week of the year from the date
let wantedComponents = gregorianCalendar.dateComponents(
[
.weekday,
.weekOfYear,
],
from: aprilFoolsDay2024Date
)
let aprilFools2024Weekday = wantedComponents.weekday!
let aprilFools2024WeekdayName = gregorianCalendar.weekdaySymbols[aprilFools2024Weekday - 1]
let aprilFools2024WeekOfYear = wantedComponents.weekOfYear!
print("\nApril Fools’ Day 2024:")
print("• happens on day \(aprilFools2024Weekday) of the week (\(aprilFools2024WeekdayName)),")
print("• on week \(aprilFools2024WeekOfYear) of 2024.")
Running this code reveals that April 1, 2024 will fall on weekday 2 (Monday), and that it will occur on the 14th week of the year. To specify which weekday day 2 is, I used Calendar
’s weekdaySymbols
array to access the name of the corresponding weekday, which in this case is Monday
.
The Big Challenge
At the start of this chapter, I said that Swift’s date and time system lets you do the following without having to do the calculations yourself, while using only a few lines of code:
- Get the date for the third Wednesday of July at 3:30 p.m. Pacific Time
- Display it as it would appear in the Coptic calendar system in Melbourne, Australia’s time zone with a few lines of code.
🛠️ Let’s give it a try! Add this code to the playground and run it:
print("\nThe Big Challenge!")
// First, create the the date —
// third Wednday of July at 3:30 p.m. Pacific Time
let challengeDateComponents = DateComponents(
calendar: gregorianCalendar,
timeZone: TimeZone(identifier: "America/Los_Angeles")!,
year: 2024,
month: 7,
hour: 15,
minute: 30,
weekday: 4,
weekdayOrdinal: 3
)
let challengeDate = gregorianCalendar.date(from: challengeDateComponents)!
print("• The challenge date in the Gregorian calendar is \(challengeDate.description(with: userLocale)).")
var melbourneCalendar = Calendar(identifier: .gregorian)
melbourneCalendar.timeZone = TimeZone(identifier: "Australia/Melbourne")!
let melbourneDateComponents = melbourneCalendar.dateComponents(
[
.year,
.month,
.day,
.weekday,
.hour,
.minute
],
from: challengeDate)
print("• melbourneDateComponents: \(melbourneDateComponents)")
// Create the Coptic calendar and set its locale to Arabic(Egypt)
// so that it us Arabic month nam,
// and its time zone to Melbourne.
var copticCalendar = Calendar(identifier: .coptic)
copticCalendar.locale = Locale(identifier: "ar_EG")
copticCalendar.timeZone = TimeZone(identifier: "Australia/Melbourne")!
// Extract the date components from the date using the Coptic calendar
let challengeCopticComponents = copticCalendar.dateComponents(
[
.year,
.month,
.day,
.weekday,
.hour,
.minute
],
from: challengeDate)
let challengeYear = challengeCopticComponents.year!
let challengeMonth = challengeCopticComponents.month!
let challengeMonthName = copticCalendar.monthSymbols[challengeMonth - 1]
let challengeDay = challengeCopticComponents.day!
let challengeWeekday = challengeCopticComponents.weekday!
let challengeWeekdayName = copticCalendar.weekdaySymbols[challengeWeekday - 1]
let challengeHour = challengeCopticComponents.hour!
let challengeMinute = challengeCopticComponents.minute!
print("• The challenge date in the Coptic calendar happens on: ")
print("••• year \(challengeYear)")
print("••• month \(challengeMonth) (\(challengeMonthName))")
print("••• day \(challengeDay)")
print("••• weekday \(challengeWeekday) (\(challengeWeekdayName))")
print("••• hour \(challengeHour)")
print("••• minute \(challengeMinute) (Melbourne time).")
You’ll see the following output:
The Big Challenge!
• The challenge date in the Gregorian calendar is Wednesday, July 17, 2024 at 6:30:00 PM Eastern Daylight Time.
• melbourneDateComponents: year: 2024 month: 7 day: 18 hour: 8 minute: 30 weekday: 5 isLeapMonth: false
• The challenge date in the Coptic calendar happens on:
••• year 1740
••• month 11 (أبيب)
••• day 11
••• weekday 5 (الخميس)
••• hour 8
••• minute 30 (Melbourne time).
The output shows that:
- In my time zone (US Eastern), the challenge date is Wednesday, July 17, 2024 at 6:30 p.m..
- In Melbourne, Australia, that date is Thursday (weekday 5), July 18, 2024 at 8:30 a.m. in the Gregorian calendar.
- In the Coptic calendar, the date is 11th day of أبيب (“Epip” or “Abib”, month 11), which is a Thursday (الخميس). I verified that this lines up with July 20, 2024 using this calendar page.
Next Steps
💻 Once again, you can find an Xcode playground with all the code in this article in this GitHub repository — it’s
intro-dates-times-swift-1.playground
.
In the next chapter of this tutorial, we’ll take a closer look at ways of converting Date
s into strings to display to the user.