Categories
Swift Kick

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

cocoa date time class chart

Click the chart to see it at full size.

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 kick

In the previous installment in this series that looks at working with dates and times in Cocoa, we looked at:

  • The NSDate class, which is the heart of date and time in Cocoa, and represents a single point in time relative to the start of the third millennium (midnight, January 1, 2001)
  • the NSCalendar class, which provides a context for dates and the ability to do date arithmetic,
  • the NSDateComponents class, which represents the parts that make up either a date or a span of time, and
  • the NSDateFormatter class, which turns dates into string representations and vice versa.

We also covered creating dates, converting components into dates and vice versa, and converting dates into strings and vice versa. With this knowledge under our belts, let’s get to this article’s topic: doing date calculations.

Creating a couple of dates to work with

valentines st patricks

Start with a fresh playground, and enter or paste the following code so it looks like this:

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

import UIKit

let userCalendar = NSCalendar.currentCalendar()

// Let's create an NSDate for Valentine's Day
// using NSDateComponents
let valentinesDayComponents = NSDateComponents()
valentinesDayComponents.year = 2015
valentinesDayComponents.month = 2
valentinesDayComponents.day = 14
let valentinesDay = userCalendar.dateFromComponents(valentinesDayComponents)!

// Let's create an NSDate for St. Patrick's Day
// using NSDateFormatter
let dateMakerFormatter = NSDateFormatter()
dateMakerFormatter.calendar = userCalendar
dateMakerFormatter.dateFormat = "yyyy/MM/dd"
let stPatricksDay = dateMakerFormatter.dateFromString("2015/03/17")!

In the code above, we’re creating two dates in two different ways (which we covered in the previous article):

  • We’re creating Valentine’s Day (February 14 for those of you in places where it’s not celebrated) by setting up date components and then using the user’s calendar to convert the components into a date.
  • We’re creating St. Patrick’s Day (March 17 for those of you in places where it’s not celebrated) by converting a string representing that date into a date by means of a date formatter. You may find that if you need to instantiate a large number of dates in code, you may want to do so this way, as you can do it in far fewer lines than by using date components.

Now that we have a couple of date objects, let’s do some date arithmetic!

Which came first?

which came first

NSDate has two methods, earlierDate and laterDate, which compare one date to another and return the appropriate date. Add the highlighted code below so that your playground looks like this:

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

import UIKit

let userCalendar = NSCalendar.currentCalendar()

// Let's create an NSDate for Valentine's Day
// using NSDateComponents
let valentinesDayComponents = NSDateComponents()
valentinesDayComponents.year = 2015
valentinesDayComponents.month = 2
valentinesDayComponents.day = 14
let valentinesDay = userCalendar.dateFromComponents(valentinesDayComponents)!

// Let's create an NSDate for St. Patrick's Day
// using NSDateFormatter
let dateMakerFormatter = NSDateFormatter()
dateMakerFormatter.calendar = userCalendar
dateMakerFormatter.dateFormat = "yyyy/MM/dd"
let stPatricksDay = dateMakerFormatter.dateFromString("2015/03/17")!

// Which date comes first? Which comes last?
valentinesDay.earlierDate(stPatricksDay)
valentinesDay.laterDate(stPatricksDay)

This is pretty straightforward: valentinesDay.earlierDate(stPatricksDay) returns the valentinesDay instance, while valentinesDay.laterDate(stPatricksDay) returns stPatricksDay.

NSDate has a compare method that works in a way similar to a lot of other “compare” methods (such as C’s strcmp) that compare a value a and b, where:

  • If a < b, it returns a negative number
  • if a == b, it returns 0
  • if a > b, it returns a positive number

Cocoa comparison methods return values of type NSComparisonResult, so that when you’re comparing two values a and b:

  • If a < b, it returns NSOrderedAscending
  • if a == b, it returns NSOrderedSame
  • if a > b, it returns NSOrderedDescending

Let’s take it out for a spin. Add the highlighted code below so that your playground looks like this:

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

import UIKit

let userCalendar = NSCalendar.currentCalendar()

// Let's create an NSDate for Valentine's Day
// using NSDateComponents
let valentinesDayComponents = NSDateComponents()
valentinesDayComponents.year = 2015
valentinesDayComponents.month = 2
valentinesDayComponents.day = 14
let valentinesDay = userCalendar.dateFromComponents(valentinesDayComponents)!

// Let's create an NSDate for St. Patrick's Day
// using NSDateFormatter
let dateMakerFormatter = NSDateFormatter()
dateMakerFormatter.calendar = userCalendar
dateMakerFormatter.dateFormat = "yyyy/MM/dd"
let stPatricksDay = dateMakerFormatter.dateFromString("2015/03/17")!

// Which date comes first? Which comes last?
valentinesDay.earlierDate(stPatricksDay)
valentinesDay.laterDate(stPatricksDay)

// Another way to compare dates
let dayOrder = valentinesDay.compare(stPatricksDay)
if dayOrder == .OrderedAscending {
  print("Valentine's Day comes before St. Patrick's Day.")
}
else if dayOrder == .OrderedDescending {
  print("Valentine's Day comes after St. Patrick's Day.")
}
else if dayOrder == .OrderedSame {
  print("They're the same day!")
}
else {
  print("Something weird happened.")
}

Valentine’s Day comes before St. Patrick’s Day, so the result you see the in sidebar should be Valentine’s Day comes before St. Patrick’s Day.

Date arithmetic: How far apart are two dates and times?

countdown clock

NSDate has the timeIntervalSinceDate method, which gives you the difference between 2 dates…in seconds.

// (Previous code goes here)

// Not all that useful
valentinesDay.timeIntervalSinceDate(stPatricksDay)
stPatricksDay.timeIntervalSinceDate(valentinesDay)

Since Valentine’s Day comes before St. Patrick’s Day, the first value is negative, while the second value is positive. Most users won’t find knowing that there are nearly 2.7 million seconds between the two days. How can we find out the number of days between Valentine’s and St. Patrick’s?

That’s where date components come in. I mentioned last time that date components can represent either:

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

We’re going to use date components for the second purpose in this example. We need to do the following:

  • Specify the units of time that we want from the calculation, which in this case is days, and
  • Provide those units of time and the two dates to NSCalendar‘s components method:
// (Previous code goes here)

// How many days between Valentine's Day and St. Patrick's Day?
let dayCalendarUnit: NSCalendarUnit = [.Day]
let stPatricksValentinesDayDifference = userCalendar.components(
  dayCalendarUnit,
  fromDate: valentinesDay,
  toDate: stPatricksDay,
  options: [])
// The result should be 31
stPatricksValentinesDayDifference.day

This version of NSCalendar‘s components method takes the following arguments:

  • unitFlags: An option set of components we want to retrieve. Since we want to know the number of days between Valentine’s and St. Patrick’s, there’s only one option in the set: .Day.
  • fromDate: The start date in the calculation.
  • toDate: The end date in the calculation.
  • options: In most cases, you’ll want this set to [], which means no options. This causes overflows in a unit to carry to the next higher unit. For example, if you specify in unitFlags that you want your result expressed in minutes and seconds, and the calculation’s result is 61 seconds, the result will be changed to 1 minute, 1 second.

Let’s try another calculation: what’s the time difference between 10:45 a.m. and 12:00 noon?

// (Previous code goes here)

// How many hours and minutes between 10:45 a.m. and 12:00 noon?
dateMakerFormatter.dateFormat = "yyyy/MM/dd hh:mm a Z"
let startTime = dateMakerFormatter.dateFromString("2015/01/27 10:45 AM -05:00")!
let endTime = dateMakerFormatter.dateFromString("2015/01/27 12:00 PM -05:00")!
let hourMinuteComponents: NSCalendarUnit = [.Hour, .Minute]
let timeDifference = userCalendar.components(
  hourMinuteComponents,
  fromDate: startTime,
  toDate: endTime,
  options: [])
timeDifference.hour
timeDifference.minute

You should see in the sidebar that timeDifference.hour‘s value is 1 and timeDifference.minute‘s value is 15.

Date addition and subtraction

plus minus dice

If you’re writing some kind of reminder app, you might want to be able to let the user say “give me a reminder in 10 days”, which means you’ll need to calculate what the date and time will be 10 days from now. Since we’re doing date addition with only one unit, we can perform this calculation by using NSCalendar‘s dateByAddingUnit method:

// (Previous code goes here)

// What will the date and time be be ten days from now?
let tenDaysFromNow = userCalendar.dateByAddingUnit(
  [.Day],
  value: 10,
  toDate: NSDate(),
  options: [])!

// What weekday (Sunday through Saturday) will it be ten days from now, and
// which weekday of the month will it be -- the 1st, 2nd, 3rd...?
let weekdayAndWeekdayOrdinal: NSCalendarUnit = [.Weekday, .WeekdayOrdinal]
let tenDaysFromNowComponents = userCalendar.components(
  weekdayAndWeekdayOrdinal,
  fromDate: tenDaysFromNow)
tenDaysFromNowComponents.weekday
tenDaysFromNowComponents.weekdayOrdinal

dateByAddingUnit expects the following parameters:

  • unit: The type of unit to be added to the date. We want to add days, so we’re setting this value to [.Day].
  • value: The number of units to be added to the date. We want to know what the date will be 10 days from now, so we set this to 10.
  • toDate: The date to which we’ll be adding units. We want to add 10 days to today, so we set this to a new NSDate (remember, instantiating an NSDate object without parameters creates an instance that refers to the current date and time).
  • options: In most cases, you’ll want to set this to [].

I ran the code on January 28, 2015 at 11:29 p.m., so the resulting date stored in tenDaysFromNow is displayed in my playground’s sidebar as Feb 7, 2015, 11:29 PM.

I wanted to know what day of the week it would be 10 days from now, and which weekday of the month (the first, second, third…?) so I used the version of NSCalendar‘s components method that takes a single date and use it to extract the weekday and weekdayOrdinal components from tenDaysFromNow. At the time I ran the code, tenDaysFromNowComponents.weekday‘s value was 7 (Saturday) and tenDaysFromNowComponents.weekdayOrdinal‘s value was 1 (meaning that it’s the first Saturday of the month).

Here’s another calculation: what was the date and time 3 days, 10 hours, 42 minutes, and 5 seconds ago? We’re doing date arithmetic with more than one kind of unit — days, hours, minutes, and seconds — so we need NSCalendar‘s dateByAddingComponents method instead:

// (Previous code goes here)

// What was the date and time 3 days, 10 hours, 42 minutes, and 5 seconds ago?
let periodComponents = NSDateComponents()
periodComponents.day = -3
periodComponents.hour = -10
periodComponents.minute = -42
periodComponents.second = -5
let then = userCalendar.dateByAddingComponents(
  periodComponents,
  toDate: NSDate(),
  options: [])!

dateByAddingComponents expects the following parameters:

  • comps: the date components that we want to add to the given date. Note that since we’re performing date subtraction, all the components are expressed as negative numbers.
  • toDate: The date from which we’ll be adding (or in this case, subtracting). We want to subtract from the current date and time, so we set this to a new NSDate object instantiated without parameters (which, as I’ve said before, gives an instance representing the current date and time).
  • options: In most cases, you’ll want to set this to [].

I ran the code on January 28, 2015 at 11:52 p.m., so the resulting date stored in then is displayed in my playground’s sidebar as Jan 25, 2015, 1:10 PM.

In closing

In this article, we covered:

  • Comparing two dates to see which one is the earlier on and which is the later one,
  • finding out how far apart two dates are, and
  • adding and subtracting from dates.

In the next installment, we’ll a closer look at date calculations and see some convenience methods that make date and time calculations simpler.

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 one: An introduction of Cocoa’s date and time classes, and how they work together. This article covers UTC (Coordinated Universal Time), and the key classes: NSDate, NSCalendar, NSDateComponents.

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.

5 replies on “How to work with dates and times in Swift, part two: Calculations with dates [Updated for Swift 2]”

[…] 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. […]

Awesome article! Thanks so much for this.

I’ve managed to convert my date strings into the date conventions of “Just now”, “x minutes ago”, “x hours ago” etc. I’ve been struggling with it for a couple of days…20 minutes reading this article and I’ve nailed it!

Thanks.

Comments are closed.