SWIFT Standards works with the user community to specify and publish Market Practice - rules and best-practice advice on how standards should be deployed to meet particular business needs or to comply with regulation. The SWIFT Standards group maintains several important message standards. Working with the SWIFT community, SWIFT Standards operates the annual maintenance process for MT, which ensures that the standard evolves to meet changing market needs. SWIFT Standards, under contract to ISO, also maintains two open messaging standards: ISO 15022, which is used for securities settlement and asset servicing, and ISO 20022, which.
- Date Time Formatting in Swift is based off of the DateFormatter class which can be used to manipulate dates and times. An Instance of DateFormatter creates a string representation of NSDate objects, and can also convert textual representations of dates and times into NSDate objects.
- Overview SwiftDate exposes several convenient functions to print (and parse, of course) dates and time intervals as strings: as custom string defined by tr35-31format; for example 'yyyy-MM-dd'for '2015-01-05' as ISO8601strings with all available sub specs.
- The above Swift code prints a date style that’s common in The Netherlands, with dashes - and day-month-year. First, we’re setting the locale to nlNL, and then we call setLocalizedDateFormatFromTemplate (:) with a custom date/time format.
How do you work with date and time in Swift? In this tutorial, we’ll discuss how you can convert date/time to strings, and vice versa, how date/time formatting works, how to calculate time durations, and much more.
Here’s what we’ll get into:
- How to work with Date, DateFormatter and DateComponents
- The nitty-gritty of timezones, locales, and date/time formatting
- How to add “+2 months” to a given
Date
object (and more…) - Calculating relative time strings, such as “2 months ago”
- Constructing
Date
objects from date components
Ready? Let’s go.
Get Current Date and Time With Swift
Let’s start with a simple example. How do you get the current date and time in Swift? Like this:
Swift Date Formatter Formats
Simple, right? The default time for a newly initialized Date
object, like the one above, is the current date and time. When you create a new Date
object with the Date()
initializer, it has the current date and time.
Let’s discuss this Date
type some more. Here’s what you need to know:
- The
Date
type is part of the Foundation framework, so it’s a fundamental type to work with dates on iOS, macOS, tvOS, watchOS and Catalyst. - A
Date
object represents a single point in time, regardless of locale or calendar setting. As you’ll soon see, this fact is crucial to work with dates effectively. - A
Date
object has millisecond precision, i.e. it can represent points in time up to 1/1000th of a second. The default time unit for date/time APIs is the second (and fractions), which is OK for common use cases.
You can print out a date in the Console, like this:
Keep in mind that a Date
object can be represented as a string, so it can be printed out, but that’s not the recommended approach to convert date to string (or vice versa). When you run the above code in Xcode, you’ll get the current date and time in ISO 8601 format (in your system’s timezone) as debug output.
If you’re familiar with working with date and time in app development, you may have heard of the term “Unix time” (or Unix timestamp). A Unix timestamp is (commonly) the number of seconds that have elapsed since January 1st, 1970 at 00:00:00 (midnight, UTC). Timestamps are commonly represented as 10-digit numbers, so it’s easy to store them in a database or simple Int
or Double
value.
But… there’s going to be a lot of “buts” in the rest of this tutorial. Because for every rule we establish, like “Unix timestamps fit in 10 digits”, there’s an exception you’ll have to take in account. Things like timezones, leap seconds, calendars, formatting and locales. It’ll be fun!
Important: Don’t have anything to take away from this tutorial!? At least know the following guidelines for working with date and time:
- Don’t use calculations like
60 * 60 * 24
for time intervals, because that’ll go awry for leap seconds - Store dates as native
Date
objects or as timestamps, but know that timestamps aren’t perfect - Store dates in UTC/GMT whenever you can, only use your local timezone if you’re 100% certain that your app never crosses timezones
- When representing dates as strings (i.e., for your app’s users), take in account an iPhone’s locale and calendar settings
Date to String: Formatting Dates & Locales
OK, let’s move on. You’ve got a Date
and you want to display it to your app’s users. How do you convert a date to a string?
Here’s how:
- Create a
DateFormatter
object (the right way) - Convert the date to string with the formatter
Let’s take a look at an example:
What’s going on? First, we got the current date and time. Then, we created a DateFormatter()
object and provided it with values for 2 properties: dateStyle
and timeStyle
. Then, we called the function string(from:)
on the date formatter, provided it the Date
object, and printed out the resulting string datetime
. Awesome!
You can choose from various styles with the dateStyle
and timeStyle
properties. Both take values from the DateFormatter.Style
enum. You can combine dateStyle
and timeStyle
to include date or time or both.
Here’s an example of what both styles can do:
Date | Time | |
---|---|---|
.none | (nothing!) | (nothing!) |
.short | 3/11/20 | 3:32 PM |
.medium | Mar 11, 2020 | 3:32:07 PM |
.long | March 11, 2020 | 3:32:07 PM GMT+1 |
.full | Wednesday, March 11, 2020 | 3:32:07 PM Central European Standard Time |
The exact date format depend on a system’s locale, which is what we’ll discuss next. 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_US
locale, which means it uses the US 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 iPhone Simulator, or on an iPhone, and check out the result.
It’s good to know that the DateFormatter
object will use the iPhone’s system locale by default. If you’re creating a date formatter object, and you use dateStyle
and timeStyle
, and then string(from:)
, you’re almost guaranteed to create a textual representation of date/time in the user’s locale.
What if you want to use a custom date/time format? Here’s how you can do that:
The above Swift code prints a date style that’s common in The Netherlands, with dashes -
and day-month-year. First, we’re setting the locale
to nl_NL, and then we call setLocalizedDateFormatFromTemplate(_:)
with a custom date/time format.
What’s interesting, is that this function will take into account the locale of the user, while allowing you to set a custom date/time format. Keep in mind that you’ll need to set the locale
prior to calling setLocalizedDateFormatFromTemplate(_:)
.
Also, be mindful of making mistakes with custom formatting symbols – if you accidentally use YYYY
instead of yyyy
, you’re bound to get interesting results…
You can also use the dateFormat
property of DateFormatter
to set a custom date/time format, but this is only advisable for parsing date time formats (see next section). The dateFormat
doesn’t take a user’s locale into account, so it’s not suitable for printing user-readable date/time formats.
Note: You can find more information about the date/time formatting symbols, like MM
or yyyy
, on this page. Currently, iOS uses the “Unicode Technical Standard #35, tr35-31”, which can be found here.
A good rule of thumb is to use as little “hardcoded” values as possible, when working with date and time. This includes fixed time intervals, like 86400, and fixed date formats, like d-m-Y
. Instead, use constants and properties like dateStyle = .medium
.
String to Date: Parsing Dates & Timezones
In the previous section, we’ve looked at how you can convert a date to string. How do you convert a string back to a Date
object? That’s what we’ll discuss in this section.
Why would you want to convert strings to dates? A few scenarios:
- You’ve received a well-formatted ISO 8601 string from a JSON-based webservice, and you need to convert it to a
Date
in Swift - You’ve stored a Unix timestamp in a database, that you need to parse to
Date
, and subsequently display to your app’s users - Data you’re working with has some obscure date formatting, that you want to parse, and write back in a more sensible format
The good news is that parsing date strings, and converting them to Date
objects, is as simple as working with a DateFormatter
object. The bad news is that parsing date strings has a ton of caveats and exceptions.
Let’s look at a quick example:
In the above code, we’ve created a date/time format: day-month-year and hour-minute-second-timezone. We’re using double digit padding, i.e. 1 AM is written as 01:00. With the function date(from:)
we’re using the formatter to convert a string to the datetime
object, of type Date
, which is printed out.
If you look closely, you can already see a discrepancy between the date string and the printed Date
object. We’ve specified 13:37, but when datetime
is printed out, we’re getting a 12:37 back. Why is that?
The Mac that I ran the above code on is set to the Central European Time (CET), which is one hour 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.
Differently said, we input the time in the CET timezone, but it’s printed back in the UTC (-1 hour) timezone. The Date
object and the date/time strings are referencing the same point in time, though. It’s the formatting that’s different! Confusing…
You can verify a Mac or iPhone’s timezone with this code:
We can assert that the date/time string 13-03-202013:37
was parsed with the CET timezone. After conversion, when printed, it was output in the UTC timezone. Hence the one hour difference. Good to know!
Can we print out the date/time in the correct timezone? Yes! First off all, it’s smart to provide timezone information whenever you’re working with date/time strings. That way you can avoid any confusion around the string’s timezone and your system’s timezone. Like this:
In the above date/time string, we’ve added a standardized offset of +1 hour. Keep in mind that this won’t change the timezone of the Date
object, because Date
objects are timezone-agnostic and/or use UTC. A fundamental principle of working with date and time is displaying them correctly, and always storing them as UTC.
Here’s how we can print the date/time in the right timezone:
What if we want to show the same datetime
object in a different timezone? Here’s how:
In the above Swift code, we’re printing out that same datetime
object (of type Date
), using the nl_NL locale, using the US Eastern Standard Time (EST) timezone. What’s important here is that the Date
object hasn’t changed, it’s still that 13:37:00 time, which is 8:37 AM in the EST timezone…
If you’re working with date and time, and your data is consistently a few hours off, check if you’ve not made any mistakes with timezones. And yes, not all timezone offsets are in 1-hour increments!
All this nonsense with timezones and locales will make you wonder: can’t we just adopt a default that always works? YES! That’s the ISO 8601 date/time format, represented with the ISO8601DateFormatter
class in Swift (and use UTC/GMT).
Here’s how you can print the current date/time:
And here’s how you can convert a date/time string to a Date
object:
Store those ISO 8601 in a database, parse to/from using the iPhone’s locale and timezone settings, and you’re good! Sensible user-representable date/time strings for the masses!
Creating Dates With DateComponents
So far we’ve looked at converting dates to strings, and strings to dates, and formatting, locales, timezones, and a bunch of gotchas.
But what if you want to work with the individual parts of a date or time, like days, day of the week, or minutes. Or what if you want to construct a Date
object from individual day, month and year values? That’s where the DateComponents
struct comes in.
The DateComponents
type encapsulates date/time information, in terms of units (or “components”), such as minute, hour, day, month and year. It’s also calendar- and locale-aware, which means it’s the ideal structure for creating and manipulating dates.
Creating a date with DateComponents
, from individual units, is simple:
In the above Swift code, we’re creating a date (January 1st, 2020) from individual units. We’re using the initializer DateComponents(···)
, which is incredibly flexible, to construct a date. We’re also explicitly providing the calendar and timezone.
On the second line, we’re printing out the components.date
value. This is a Date
object that’s constructed from the date/time components we put in. Parameters for the DateComponents(···)
initializer are ordered from biggest (era) to smallest (nanoseconds), followed by ordinal units, like weekday
.
Because not every date component combination can be used (i.e., February 30th), we can use the isValidDate
property to check if the date we put in actually exists. This is also affected by different calendar types, such as Gregorian and Chinese calendars.
It’s recommended to always provide a Calendar
object, such as Calendar.current
, and in most cases, recommended to provide explicit timezone information, such as TimeZone.current
or TimeZone(abbreviation: ···)
. This way you avoid confusion around timezone offsets, and valid dates in various calendars.
You can also use the DateComponents
structs to get individual date components you’re interested in. Here’s how:
In the above code, we’re using the dateComponents(_:from:)
function to extract date components from now
. The result is a tuple, assigned to components
, which we can deconstruct further:
Each of the items you can add to the dateComponents(_:from:)
function has a corresponding enumeration value, in the Calendar.Components
type, which are identical to the parameters for the DateComponents(···)
initializer.
Calculating Date/Time Durations 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.
Here, check this out:
What’s going on here? In short, we’re adding a duration of 2 months to the date February 1st, 2020. The duration
date components object is used to add a date period to now
. As a result, we’re moving 2 months into the future: from February 1st to April 1st.
Remember when we discussed that you shouldn’t calculate date periods by a fixed number of seconds? Don’t calculate “2 months later” by adding 60 * 60 * 24 * 30 * 2
seconds to a February 1st.
Why not? Because 2020 is a leap year, and not all months are 30 days! February has 29 days, so adding any number of days to a date period that crosses February 29th, results in the wrong date calculation. The same is true for that 86400 seconds in a day rule – don’t use it!
Similarly, we can also use individual date components to add more units to a specific date. Like this:
In the above code, we’re directly adding 2 months to now.date
. This step-based approach is useful if you’re working with date/time units directly.
And last but not least… How do you calculate a relative “2 months ago”-like string from a given time period? This is how:
What’s going on?
- First, we’re calculating a
Date
object based on the January 1st, 2020 date using theDateComponents
struct. We want to know how many months, days, etc. have elapsed since that day. - Then, we’re constructing an array with the date units that we’re looking for. It’s type is
[Calendar.Component]
, an array ofCalendar.Component
values. - Then, we’re calculating the time period between
date
and now, using theDateComponents
(as duration). We’re quickly converting theunits
array to aSet
. (We could iterate that set too, but it has no order, which won’t work for our algorithm.) - Finally, we’re iterating over the individual date components. The key is to check these components one by one, biggest to smallest, and to check if one is bigger than zero. When it is, we can say that
date
is roughly “X months” or “X days” etc. ago. If we find a match, we print out that unit and how many of them have passed. Theforin
loop is halted withbreak
.
The principle we’re using in this algorithm, is figuring out how much time has passed between the given date and now. This duration is broken down into date/time components, like months, days, hours, etc.
We’re iterating these components, ordered biggest to smallest. This means we’ll encounter months before minutes, for example. As a result, when a duration is expressed in both months and days (non-zero), we’ll print out “X months ago”, because months is the bigger significant unit. When all units up to hours are zero, we’ll print out “X hours ago”, because hours is the biggest unit.
Differently said, when a non-zero unit is bigger than another unit, we’ll use that unit to roughly express the relative date/time duration. Which is exactly what “X days ago” needs to do. Awesome!
Further Reading
In this tutorial, we’ve looked at how to work with date/time in Swift.
We’ve discussed fundamental principles, like timezones and locales. You’ve learned how to work with staple date/time components, such as Date
, DateFormatter
and DateComponents
. We’ve discussed what you can do with them, such as formatting date/time strings, parsing them, creating dates, and working with date/time durations.
Swift Date Formatter
Want to learn more? Check out these resources: