Working with Dates in Swift iOS 04.03.2020

Each of following types have their purposes. Date is the simplest and best for the current date and dates based on the current date. To show an interval in seconds, use the TimeInterval. To work with indivdual date components, use DateComponents.

Date

Date is an object which represent a single point of time. It is independent of any particular calendrical system or time zone.

import Foundation
let date = Date()

This will generate a timestamp at the time code is executed. By default, it uses the GMT+0 timezone.

Create a date by adding time interval-in seconds - to current date

let date = Date.init(timeIntervalSinceNow: 86400)

Create a date by adding time interval – in seconds – since reference date

let date = Date.init(timeIntervalSinceReferenceDate: 86400)

Create a date by adding time interval – in seconds – since 1970

let date = Date(timeIntervalSince1970: 1577232000.0)

This type also supports comparisons with the operators (<, ==) and with the compare method.

We will use DateFormatter to change the way it will present to users with specific formating. A formatter providing methods for converting from Date to String and from String to Date.

let date = Date()

var formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ"
let defaultTimeZoneStr = formatter.string(from: date)

DateFormatter can change attibute like dateFormat, and timeZone.

formatter.timeZone = TimeZone(abbreviation: "UTC")
let utcTimeZoneStr = formatter.string(from: date)

If you want to see all the available formats for a DateFormatter you can go to nsdateformatter.com.

Also you can setup date formatters via a style for the date using the dateStyle property and time property timeStyle. You have a choice of .full, .long, .medium, .short and .none.

On the other hand, you might want a measure of time. For this, use the type TimeInterval. This is a measure of time using seconds. They are of Double type. I’ll set a few constants using time intervals for day, hour and minute.

let minute:TimeInterval = 60.0
let hour:TimeInterval = 60.0 * minute
let day:TimeInterval = 24 * hour

ISO8601DateFormatter

This is a standard time formatting that being used frequently on web server like Ruby on Rails. New API since iOS 10.

The format is like "2020-01-17T22:03:15Z". Or in dateFormat ""yyyy-MM-dd'T'HH:mm:ssZ". By using this class you don't have to write the dateFormat anymore to avoid mistakes.

let date = Date();
var isoformatter = ISO8601DateFormatter.init()
let timeStr = isoformatter.string(from: date)

var dateFromString = isoformatter.date(from: timeStr)

DateComponents

DateComponents encapsulates the components of a date in an extendable, structured manner.

It is used to specify a date by providing the temporal components that make up a date and time in a particular calendar: hour, minutes, seconds, day, month, year, and so on. It can also be used to specify a duration of time, for example, 5 hours and 16 minutes. A DateComponents is not required to define all the component fields.

When a new instance of DateComponents is created, the date components are set to nil.

var dateCompo = DateComponents()
dateCompo.hour = 8
dateCompo.minute = 30
dateCompo.day = 0
dateCompo.calendar = Calendar.current

var myDate = dateCompo.date

var dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm:ss ZZZ EEEE"

var dateString = dateFormatter.string(from: myDate!)

Date components can be changed. The date components are all optional type Int. While it is easier to change them using Date and TimeInterval, you might need to do it this way if you’ve created a series of controls in your UI to set a date. For example, to add five days do this:

dateCompo.day = dateCompo.day! + 5
myDate = dateCompo.date
print(dateFormatter.string(from: myDate!))

DateComponentsFormatter is all about converting a TimeInterval or DateComponents into a nicely formatted, human-readable String.

let formatter = DateComponentsFormatter()
formatter.unitsStyle = .full
formatter.allowedUnits = [.minute, .second]
formatter.string(from: 543.0) // "9 minutes, 3 seconds"

formatter.unitsStyle = .abbreviated
formatter.string(from: 123.0) // "2m 3s"

formatter.unitsStyle = .spellOut
formatter.string(from: 123.0) // "two minutes, three seconds"

formatter.includesApproximationPhrase = true
formatter.includesTimeRemainingPhrase = true
formatter.unitsStyle = .brief
formatter.string(from: 123.0)
// "About 2min 3sec remaining"

Calendar

This class is the class that manipulates all the others. Using Calendar you can work with dates based on a calendar.

var comps = DateComponents()
comps.day = 17
comps.month = 1
comps.year = 2020
comps.hour = 22
comps.minute = 26

let cal = Calendar.current

// components to date
let date = cal.date(from: comps)

// get specific components from date
let comp2 = cal.dateComponents([.hour, .minute], from: Date())

// get a specific component from date
let weekday = cal.component(.weekday, from: Date())

// adding 2 days to a Date
let nextWeek = cal.date(byAdding: .day, value: 2, to: Date())

// return the difference in hours between 2 dates
comps.day = 20
let date2 = cal.date(from: comps)

let interval = cal.dateComponents([.hour], from: date!, to: date2!)

Calendar has the method func isDateInToday to check if Date is from today

let date = Date()
print(Calendar.current.isDateInToday(date))

Calendar has the method func isDate to check if Date is from the same day

let date1 = Date()
let date2 = Date()
print(Calendar.current.isDate(date1, inSameDayAs: date2))

// check if from the same week
let date1 = Date()
let date2 = Date()
print(Calendar.current.isDate(date1, equalTo: date2, toGranularity: .weekOfYear))

// check if from this month

let date1 = Date()
let date2 = Date()
print(Calendar.current.isDate(date1, equalTo: date2, toGranularity: .month))

// check if from this year
let date1 = Date()
let date2 = Date()
print(Calendar.current.isDate(date1, equalTo: date2, toGranularity: .year)

You can use DateComponents to change the year, month or day of any Date object. Here’s an example where we change all three:

let date = Date()
var dateComponents = Calendar.current.dateComponents([.hour, .minute, .second], from: date)

dateComponents.day = 1
dateComponents.month = 2
dateComponents.year = 2000

let newDate = Calendar.current.date(from: dateComponents)

DateInterval

As the name implies, a DateInterval instance defines an interval between two dates.

let now = Date()
let tomorrow = now.addingTimeInterval(24.0 * 3600.0)
let dateInterval = DateInterval(start: now, end: tomorrow)

It defines three properties:

  • start of type Date
  • end of type Date
  • duration of type TimeInterval

My favorite method of the DateInterval structure is the intersection(with:) method. This method accepts another date interval and returns the intersection of the date intervals.

import Foundation

let start1 = Date(timeIntervalSinceNow: -470482.0)
let end1 = Date(timeIntervalSinceNow: 20482.0)

let start2 = start1.addingTimeInterval(112560.0)
let end2 = end1.addingTimeInterval(-222304.0)

let dateInterval1 = DateInterval(start: start1, end: end1)
let dateInterval2 = DateInterval(start: start2, end: end2)

let intersection = dateInterval1.intersection(with: dateInterval2)

DateIntervalFormatter is similar to the basic DateFormatter, but it displays both a beginning and end date.

To use it, create a formatter and generate a string from two dates:

let formatter = DateIntervalFormatter()

let currentDate = Date()

let twoMinutesAgo = Calendar.current.date(byAdding: .minute, value: -2, to: currentDate) ?? currentDate
formatter.string(from: currentDate, to: twoMinutesAgo) 

let fiveDaysAway = Calendar.current.date(byAdding: .day, value: 5, to: currentDate) ?? currentDate
formatter.string(from: currentDate, to: fiveDaysAway) 

Note that by default, the output will be based on the locale and time style from the device preferences.

DateIntervalFormatter uses the same date styles that the basic date formatter does.

formatter.dateStyle = .long
formatter.string(from: currentDate, to: fiveDaysAway) // 15-20 January 2020

formatter.timeStyle = .short
formatter.string(from: currentDate, to: twoMinutesAgo) // 09:00-09:02 AM

Date to String to Date

Sometime we just want to save the date as string. And get it back from String to Date.

// date to string
var date = Date()

let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd HH:mm"
var dateString = dateFormatter.string(from:date)

// string to date
let strTime = "2015-07-27 19:29"
let formatter = DateFormatter()
formatter.dateFormat = "yyyy-MM-dd HH:mm"
var date = formatter.date(from: strTime)

Comparing Dates

Date() conforms to Comparable protocol so you can simply use < , > and == to compare two dates.

if leftDate < rightDate { 
       print("leftDate is earlier than rightDate") 
} else if leftDate > rightDate {
       print("leftDate is later than rightDate")
} else if leftDate == rightDate {
       print("dates are equal")
}

Get day of the week from date

To get the day of the week from a date in Swift, use DateFormatter:

let date = Date()
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "EEEE"
let dayOfTheWeekString = dateFormatter.string(from: date)

This will give you the full name of the day of the week, such as "Friday".

If you want to get the number 1-7, with 1 representing Sunday and 7 representing Saturday, use dateComponents:

let date = Date()
let calendar = Calendar.current
let components = calendar.dateComponents([.weekday], from: date)
let dayOfWeek = components.weekday

Get day of the month

To get the day of the month, you can use dateComponents:

let date = Date()
let calendar = Calendar.current
let components = calendar.dateComponents([.day], from: date)
let dayOfMonth = components.day

Relative time

Before iOS 13

You can add DateTools or SwiftDate to your Podfile.

Or add follwoing extension to Date

extension Date {
  func years(from date: Date) -> Int {
    return Calendar.current.dateComponents([.year], from: date, to: self).year ?? 0
  }
  func months(from date: Date) -> Int {
    return Calendar.current.dateComponents([.month], from: date, to: self).month ?? 0
  }
  func weeks(from date: Date) -> Int {
    return Calendar.current.dateComponents([.weekOfYear], from: date, to: self).weekOfYear ?? 0
  }
  func days(from date: Date) -> Int {
    return Calendar.current.dateComponents([.day], from: date, to: self).day ?? 0
  }
  func hours(from date: Date) -> Int {
    return Calendar.current.dateComponents([.hour], from: date, to: self).hour ?? 0
  }
  func minutes(from date: Date) -> Int {
    return Calendar.current.dateComponents([.minute], from: date, to: self).minute ?? 0
  }
  func seconds(from date: Date) -> Int {
    return Calendar.current.dateComponents([.second], from: date, to: self).second ?? 0
  }
  var relativeTime: String {
    let now = Date()
    if now.years(from: self)   > 0 {
      return now.years(from: self).description  + " year"  + { return now.years(from: self)   > 1 ? "s" : "" }() + " ago"
    }
    if now.months(from: self)  > 0 {
      return now.months(from: self).description + " month" + { return now.months(from: self)  > 1 ? "s" : "" }() + " ago"
    }
    if now.weeks(from:self)   > 0 {
      return now.weeks(from: self).description  + " week"  + { return now.weeks(from: self)   > 1 ? "s" : "" }() + " ago"
    }
    if now.days(from: self)  > 0 {
      if now.days(from:self) == 1 { return "Yesterday" }
      return now.days(from: self).description + " days ago"
    }
    if now.hours(from: self)   > 0 {
      return "\(now.hours(from: self)) hour"   + { return now.hours(from: self)   > 1 ? "s" : "" }() + " ago"
    }
    if now.minutes(from: self) > 0 {
      return "\(now.minutes(from: self)) minute" + { return now.minutes(from: self) > 1 ? "s" : "" }() + " ago"
    }
    if now.seconds(from: self) > 0 {
      if now.seconds(from: self) < 15 { return "Just now"  }
      return "\(now.seconds(from: self)) second" + { return now.seconds(from: self) > 1 ? "s" : "" }() + " ago"
    }
    return ""
  }
}

Usage

let timeInterval = TimeInterval(-3600.0)
let date = Date(timeIntervalSinceNow: timeInterval)
print(date.relativeTime)

After iOS 13

At WWDC19, Apple added a new RelativeDateTimeFormatter, which formats relative dates from the current date, for example by formatting a past date as "X days ago" or today as "today".

RelativeDateTimeFormatter requires Xcode 11 and the latest beta versions of macOS 10.15 or iOS 13 which it is not available on previous versions.

Using this new formatter is very easy. It provides three methods which you can use to do the formatting itself:

  • localizedString(fromTimeInterval:). This takes a TimeInterval and formats the difference between the current time in the user’s device and the passed interval.
  • localizedString(for:relativeTo:). You can use this method to get the time difference between two different dates.
  • localizedString(from:). This methods takes a DateComponents object, so you can easily construct objects and check their time difference relative to the current time on the device.

To use it, create a formatter and generate a string from two dates. RelativeDateTimeFormatter supports different date/time and unit styles.

let formatter = RelativeDateTimeFormatter()

formatter.localizedString(fromTimeInterval: 60.0) // "in 1 minute"

formatter.dateTimeStyle = .named
formatter.unitsStyle = .full

let currentDate = Date()
formatter.localizedString(for: currentDate, relativeTo: currentDate) // now

let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: currentDate) ?? currentDate
formatter.localizedString(for: yesterday, relativeTo: currentDate) // yesterday

let fiveDays = Calendar.current.date(byAdding: .day, value: 5, to: currentDate) ?? currentDate
formatter.localizedString(for: fiveDays, relativeTo: currentDate) // in 5 days

You can also use a instance of DateComponents, which will format it based on the current date.

let minusOneDay = DateComponents(day: -1)
formatter.localizedString(from: minusOneDay) // yesterday

let plusOneDay = DateComponents(day: 1)
formatter.localizedString(from: plusOneDay) // tomorrow

RelativeDateTimeFormatter supports two different date/time styles, which can be used by setting them before generating the string.

formatter.dateTimeStyle = .numeric // .numeric or .named

The .numeric style is the default style and always uses the literal definition of the date.

formatter.dateTimeStyle = .numeric

formatter.localizedString(for: currentDate, relativeTo: currentDate) // in 0 seconds
formatter.localizedString(for: yesterday, relativeTo: currentDate) // 1 day ago
formatter.localizedString(for: fiveDays, relativeTo: currentDate) // in 5 days

The .named style falls back to the numeric style, but when possible, uses relative names such as yesterday or tomorrow.

formatter.dateTimeStyle = .named

formatter.localizedString(for: currentDate, relativeTo: currentDate) // now
formatter.localizedString(for: yesterday, relativeTo: currentDate) // yesterday
formatter.localizedString(for: fiveDays, relativeTo: currentDate) // in 5 days