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.Style | Date | Time |
---|---|---|
.none | (nothing!) | (nothing!) |
.short | 1/3/21 | 9:15 AM |
.medium | Mar 1, 2021 | 9:15:09 AM |
.long | March 1, 2021 | 9:15:09 AM GMT+5:30 |
.full | Monday, March 1, 2021 | 9: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 aDate
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
:
Characters | Example | Description |
---|---|---|
y | 2021 | Year, no padding |
yy | 21 | Year, two digits (padding with a zero if necessary) |
yyyy | 2021 | Year, minimum of four digits (padding with zeros if necessary) |
Below table shows the available format options of Quarter in DateFormatter
:
Characters | Example | Description |
---|---|---|
Q | 4 | The quarter of the year. Use QQ if you want zero padding |
QQQ | Q4 | Quarter including "Q" |
QQQQ | 4th quarter | Quarter spelled out |
Below table shows the available format options of Month in DateFormatter
:
Characters | Example | Description |
---|---|---|
M | 3 | The numeric month of the year. A single M will use '3' for March |
MM | 03 | The numeric month of the year. A double M will use '03' for March |
MMM | Mar | The shorthand name of the month |
MMMM | March | Full name of the month |
MMMMM | M | Narrow name of the month |
Below table shows the available format options of Day in DateFormatter
:
Characters | Example | Description |
---|---|---|
d | 1 | The day of the month. A single d will use 1 for March 1st |
dd | 01 | The day of the month. A single d will use 01 for March 1st |
F | 1st Mondy in March | The day of week in the month |
E | Mon | The abbreviation for the day of the week |
EEEE | Monday | The wide name of the day of the week |
EEEEE | M | The narrow day of week |
EEEEEE | Mo | The short day of week |
Below table shows the available format options of Hour in DateFormatter
:
Characters | Example | Description |
---|---|---|
h | 9 | The 12-hour hour |
hh | 09 | The 12-hour hour padding with a zero if there is only 1 digit |
H | 21 | The 24-hour hour |
HH | 21 | The 24-hour hour padding with a zero if there is only 1 digit |
a | AM | AM / PM for 12-hour time formats |
Below table shows the available format options of Minute in DateFormatter
:
Characters | Example | Description |
---|---|---|
m | 15 | The minute, with no padding for zeroes |
mm | 15 | The minute with zero padding |
Below table shows the available format options of Second in DateFormatter
:
Characters | Example | Description |
---|---|---|
s | 7 | The seconds, with no padding for zeroes |
ss | 07 | The seconds with zero padding |
SSS | 700 | The milliseconds |
Below table shows the available format options of Time zone in DateFormatter
:
Characters | Example | Description |
---|---|---|
zzz | IST | The 3 letter name of the time zone. Falls back to GMT-08:00 (hour offset) if the name is not known |
zzzz | India Standard Time | The expanded time zone name, falls back to GMT-08:00 (hour offset) if name is not known |
ZZZZ | IST-05:30 | Time zone with abbreviation and offset |
Z | +0530 | RFC 822 GMT format. Can also match a literal Z for Zulu (UTC) time |
ZZZZZ | +05:30 | ISO 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.