Categories
Swift Kick

How to work with dates and times in Swift, part one [Updated for Swift 2]

i just want to use dates

Update, August 26, 2015: I’ve updated this article so that its code works with Swift 2. It compiles under the latest version of Xcode 7, beta 6.

swift kickIf you’re just getting started with date and time programming in Swift, chances are that you probably did some Googling, found NSDate and its companion classes in Apple’s documentation and promptly got confused. Let me reassure you that it isn’t your fault. Apple’s Date and Time Programming Guide isn’t set up in the most helpful way, and its examples are in Objective-C, which can throw you off if you’re not familiar with its [instance method] calling syntax.

If you’re coming to Swift 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:

cocoa date time class chart

Click the chart to see it at full size.

This is the first article in a short series on programming dates and times in Swift. It’ll help you make sense of working with NSDate and its companion classes.

What is UTC? (or: “It’s 5 o’clock somewhere!”)

what time is it right now

People who like to drink are fond of saying “Well, it’s five o’clock somewhere!“, and it’s my favorite way of illustrating that what time it is depends on where you are. It’s why we have UTC — Coordinated Universal Time — the “One True Time” on which every other time zone is based. UTC replaced GMT (Greenwich Meridian Time, the time as observed at the Royal Observatory in Greenwich, London), and it doesn’t change for daylight savings. By having this standard, we eliminate the confusion that comes up when discussing times.

While most places have some names to refer to time zones in everyday conversation — such as Pacific, Mountain, Central, and Eastern in North America — the clearest way to indicate time zones is to express them as negative or positive offsets from UTC. For example, the North American time zone known as “Eastern” is 5 hours behind UTC, so it’s expressed as UTC-05:00. There are certain time zones where the offset isn’t whole hours, such as Newfoundland’s, which is UTC-03:30, and Nepal’s, which for some reason is UTC+05:45.

NSDate: Cocoa standard time

When you want to represent a date or time in Cocoa, you do so with an instance of the NSDate class. Here’s a nice, simple description of what NSDate is:

nsdate

When you’re measuring time, you need a reference point. The Gregorian calendar — the one that I’m 99.999999% sure that you’re using right now — uses what is said to be the year of the birth of Christ as its reference point. Your age uses the date of your birth as a reference point. A time trial race uses the time at the start of the race as its reference point.

NSDate‘s reference point is the start of the third millennium: January 1, 2001 at midnight UTC. It stores time as an NSTimeInterval, a 64-bit floating point value representing a number of seconds. Negative values represent a number seconds before January 1, 2001, 00:00 UTC, and positive values represent a number seconds after that time. According to Apple’s documentation, this representation of time “yields sub-millisecond precision over a range of 10,000 years”, which means that an NSDate instance can represent any point in time from about 3000 BC/BCE to 7000 AD/CE.

Here are 4 historical dates, as seen from NSDate‘s perspective:

historical dates nsdate style

There are some consequences to the fact that NSDate treats time as an offset of seconds:

  • All NSDate values refer to both a date and a time. If you want to use an NSDate to store a date only, you ignore the time portion, and vice versa.
  • NSDate has no concept of time zones or any time unit other than seconds. That means you can’t ask it what year, month, day, hour, or minute correspond to the time it’s storing. As far as it’s concerned, there are no calendars; you work with NSDate‘s representation of time, when it’s time to display a date, time, or both, you format it to use the calendar and time zone that’s appropriate for the user.

There’s a method to this madness: it allows us to use and store dates and times in a way that’s independent of calendar systems, time zones, languages, and date formats. As I write this, it’s the year 2015 in the Gregorian calendar, but the Buddhist calendar says it’s 2558, and if you go by the Hebrew Calendar, it’s 5775. I may say it’s 9:45 a.m. as I write this in Tampa, but if you’re in California, it’s 6:45, and if you’re a soldier, you might call it 14:45 Zulu Time. I call the current month January, but you might call it Enero or Janvier. It’s all the same as far as NSDate is concerned, which makes it incredibly flexible.

Creating NSDates without any helper classes: now is easy, other dates ain’t so pretty

Let’s create some NSDates right now. Fire up Xcode, open a new playground, and enter the following code so that it looks like this:

// Playground - noun: a place where people can play

import UIKit

let now = NSDate()
print(now)
now.timeIntervalSinceReferenceDate

You should see results in the sidebar that look similar to this:

playground 01

If you create an NSDate without any parameters, you get an instance representing the date and time at the moment it was created. That’s what we’ve done by creating the instance named now.

Note that in the sidebar beside line 5, where we created now, the result displayed in the sidebar is Jan 19, 2015, 8:52 AM. While the internal representation of the current time is a 64-bit floating point value, Xcode’s doing us a favor by representing it in a more readable format and using the local time zone. It’s doing this by making use of a date formatter, which we’ll cover later.

In line 6, we’re using the println function to display the default string representation of an NSDate, which is a completely numeric one. It’s more readable than a 64-bit floating point value, but it might not be in the format or time zone (or even the calendar system) that you want. Once again, this output comes courtesy of a date formatter.

Finally, in line 7, we use the timeIntervalSinceReferenceDate property to display now‘s internal representation of the date and time it’s storing: about 443 million seconds after January 1, 2001 at midnight UTC.

Let’s create the dates from the Historical dates, NSDate style picture above. Enter or paste the highlighted code below so that your playground looks like this:

// Playground - noun: a place where people can play

import UIKit

let now = NSDate()
println(now)
now.timeIntervalSinceReferenceDate

// March 10, 1876 was 3,938,698,800 seconds before the third millennium (January 1, 2001 midnight UTC)
let firstLandPhoneCallDate = NSDate(timeIntervalSinceReferenceDate: -3_938_698_800.0)

// April 3, 1973 was 875,646,000 seconds before the third millennium
let firstCellPhoneCallDate = NSDate(timeIntervalSinceReferenceDate: -875_646_000.0)

// January 9, 2007, 18:00 UTC was 190,058,400 seconds after the third millennium
let iPhoneAnnouncementDate = NSDate(timeIntervalSinceReferenceDate: 190_058_400.0)

// January 27, 2010, 18:00 UTC was 286,308,000 seconds after the third millennium
let iPadAnnouncementDate = NSDate(timeIntervalSinceReferenceDate: 286_308_000.0)

Note my use of the underscore character, _, to act as a “thousands” separator. It’s not required; it just makes large numbers easier to read.

In the sidebar, you should see nicely-formatted dates beside the NSDates you created:

playground 02

Click the screenshot to see it at full size.

NSDate has four initializers for creating specified dates and times:

Initializer Description
init(timeIntervalSinceReferenceDate:) Create an NSDate instance representing a time specified by a number of seconds before or after January 1, 2001 00:00 UTC.
init(timeIntervalSinceNow:) Create an NSDate instance representing a time specified by a number of seconds before or after the current date and time.
init(timeIntervalSince1970:) Create an NSDate instance representing a time specified by a number of seconds before or after January 1, 1970 00:00 UTC. This method exists not because Apple’s founders were California hippies nostalgic for the era of their youth, but for compatibility with Unix time.
init(timeInterval:sinceDate:) Create an NSDate instance representing a time specified by a number of seconds before or after a given NSDate.

You’ve probably noticed that none of these initializers lets you create an NSDate by giving it something convenient like a year, month, day, time zone and so on. Luckily, there are classes that will help us do this.

Creating NSDates with the help of NSCalendar and NSDateComponents

If you’re like most people, you’d probably much rather initialize a date object using a day, month, year, and time instead of some number of seconds before and after midnight on January 1, 1970, January 1, 2001, or any other arbitrary date. For this, we’ll need a couple of additional classes:

nscalendar and nsdatecomponents

First, there’s the NSCalendar class, which among other things, gives us a context for converting NSDate‘s “seconds before or after the third millennium” measurements into a familiar time system with years, months, days, hours, and minutes, and it accounts for time zones as well. Most of the time, the date and time system will be the Gregorian calendar, but iOS also lets you choose from 15 other calendar systems, including Hebrew and Buddhist.

Next, there’s the NSDateComponents class, which is an assembly of properties that make up a date, such as year, month, date, hour, minute, second, and so on. An NSDateComponents instance can be used to represent either:

  • A specific point in time, or
  • a duration of time.

To create an NSDate by specifying things like a year, month, day, and time, we’ll do the following:

  • Create an NSCalendar instance pointing to the user’s calendar
  • Specify a date using an NSDateComponents instance
  • Create the NSDate by passing the NSDateComponents instance to NSCalendar‘s dateFromComponents instance method

nsdatecomponents to nsdate

Let’s go back to our playground and create our first historical date: that of Alexander Graham Bell’s first phone call. We know it took place on March 10, 1876. While we don’t know the exact time it happened, we do know that it happened in North America’s Eastern time zone, as opposed to something like Kiritimati, a.k.a. Christmas Island. That place is 19 hours ahead and would’ve resulted in historians recording that day as March 11th rather than the 10th.

Enter or paste the highlighted code below so that your playground looks like this:

// Playground - noun: a place where people can play

import UIKit

// The user's calendar incorporates the user's locale and
// time zones settings, so we'll often use it.
let userCalendar = NSCalendar.currentCalendar()


// March 10, 1876
// In this case, we're using an NSDatesComponents instance
// to represent a date rather than a duration of time.
let firstLandPhoneCallDateComponents = NSDateComponents()

// We don't know the time when Alexander Graham Bell made
// his historic phone call, so we'll simply provide the
// year, month and day. 
// We *do* know that he made that call in North America's
// eastern time zone, so we'll specify that.
firstLandPhoneCallDateComponents.year = 1876
firstLandPhoneCallDateComponents.month = 3
firstLandPhoneCallDateComponents.day = 10
firstLandPhoneCallDateComponents.timeZone = NSTimeZone(name: "US/Eastern")

Any calendar, regardless of its time zone, to turn date components into dates using its calendar system. If you specify a time zone in the date components, it will take that time zone into account; if you don’t specify one, the date will be created using the calendar’s time zone. We created an instance of the user’s calendar, which incorporates the user’s local and time zone settings, because it’ll be useful later when we want to go in the opposite direction and convert dates into date components.

Note that after setting the year, month, and day properties of firstLandPhoneCallComponents, we set the timeZone component using the preferred initializer, init(name:), which lets you specify a time zone by any of the standard string identifiers listed in the tz database of time zones. I could’ve used one of the city names such as America/New_York (or for those of you familiar with Canada, America/Toronto, America/MontrealAmerica/Atikokan, or America/Pangnirtung) to specify the Eastern time zone. It’s far clearer — especially to people not from the U.S. — to use the time zone names that begin with US or Canada, such as US/Eastern, US/Central, US/Mountain, and US/Pacific rather than city names.

Once we’ve set up the date components, we use the calendar’s dateFromComponents instance method to convert them into a date. I’m in the US/Eastern time zone, the same one as the one specified in the date components, so the result in the sidebar beside this line of code shows as “Mar 10, 1876, 12:00 AM” (we didn’t specify a time in the date components, so the resulting date has the default time of 00:00). The date displayed in the sidebar uses your system settings, which may be different from mine.

Let’s enter the other three historic dates. Enter or paste the highlighted code below so that your playground looks like this:

// Playground - noun: a place where people can play

import UIKit

// The user's calendar incorporates the user's locale and
// time zones settings, so we'll often use it.
let userCalendar = NSCalendar.currentCalendar()


// March 10, 1876
// In this case, we're using an NSDatesComponents instance
// to represent a date rather than a duration of time.
let firstLandPhoneCallDateComponents = NSDateComponents()

// We don't know the time when Alexander Graham Bell made
// his historic phone call, so we'll simply provide the
// year, month and day. 
// We *do* know that he made that call in North America's
// eastern time zone, so we'll specify that.
firstLandPhoneCallDateComponents.year = 1876
firstLandPhoneCallDateComponents.month = 3
firstLandPhoneCallDateComponents.day = 10
firstLandPhoneCallDateComponents.timeZone = NSTimeZone(name: "US/Eastern")

// We have a calendar and date components. We can now make a date!
// On my system (US/Eastern time zone), the result for the line below is
// "Mar 10, 1876, 12:00 AM"
let firstLandPhoneCallDate = userCalendar.dateFromComponents(firstLandPhoneCallDateComponents)!


// April 3, 1973
let firstCellularPhoneCallDateComponents = NSDateComponents()
firstCellularPhoneCallDateComponents.year = 1973
firstCellularPhoneCallDateComponents.month = 4
firstCellularPhoneCallDateComponents.day = 3
firstCellularPhoneCallDateComponents.timeZone = NSTimeZone(name: "US/Eastern")
// On my system (US/Eastern time zone), the result for the line below is
// "Apr 3, 1973, 12:00 AM"
let firstCellularPhoneCallDate = userCalendar.dateFromComponents(firstCellularPhoneCallDateComponents)!


// January 9, 2007, 18:00 UTC
let iPhoneAnnouncementDateComponents = NSDateComponents()
// We know that the "Stevenote" when the iPhone was announced
// started at 10:00 am Pacific time.
iPhoneAnnouncementDateComponents.year = 2007
iPhoneAnnouncementDateComponents.month = 1
iPhoneAnnouncementDateComponents.day = 9
iPhoneAnnouncementDateComponents.hour = 13
iPhoneAnnouncementDateComponents.minute = 0
iPhoneAnnouncementDateComponents.timeZone = NSTimeZone(name: "US/Eastern")
// On my system (US/Eastern time zone), the result for the line below is
// "Jan 9, 2007, 1:00 PM"
let iPhoneAnnouncementDate = userCalendar.dateFromComponents(iPhoneAnnouncementDateComponents)!


// January 27, 2010, 18:00 UTC
// We know that the "Stevenote" when the iPad was announced
// started at 10:00 am Pacific time.
let iPadAnnouncementDateComponents = NSDateComponents()
iPadAnnouncementDateComponents.year = 2010
iPadAnnouncementDateComponents.month = 1
iPadAnnouncementDateComponents.day = 27
// Let's set the clock using Pacific Time
iPadAnnouncementDateComponents.hour = 10
iPadAnnouncementDateComponents.minute = 0
iPadAnnouncementDateComponents.timeZone = NSTimeZone(name: "US/Pacific")
// On my system (US/Eastern time zone), the result for the line below is
// "Jan 27, 2010, 1:00 PM"
let iPadAnnouncementDate = userCalendar.dateFromComponents(iPadAnnouncementDateComponents)!

Note that we used different time zones for the “Stevenotes”. Both took place at the same time, 10:00 a.m. Pacific, but we set the time for the iPhone announcement as 1:00 p.m. US/Eastern, and the time for the iPad announcement as 10:00 a.m. US/Pacific. Both results in the sidebar appear at the same time; on my machine, they appear as Jan 27, 2010, 1:00 PM.

dateFromComponents works with what you give it

Suppose we want to create a date just by specifying that it’s 11:00 a.m. on the first Saturday of March 2015 in the US/Eastern time zone. Here’s how it’s done:

// (Previous code goes here)

// First Saturday of March 2015, US/Eastern
let firstSaturdayMarch2015DateComponents = NSDateComponents()
firstSaturdayMarch2015DateComponents.year = 2015
firstSaturdayMarch2015DateComponents.month = 3
firstSaturdayMarch2015DateComponents.weekday = 7
firstSaturdayMarch2015DateComponents.weekdayOrdinal = 1
firstSaturdayMarch2015DateComponents.hour = 11
firstSaturdayMarch2015DateComponents.minute = 0
firstSaturdayMarch2015DateComponents.timeZone = NSTimeZone(name: "US/Eastern")
// On my system (US/Eastern time zone), the result for the line below is
// "Mar 7, 2015, 11:00 AM"
let firstSaturdayMarch2015Date = userCalendar.dateFromComponents(firstSaturdayMarch2015DateComponents)!

NSDateComponentsweekday property lets you specify a weekday numerically. In Cocoa’s Gregorian calendar, the first day is Sunday, and is represented by the value 1. Monday is represented by 2, Tuesday is represented by 3, all the way to Saturday, which is represented by 7.

The weekdayOrdinal property lets you specify which specified weekday of the month. By setting weekday to 7, we’re specifying a Saturday; by then setting weekdayOrdinal to 1, we’re specifying the first Saturday of the month.

Here’s another example, where we get the date for the Thursday on the 18th week of 2015:

// (Previous code, including the definition of calendar,
// goes here)

// Thursday of the 18th week of 2015, US/Eastern
let thursday18thWeekOf2015DateComponents = NSDateComponents()
thursday18thWeekOf2015DateComponents.year = 2015
thursday18thWeekOf2015DateComponents.weekOfYear = 18
thursday18thWeekOf2015DateComponents.weekday = 5
thursday18thWeekOf2015DateComponents.timeZone = NSTimeZone(name: "US/Eastern")
// On my system (US/Eastern time zone), the result for the line below is
// ""Apr 30, 2015, 12:00 AM""
let thursday18thWeekOf2015Date = userCalendar.dateFromComponents(thursday18thWeekOf2015DateComponents)!

The other way around: getting NSDateComponents from NSDates

Right now, your playground should look like this, with a number of dates being created from date components:

// Playground - noun: a place where people can play

import UIKit

// The user's calendar incorporates the user's locale and
// time zones settings, so we'll often use it.
let userCalendar = NSCalendar.currentCalendar()


// March 10, 1876
// In this case, we're using an NSDatesComponents instance
// to represent a date rather than a duration of time.
let firstLandPhoneCallDateComponents = NSDateComponents()

// We don't know the time when Alexander Graham Bell made
// his historic phone call, so we'll simply provide the
// year, month and day. 
// We *do* know that he made that call in North America's
// eastern time zone, so we'll specify that.
firstLandPhoneCallDateComponents.year = 1876
firstLandPhoneCallDateComponents.month = 3
firstLandPhoneCallDateComponents.day = 10
firstLandPhoneCallDateComponents.timeZone = NSTimeZone(name: "US/Eastern")

// We have a calendar and date components. We can now make a date!
// On my system (US/Eastern time zone), the result for the line below is
// "Mar 10, 1876, 12:00 AM"
let firstLandPhoneCallDate = userCalendar.dateFromComponents(firstLandPhoneCallDateComponents)!


// April 3, 1973
let firstCellularPhoneCallDateComponents = NSDateComponents()
firstCellularPhoneCallDateComponents.year = 1973
firstCellularPhoneCallDateComponents.month = 4
firstCellularPhoneCallDateComponents.day = 3
firstCellularPhoneCallDateComponents.timeZone = NSTimeZone(name: "US/Eastern")
// On my system (US/Eastern time zone), the result for the line below is
// "Apr 3, 1973, 12:00 AM"
let firstCellularPhoneCallDate = userCalendar.dateFromComponents(firstCellularPhoneCallDateComponents)!


// January 9, 2007, 18:00 UTC
let iPhoneAnnouncementDateComponents = NSDateComponents()
// We know that the "Stevenote" when the iPhone was announced
// started at 10:00 am Pacific time.
iPhoneAnnouncementDateComponents.year = 2007
iPhoneAnnouncementDateComponents.month = 1
iPhoneAnnouncementDateComponents.day = 9
iPhoneAnnouncementDateComponents.hour = 13
iPhoneAnnouncementDateComponents.minute = 0
iPhoneAnnouncementDateComponents.timeZone = NSTimeZone(name: "US/Eastern")
// On my system (US/Eastern time zone), the result for the line below is
// "Jan 9, 2007, 1:00 PM"
let iPhoneAnnouncementDate = userCalendar.dateFromComponents(iPhoneAnnouncementDateComponents)!


// January 27, 2010, 18:00 UTC
// We know that the "Stevenote" when the iPad was announced
// started at 10:00 am Pacific time.
let iPadAnnouncementDateComponents = NSDateComponents()
iPadAnnouncementDateComponents.year = 2010
iPadAnnouncementDateComponents.month = 1
iPadAnnouncementDateComponents.day = 27
// Let's set the clock using Pacific Time
iPadAnnouncementDateComponents.hour = 10
iPadAnnouncementDateComponents.minute = 0
iPadAnnouncementDateComponents.timeZone = NSTimeZone(name: "US/Pacific")
// On my system (US/Eastern time zone), the result for the line below is
// "Jan 27, 2010, 1:00 PM"
let iPadAnnouncementDate = userCalendar.dateFromComponents(iPadAnnouncementDateComponents)!


// First Saturday of March 2015, US/Eastern
let firstSaturdayMarch2015DateComponents = NSDateComponents()
firstSaturdayMarch2015DateComponents.year = 2015
firstSaturdayMarch2015DateComponents.month = 3
firstSaturdayMarch2015DateComponents.weekday = 7
firstSaturdayMarch2015DateComponents.weekdayOrdinal = 1
firstSaturdayMarch2015DateComponents.hour = 11
firstSaturdayMarch2015DateComponents.minute = 0
firstSaturdayMarch2015DateComponents.timeZone = NSTimeZone(name: "US/Eastern")
// On my system (US/Eastern time zone), the result for the line below is
// "Mar 7, 2015, 11:00 AM"
let firstSaturdayMarch2015Date = userCalendar.dateFromComponents(firstSaturdayMarch2015DateComponents)!


// Thursday of the 18th week of 2015, US/Eastern
let thursday18thWeekOf2015DateComponents = NSDateComponents()
thursday18thWeekOf2015DateComponents.year = 2015
thursday18thWeekOf2015DateComponents.weekOfYear = 18
thursday18thWeekOf2015DateComponents.weekday = 5
thursday18thWeekOf2015DateComponents.timeZone = NSTimeZone(name: "US/Eastern")
// On my system (US/Eastern time zone), the result for the line below is
// "Apr 30, 2015, 12:00 AM"
let thursday18thWeekOf2015Date = userCalendar.dateFromComponents(thursday18thWeekOf2015DateComponents)!

Now it’s time to go the other way around, and extract date components from those dates. Once again, it’s the calendar that provides the method for making the conversion.

To extract NSDateComponents from an NSDate, we’ll do the following:

  • Create NSCalendar instances, if needed
  • Specify a set of date components using NSCalendarUnit bitmasks
  • Create the NSDateComponents by passing the NSDate instance to NSCalendar‘s components instance method

nsdate to nsdatecomponents

We already have a calendar instance: userCalendar, which is associated with the user’s time zone. If we use it to extract date components from a given date, the dates and times will be interpreted in the context of its time zone.

Let’s create two more calendars with two different time zones:

  • The US/Pacific time zone (UTC-08:00)
  • The Japan time zone (UTC+09:00)

Here’s what the code looks like:

// (Previous code goes here)

let pacificCalendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
pacificCalendar.timeZone = NSTimeZone(name: "US/Pacific")!

let japanCalendar = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
japanCalendar.timeZone = NSTimeZone(name: "Asia/Tokyo")!

Extracting all the possible date components from a date can be computationally costly, so NSCalendar‘s components instance method requires you to specify the components you want to extract. We want to extract these components from our dates:

  • year
  • month
  • day
  • hour
  • minute
  • weekday
  • weekdayOrdinal

Here’s the code that specifies this:

// (Previous code goes here)

let requestedDateComponents: NSCalendarUnit = [.Year,
                                               .Month,
                                               .Day,
                                               .Hour,
                                               .Minute,
                                               .Weekday,
                                               .WeekdayOrdinal]

Now that we’ve done that, we can start extracting date components:

// (Previous code goes here)

// Date components in the user's time zone
let jan9_2007Components = userCalendar.components(requestedDateComponents,
                                                  fromDate: iPhoneAnnouncementDate)
jan9_2007Components.year
jan9_2007Components.month
jan9_2007Components.day
jan9_2007Components.hour
jan9_2007Components.minute
jan9_2007Components.weekday
jan9_2007Components.weekdayOrdinal

// Date components in the US/Pacific time zone
let march10_1876PacificComponents = pacificCalendar.components(requestedDateComponents,
                                                               fromDate: iPhoneAnnouncementDate)
march10_1876PacificComponents.year
march10_1876PacificComponents.month
march10_1876PacificComponents.day
march10_1876PacificComponents.hour
march10_1876PacificComponents.minute
march10_1876PacificComponents.weekday
march10_1876PacificComponents.weekdayOrdinal

// Date components in Japan's time zone
let march10_1876JapanComponents = japanCalendar.components(requestedDateComponents,
                                                           fromDate: iPhoneAnnouncementDate)
march10_1876JapanComponents.year
march10_1876JapanComponents.month
march10_1876JapanComponents.day
march10_1876JapanComponents.hour
march10_1876JapanComponents.minute
march10_1876JapanComponents.weekday
march10_1876JapanComponents.weekdayOrdinal

In your playground’s sidebar, you should see results similar to those listed in the table below:

Component My user calendar Pacific calendar Japan calendar
year 2007 2007 2007
month 1 1 1
day 9 9 10
hour 13 10 3
minute 0 0 0
weekday 3 3 4
weekdayOrdinal 2 2 2

As you can see, January 9, 2007 at 10:00 a.m. in the US/Pacific time zone is January 9, 2007 at 1:00 p.m. in my time zone (US/Eastern) and January 10, 2007 at 3:00 a.m. in Japan. In the US, that date was the second Tuesday in January 2007; in Japan, it was the second Wednesday.

Turning dates into strings (and vice versa) with NSDateFormatter

nsdate - nsdateformatter - stringJust as you use an calendar to convert date components into dates and vice versa, you use a date formatter — an instance of the NSDateFormatter — to do the conversions.

Formatting date strings for the user

If you need to display a date as text for the user, it’s best if you use Cocoa’s built-in date styles. These are a set of predefined styles for formatting dates and times based on the user’s preferred settings. These styles, which are all values of the NSDateFormatterStyle enumeration, come in a selection of lengths — short, medium, long, and full — and using them is the preferred way to create date strings for the user to read.

Add the following code to your playground:

// (Previous code goes here)

let formatter = NSDateFormatter()

// No date style or time style defined:
// The result for the line below is
// ""
formatter.stringFromDate(firstLandPhoneCallDate)

// Only the date style is defined:
formatter.dateStyle = .MediumStyle
// The result for the line below is
// "Mar 10, 1876"
formatter.stringFromDate(firstLandPhoneCallDate)

// The date style and time style have been defined:
formatter.timeStyle = .ShortStyle
// The result for the line below is
// "Mar 10, 1876, 12:00 AM"
formatter.stringFromDate(firstLandPhoneCallDate)

Here’s how the various date and time formatter styles get rendered:

If dateStyle and timeStyle are both set to… the date formatter’s output looks like…
NoStyle
ShortStyle 1/27/10, 1:00 PM
MediumStyle Jan 27, 2010, 1:00:00 PM
LongStyle January 27, 2010 at 1:00:00 PM EST
FullStyle Wednesday, January 27, 2010 at 1:00:00 PM Eastern Standard Time

What’s up with NoStyle? It’s there so you can limit the resulting string so that it shows only the date or only the time.

NSDateFormatter has a timeZone property so that you can ensure that the date string reflects a specific time zone:

// (Previous code goes here)

formatter.dateStyle = .MediumStyle
formatter.timeStyle = .ShortStyle
formatter.timeZone = NSTimeZone(name: "US/Pacific")

// The result for the line below is
// "Jan 27, 2010, 10:00 AM"
formatter.stringFromDate(iPadAnnouncementDate)

Formatting date strings for other computers

As I wrote earlier, if you’re formatting dates for the user, it’s strongly recommended that you use NSDateFormatter‘s dateStyle and timeStyle properties, which will format dates and times according to the user’s settings. However, if you need to need your date strings to be in a specific format (for an API, for example), you can provide NSDateFormatter with a format string:

// (Previous code goes here)

// Setting the locale to POSIX ensures that
// the user's locale won't be used
formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")

// NSDateFormatter'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)
formatter.dateFormat = "y-MM-dd"

// The result for the line below is
// "2007-01-09"
formatter.stringFromDate(iPhoneAnnouncementDate)

While you can browse through Appendix F of the Unicode Technical Standard #35 to look at all the date format specifiers supported by NSDateFormatter, you might find it easier to use the table below. It shows a number of format strings applied to the iPhone announcement date (January 9, 2007 at 10:00 a.m. Pacific):

Format string Result
'Year: 'y' Month: 'M' Day: 'd Year: 2007 Month: 1 Day: 9
MM/dd/yy 01/09/07
MMM dd, yyyy Jan 09, 2007
E MMM dd, yyyy Tue Jan 09, 2007
EEEE, MMMM dd, yyyy' at 'h:mm a. Tuesday, January 09, 2007 at 10:00 AM.
EEEE, MMMM dd, yyyy' at 'h:mm a zzzz. Tuesday, January 09, 2007 at 10:00 AM Pacific Standard Time.

Turning strings into dates

If you specify a date format string, you can use NSDateFormatter to take a string following that format to turn it into an NSDate. For example:

// (Previous code goes here)

formatter.dateFormat = "yyyy/MM/dd hh:mm Z"
// This string will be converted to a date because
// it's in the same format as the dateFormat string:
formatter.dateFromString("2015/03/07 11:00 -0500")!
// This string will NOT be converted to a date because
// it's NOT in the same format as the dateFormat string;
// its result will be nil:
formatter.dateFromString("Mar 7, 2015 11:00 AM EST")

// Let's change the date format strings and try
// dateFromString with the same two strings:
formatter.dateFormat = "MMM d, yyyy hh:mm a zz"
// This results in an NSDate:
formatter.dateFromString("Mar 7, 2015 11:00 AM EST")!
// This results in nil:
formatter.dateFromString("2014/11/05")

Tying it all together

If you’ve made it to this point in the article, this chart should now make sense:

cocoa date time class chart

Once again, click the chart to see it at full size.

You should now be able to:

  • Create dates “from scratch” (that is, create them using an offset of a number of  seconds from the start of the third millennium)
  • Create dates from date components (that is, from numbers representing a day, month, and year)
  • Convert between dates and date components
  • Convert dates into string representations and string representations of dates into dates

In the next article in this series, we’ll look at date calculations and some handy functions that let you harness Swift’s expressive power and make working with dates easier.

dates and times in swift - smallRelated articles

A very brief introduction to date formatting in Swift and iOS: The oversight in a mostly-good book on Swift programming led me down the path of writing articles about dates and times in Swift, starting with this one, where I look atNSDateFormatter.

How to work with dates and times in Swift, part two: Calculations with dates: Now that we’ve got the basics, it’s time to do some date arithmetic: comparing two dates to see which one is the earlier and later one, finding out how far apart two dates are, and adding and subtracting from dates.

How to work with dates and times in Swift, part three: Making date arithmetic more Swift-like: Cocoa’s date and time classes have an Objective-C heritage, which in the Swift context, feel kind of clunky. In this article, I look at ways — and by ways, I mean helper functions and class extensions — to make date calculations feel more like Swift.

How to work with dates and times in Swift, part four: A more Swift-like way to get the time interval between two dates: This quick article shows you how to make an operator overload that makes getting the time interval between two dates more like subtraction.

20 replies on “How to work with dates and times in Swift, part one [Updated for Swift 2]”

I find it strange that Apple chose 2001 instead of 1970 as their base date.

At least it’s a simple offset to calculate.

No, it actually makes a lot of sense. The Unix-style reference date of 1970 is what always seemed amateurish to me. At least Apple uses the start of the third millennium CE.

This is the best summary of date handling I’ve found. Thanks.

Has something changed in the latest Xcode/Swift? My code has been working fine with the above approach but when I load it into the Xcode 7 beta, I get errors on the bit mask constants such as: ‘NSCalendarUnit.Type’ does not have a member named ‘CalendarUnitDay’. The documentation is saying all of the constants were deprecated in iOS 8.0 which doesn’t quite compute because I built and ran my app targeted for 8.3 just fine in Xcode 6.

This code wouldn’t compile for me:
let requestedDateComponents: NSCalendarUnit = [.Year,
.Month,
.Day,
.Hour,
.Minute,
.Weekday,
.WeekdayOrdinal]

Instead, this code did what I nedeed:

let userCalendar = NSCalendar.currentCalendar()
let requestedDateComponents = userCalendar.components( .CalendarUnitMonth |
.CalendarUnitDay |
.CalendarUnitYear |
.CalendarUnitWeekday,

fromDate: myDate)

Actually that wouldn’t compile for me either. Here is my solution:

let requestedDateComponents = userCalendar.components(NSCalendarUnit.CalendarUnitYear | NSCalendarUnit.CalendarUnitMonth | NSCalendarUnit.CalendarUnitDay | NSCalendarUnit.CalendarUnitWeekday, fromDate: myDate)

This was EXACTLY what I needed. I had no idea how to convert the components of the date picker into respective day (say Monday) and etc. Thanks.

It’s an fantastic explanation.

I’ve met one question that makes me confused. Could you explain it for me?
I want to specify the time: “Jan 1, 2001, 1:01 AM” by following code snippets. But I’ve got “Dec 31, 2001, 1:01 AM”. I have no ideas about it.

let localCalander = NSCalendar(calendarIdentifier: NSCalendarIdentifierGregorian)!
localCalander.timeZone = NSTimeZone(name: “Europe/Stockholm”)!

let dateComponents = NSDateComponents()
dateComponents.year = 2001
dateComponents.weekOfYear = 1
dateComponents.weekday = 2
dateComponents.hour = 1
dateComponents.minute = 1
dateComponents.timeZone = NSTimeZone(name: “Europe/Stockholm”)
let date2 = userCalendar.dateFromComponents(dateComponents)

You probably can do everything you can imagine with dates, and a lot you will never think up. That is, if you study this one tiny aspect of the language every relase for eight years, one day, sixteen hours, fourty seven minutes, twenty nine . 0045678 seconds. Ok it’great to have all this when you need it, and if you bill by the six minute interval.

And yet..why is there not a simple wrapper over this giant obtuse api that is so horrifically documented so I can just get the time components without all the NSDateCalendarUnitUpYourRearWasteYourTimeIlluminate() objectionableness?

There is nooo enum in NSDate.h!!!! NSCalendarUnit is a struct, so how does enum syntax apply here? The array of requested components does not work (anymore) and the | has to be separated by whitespace or it says | is not a postfix operator!

I hate this so much, I know about 10 other languages and this is the most time I ever spent learning to get and print the time.

How come most lines of code return this in Playgrounds
in this particular line of code it returns the above. Not until I call this line let firstSaturdayMarch2015Date = userCalendar.dateFromComponents(firstSaturdayMarch2015DateComponents) does it return a readable date.

Comments are closed.