Swift Date

A specific point in time, independent of any calendar or time zone.

Date & Time

In Swift, a Date() value encapsulates a single point in time, independent of any particular time zone or calendarical system. To accomplish this, a Date() value is stored as a 64-bit floating point number counting the number of seconds as a relative offset from the reference date of January 1, 2001 at 00:00:00 UTC.

The Date type is part of the Foundation framework, so it’s a fundamental type to work with dates on iOS, watchOS, macOS, Catalyst, and tvOS.

Note: UTC stands for Coordinated Universal Time.

Let's begin with creating a variable referring to the current date/time and printout the result.

let now = Date()
print("Current date/time: \(now)")

In the above Swift code note that the default date/time output is printed as an ISO8061-formatted string referenced to UTC.

let timestamp = Date().timeIntervalSince1970
print("Number of seconds since ref date: \(timestamp)")

The above Swift code prints the actual number of seconds relative to the reference date (January 1, 1970 at 00:00:00 UTC).

Note: The timeIntervalSince1970 property’s value is negative if the date object is earlier than 00:00:00 UTC on 1 January 1970.

However, the real feature-rich functionality benefit in Swift comes from using DateFormatter, Locale, and Calendar. This is how Swift supports timezones, localized date formats, and calendar arithmetic (e.g., 1 month ago, 5 days later).

A DateFormatter let us convert between dates and their textual representations.

Convert Date to String: Formatting Date & Locale

Suppose we got a Date and want to display it to our app’s users. How do we convert a Date to a String?

let now = Date()

let dtFormatter = DateFormatter()
dtFormatter.dateStyle = .full
dtFormatter.timeStyle = .full

let formattedDateTime = dtFormatter.string(from: now)
print(formattedDateTime)

The above Swift code printed "Monday, March 01, 2021 at 9:00:00 AM India Standard Time" in the console.

We can choose from various styles with the dateStyle and timeStyle properties. Both take values from the DateFormatter.Style enum. We can combine dateStyle and timeStyle to include date or time or both. Below is list of what both styles can do:

DateFormatter.StyleDateTime
.none(nothing!)(nothing!)
.short1/3/219:15 AM
.mediumMar 1, 20219:15:09 AM
.longMarch 1, 20219:15:09 AM GMT+5:30
.fullMonday, March 1, 20219:15:09 AM India Standard Time

The exact date format depend on a system’s locale. What’s a locale? In short, it’s a set of parameters that defines a user’s language, region (or country), and any additional settings or variations.

The above table was generated with the en_IN locale, which means it uses the Indian English style of formatting date and time: AM/PM, and month/day/year. Compare this to different countries and locales around the world, that use a 24-hour clock, and day-month-year, and you see why locales are so important!

Want to know what your system’s locale is? Try to run the following code in Xcode Playground, and check out the result.

let dtFormatter = DateFormatter()
print(dtFormatter.locale ?? "N/A")

The DateFormatter object uses the iPhone’s system locale by default. If we’re creating a DateFormatter object, and we use dateStyle and timeStyle, and then string(from:), we’re almost guaranteed to create a textual representation of date/time in the user’s locale.

In case we want to use a custom date/time format? Here’s how we can do that:

let now = Date()

let dtFormatter = DateFormatter()
dtFormatter.locale = Locale(identifier: "en_GB")
dtFormatter.setLocalizedDateFormatFromTemplate("dd-MM-yyyy")

let formattedDateTime = dtFormatter.string(from: now)
print(formattedDateTime)

The above Swift code prints a date style that’s common in The UK, with dashes - and day-month-year.

Convert String to Date: Parsing Date & Timezone

A few use cases about when we should to convert strings to dates:

  • We’ve received a well-formatted ISO 8601 String from database, and we need to convert it to a Date in Swift
  • We’ve stored a Unix timestamp in a database, that we need to parse to Date in Swift
  • Data we’re working with has some specific date formatting, that we want to parse it to a Date in Swift
let dtFormatter = DateFormatter()
dtFormatter.dateFormat = "dd-MM-yyyy HH:mm:ss Z"

if let dt = dtFormatter.date(from: "01-03-2021 09:15:07 +0530") {
    print(dt)
}

The above Swift code printed "2021-03-01 03:45:07 +0000" in the console.

We can already see a discrepancy between the date string and the printed Date object. We’ve specified 09:15:07, but when datetime is printed out, we’re getting a 03:45:07 back. Why is that?

The Mac that I ran the above code on is set to the India Standard Time (IST), which is five hour thirty minutes past Coordinated Universal Time (UTC). Based on the +0000, we can also see that the printed datetime object doesn’t have a timezone offset – so it’s in UTC.

We can verify a Mac or iPhone’s timezone with this code:

let timeZone = TimeZone.current
print(timeZone)

The above Swift code printed "Asia/Kolkata (current)" in the console on my Mac.

The ISO8601DateFormatter class in Swift representation of the ISO 8601 date/time format, that makes us hussle free from timezones and locales.

Here’s how we can print the current date/time using ISO8601DateFormatter:

let now = Date()
let dtFormatter = ISO8601DateFormatter()
let formattedDateTime = dtFormatter.string(from: now)
print(formattedDateTime)

The above Swift code printed "2021-03-01T09:15:07Z" in the console.

let dtFormatter = ISO8601DateFormatter()
if let dt = dtFormatter.date(from: "2021-03-01T09:15:07Z") {
    print(dt)
}

The above Swift code shows how we can convert a ISO 8601 format date/time string to a Date object:

Below table shows the available format options of Year in DateFormatter:

CharactersExampleDescription
y2021Year, no padding
yy21Year, two digits (padding with a zero if necessary)
yyyy2021Year, minimum of four digits (padding with zeros if necessary)

Below table shows the available format options of Quarter in DateFormatter:

CharactersExampleDescription
Q4The quarter of the year. Use QQ if you want zero padding
QQQQ4Quarter including "Q"
QQQQ4th quarterQuarter spelled out

Below table shows the available format options of Month in DateFormatter:

CharactersExampleDescription
M3The numeric month of the year. A single M will use '3' for March
MM03The numeric month of the year. A double M will use '03' for March
MMMMarThe shorthand name of the month
MMMMMarchFull name of the month
MMMMMMNarrow name of the month

Below table shows the available format options of Day in DateFormatter:

CharactersExampleDescription
d1The day of the month. A single d will use 1 for March 1st
dd01The day of the month. A single d will use 01 for March 1st
F1st Mondy in MarchThe day of week in the month
EMonThe abbreviation for the day of the week
EEEEMondayThe wide name of the day of the week
EEEEEMThe narrow day of week
EEEEEEMoThe short day of week

Below table shows the available format options of Hour in DateFormatter:

CharactersExampleDescription
h9The 12-hour hour
hh09The 12-hour hour padding with a zero if there is only 1 digit
H21The 24-hour hour
HH21The 24-hour hour padding with a zero if there is only 1 digit
aAMAM / PM for 12-hour time formats

Below table shows the available format options of Minute in DateFormatter:

CharactersExampleDescription
m15The minute, with no padding for zeroes
mm15The minute with zero padding

Below table shows the available format options of Second in DateFormatter:

CharactersExampleDescription
s7The seconds, with no padding for zeroes
ss07The seconds with zero padding
SSS700The milliseconds

Below table shows the available format options of Time zone in DateFormatter:

CharactersExampleDescription
zzzISTThe 3 letter name of the time zone. Falls back to GMT-08:00 (hour offset) if the name is not known
zzzzIndia Standard TimeThe expanded time zone name, falls back to GMT-08:00 (hour offset) if name is not known
ZZZZIST-05:30Time zone with abbreviation and offset
Z+0530RFC 822 GMT format. Can also match a literal Z for Zulu (UTC) time
ZZZZZ+05:30ISO 8601 time zone format

Creating & calculating Date with DateComponents

The DateComponents isn’t only useful to create dates – it’s also helpful for specifying durations. The DateComponents struct can both define points in time, as well as durations of time.

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.

let now = DateComponents(calendar: Calendar.current, year: 2021, month: 3, day: 1)
print(now)

The above Swift code printed "calendar: gregorian (current) year: 2021 month: 3 day: 1 isLeapMonth: false" in the console.

We can also use individual date components to add more units to a specific date.

let now = DateComponents(calendar: Calendar.current, year: 2021, month: 3, day: 1)
if let dt = now.date, let later = Calendar.current.date(byAdding: .month, value: 1, to: dt) {
    print(later)
}

The above Swift code printed "2021-03-31 18:30:00 +0000" in the console.

Using Calendar & DateCmponents we can also get start and end of a specific date.

let todayStartOfDay = Calendar.current.startOfDay(for: Date())
let todayEndOfDay = Calendar.current.date(byAdding: DateComponents(day: 1), to: todayStartOfDay)?.addingTimeInterval(-1)

print(todayStartOfDay)
print(todayEndOfDay!)

The above Swift code prints time of current date's start and end of day in the console.

if let date: Date = DateComponents(calendar: Calendar.current, year: 2020, month: 3, day: 1).date {
    let units = Array<Calendar.Component>([.year, .month, .day, .hour, .minute, .second])
    let components = Calendar.current.dateComponents(Set(units), from: date, to: Date())
    
    for unit in units {
        guard let value = components.value(for: unit) else {
            continue
        }
        
        if value > 0 {
            print("\(value) \(unit)s ago")
        }
    }
}

The above Swift code printed "11 months ago", "27 days ago", "22 hours ago", "48 minutes ago", and "16 seconds ago" in the console:

An exmaple of using Date & extension in practical Swift development

extension Date {
    
    init?(value: String, format: String) {
        let dtFormatter = DateFormatter()
        dtFormatter.dateFormat = format
        
        if let date = dtFormatter.date(from: value) {
            self = date
        } else {
            return nil
        }
    }
    
    init?(value: String, format: String, timeZone: TimeZone? = nil) {
        let dtFormatter = DateFormatter()
        dtFormatter.dateFormat = format
        if let value = timeZone {
            dtFormatter.timeZone = value
        }
        
        if let date = dtFormatter.date(from: value) {
            self = date
        } else {
            return nil
        }
    }
    
    func to(format: String) -> String {
        let dtFormatter = DateFormatter()
        dtFormatter.dateFormat = format
        
        return dtFormatter.string(from: self)
    }
    
    func to(format: String, timeZone: TimeZone) -> String {
        let dtFormatter = DateFormatter()
        dtFormatter.dateFormat = format
        dtFormatter.timeZone = timeZone
        return dtFormatter.string(from: self)
    }
    
    func to(dateStyle: DateFormatter.Style, relativeFormatting: Bool = false, timeZone: TimeZone? = nil) -> String {
        let dtFormatter = DateFormatter()
        dtFormatter.dateStyle = dateStyle
        dtFormatter.doesRelativeDateFormatting = relativeFormatting
        if let value = timeZone {
            dtFormatter.timeZone = value
        }
        
        return dtFormatter.string(from: self)
    }
}

if let dt = Date(value: "2021-01-01T08:35:00", format: "yyyy-MM-dd'T'HH:mm:ss", timeZone: TimeZone(abbreviation: "UTC")) {
    print(dt.to(format: "dd-MM-yyyy", timeZone: (TimeZone(abbreviation: "IST") ?? TimeZone.current)))
}

The above Swift code printed "01-01-2021" in the console of Xcode Playground.

Conclusion

Swift Date struct is very usefull when it comes to deal with date and time in Swift development. However, most of those complexities can be handled for us by the system, as long as we use the right APIs to perform our date calculations.