Hallo papieren en boekjes, leo hier. Today we will explore a native date manipulation API called RelativeDateTimeFormatter.
We all know how tricky it can be to work with dates and times, right? We’ve got timezone conversions, internationalization, and daylight saving time to name a few challenges. Thankfully, Swift makes our lives more manageable with a tool called
There’s a lot of pain involved in manipulating dates, but don’t worry Swift got your back with this API.
Last week I was doing a screen in SwiftUI and I had to create some space between the section header of a List and the list content itself. I’m glad that I discover that the Section struct has an initializer that we can customize the header the way we want. Maybe that became an article anywhere in the future.
Lately, I’ve been exploring a variety of topics to help you improve your Swift skills. In one of my recent articles, I delved into how to add programmatic app icon badges in SwiftUI, a handy technique that can enhance user engagement by providing visual cues about pending tasks, messages, or notifications. This can be especially useful for productivity apps or social media platforms where users need to stay informed about app-related activities.
In another fascinating article, I tackled the subject of cyclomatic complexity, an essential software metric that measures the complexity of your code. Understanding cyclomatic complexity and applying strategies to reduce it can significantly improve code readability, maintainability, and overall quality. As you progress in your Swift development journey, being mindful of such architectural aspects will set you apart and enable you to write more efficient and clean code.
With these topics under our belts, we’re now ready to dive into the world of RelativeDateTimeFormatter and explore its features and real-world examples.
So, grab a coffee, and let’s code! But first…
Painting of The Day
The art piece I chose for today is a 1920 painting called “Sunlit Interior with Three Generations” by Anna Ancher.
Anna Ancher (1859-1935) was a renowned Danish artist and a member of the Skagen Painters, an artist colony in northern Denmark. As one of the country’s most exceptional visual artists, Ancher was a trailblazer in observing the interplay of colors in natural light.
Born and raised in Skagen, Ancher was the only Skagen Painter native to the area. Her artistic talent was evident from a young age, and she honed her skills through exposure to numerous artists who visited Skagen to paint. After studying drawing in Copenhagen and Paris, she married fellow painter Michael Ancher and continued to paint despite societal expectations for married women.
I chose this painting because it relates to the passage of time. In the painting, we can compare three different time generations, and that is exactly what we will do in Swift today!
What is RelativeDateTimeFormatter?
RelativeDateTimeFormatter is a class introduced in Swift 5.1 that helps format dates and times relative to the current time or specified reference date. It’s convenient when you want to convey the difference between two dates in a human-readable format.
For example, if you have an app that shows the time elapsed since an event occurred, like the last time your app was updated, RelativeDateTimeFormatter will help you display this information in an easily understood manner, such as “2 days ago” or “in 3 weeks.”
A good disclaimer you can observe in the docs is that “Embedding them in other strings may not be grammatically correct”. Don’t fully trust this framework to add information to your phrases, create your own unit test suit to make sure the structure is ok.
Setting Up Your Formatter
RelativeDateTimeFormatter is a piece of cake. To get started, create an instance and set your desired style.
Here’s a simple example:
import Foundation let formatter = RelativeDateTimeFormatter() formatter.unitsStyle =.full
In this snippet, we’ve initialized a formatter and set its
unitsStyle property allows you to customize the format of the output. It supports four different styles:
Let’s take a closer look at each of these styles.
This style spells out the unit of time in full. For example, “in 3 hours” or “5 days ago.”
This style uses an abbreviated version of the time unit. For example, “in 3 hr” or “5 d ago.”
The abbreviated style is even more concise, using only the first character of the time unit. For example, “in 3h” or “5d ago.”
Spell Out Style
The spell-out style spells out the time unit and the numeric value. For example, “in three hours” or “five days ago.”
What are the differences between Unit Style and Date Time Style?
The Unit Style is the style to use when formatting the quantity or the name of the unit, such as “1 day ago” or “one day ago”.
On the other hand, the Date Time Style is a style to use when describing a relative date, for example, “yesterday” or “1 day ago”. By default the date time style is numeric, but we also have the named style.
Let’s see the difference in the code below:
let currentDate = Date() let someDateInThePast = Date(timeIntervalSinceNow: (61 * 60 * 24 * 2) * -1) // 2 days ago let formatterNumeric = RelativeDateTimeFormatter() //formatter.locale = Locale(identifier: "nl") formatterNumeric.unitsStyle = .full formatterNumeric.dateTimeStyle = .numeric // using the default numeric style let now = formatterNumeric.localizedString(for: currentDate, relativeTo: currentDate) let relativeDateString = formatterNumeric.localizedString(for: someDateInThePast, relativeTo: currentDate) print(relativeDateString) // result is >>> "2 days ago" print(now) // result is >>> "in 0 sec." // -------- using named date time style below --------- let currentDate = Date() let someDateInThePast = Date(timeIntervalSinceNow: (61 * 60 * 24 * 2) * -1) // 2 days ago let formatterNamed = RelativeDateTimeFormatter() //formatter.locale = Locale(identifier: "nl") formatterNamed.unitsStyle = .full formatterNamed.dateTimeStyle = .named // changing to the named style let now = formatterNamed.localizedString(for: currentDate, relativeTo: currentDate) let relativeDateString = formatterNamed.localizedString(for: someDateInThePast, relativeTo: currentDate) print(relativeDateString) //this result is the same >>> "2 days ago" print(now) // this is totally different >>>> now.
As you can check above when using the default date time style, the numeric one, the result will always be numeric even with the spell-out option it will just spell the numbers. However, if you set the date time style to named you get different context results.
Formatting Context of RelativeDateTimeFormatter
You can also set the context of how you are going to use the formatted String. This is handy because you don’t need to worry about some things, for exmaple capitalization.
See the example below:
let currentDate = Date() let someDateInThePast = Date(timeIntervalSinceNow: (61 * 60 * 24 * 3) * -1) // 2 days ago let formatter = RelativeDateTimeFormatter() formatter.unitsStyle = .full formatter.formattingContext = .beginningOfSentence let now = formatter.localizedString(for: currentDate, relativeTo: currentDate) let relativeDateString = formatter.localizedString(for: someDateInThePast, relativeTo: currentDate) formatter.formattingContext = .middleOfSentence print(relativeDateString) // "3 days ago" print(now) // "In 0 seconds"
Really cool, isn’t it?
Other ones you can use are described in the table below:
|Formatting Context Case||Meaning|
|Represents dates and times in standalone contexts, such as a date or time picker.|
|context is automatically determined to be one of the following: Formatter.Context.standalone, Formatter.Context.beginningOfSentence, or Formatter.Context.middleOfSentence.|
|Represents dates and times in list items, such as a list or menus.|
|Represents dates and times at the beginning of a sentence.|
|Represents dates and times in the middle of a sentence.|
There’s one more that is called .unknown but I couldn’t find any relevant info about it, so I left it out of the above table. I guess that is really unknown, right?
With all these settings in hand, we are pretty good to go to the examples now.
RelativeDateTimeFormatter in Action
Now that we’ve explored the available styles, let’s put
RelativeDateTimeFormatter to work. Check out this example that shows how to compare two dates:
let currentDate = Date() let someDateInThePast = Date(timeIntervalSinceNow: (-61 * 60 * 24 * 3) // 3 days ago let formatter = RelativeDateTimeFormatter() formatter.unitsStyle = .full let now = formatter.localizedString(for: currentDate, relativeTo: currentDate) let relativeDateString = formatter.localizedString(for: someDateInThePast, relativeTo: currentDate) formatter.formattingContext = .middleOfSentence print(relativeDateString) // "3 days ago" print(now) // "in 0 seconds"
In this example, we’ve created a current date and another date three days prior. Then, we used our
RelativeDateTimeFormatter to generate a human-readable string that represents the difference between the two dates. The result is “3 days ago“
Comparison Between Styles using RelativeDateTimeFormatter
To help you get a better understanding of how
RelativeDateTimeFormatter works with different styles, here’s a table showing examples for each style:
|23 hours ago||“23 hours ago“|
|23 hours ago||“23 hr. ago“|
|23 hours ago||“23 hr. ago“|
|23 hours ago||“twenty-three hours ago“|
As you can see, each style provides a different level of detail in the output, allowing you to choose the one that best suits your needs. All the results were tested in Xcode 14.3 and may change.
Customizing the Formatter
RelativeDateTimeFormatter also allows you to further customize the output by adjusting other properties such as:
If you’re building an app that supports multiple languages, you’ll need to ensure your date and time information is formatted correctly for each language. To do this, simply set the formatter’s
locale property to the desired
let currentDate = Date() let someDateInThePast = Date(timeIntervalSinceNow: (-61 * 60 * 24 * 3)) // 3 days ago let formatter = RelativeDateTimeFormatter() formatter.unitsStyle = .full formatter.locale = Locale(identifier: "es") // <<< ADD THIS let now = formatter.localizedString(for: currentDate, relativeTo: currentDate) let relativeDateString = formatter.localizedString(for: someDateInThePast, relativeTo: currentDate) print(relativeDateString) // "hace 3 días" print(now) // "dentro de 0 segundos"
In this example, we’ve set the formatter’s locale to Spanish and the result is formatted as we expected.
You can also customize the calendar used by the formatter. By default, it uses the current system calendar, but you can specify a different one if needed:
let formatter = RelativeDateTimeFormatter() formatter.calendar = Calendar(identifier: .gregorian)
In this example, we’ve set the formatter’s calendar to the Gregorian calendar, but you can also set it to Chinese/Hebrew/etc.
Handling Edge Date Cases
There are a few edge cases you might encounter while working with
RelativeDateTimeFormatter. Making wrong assumptions about time is a very known problem among software developers. Some false statements examples are:
- There are always 24 hours in a day.
- The server clock and the client clock will always be set to the same time.
- Time stamps will always have the same level of precision.
- The offsets between the two time zones will remain constant.
And the list goes on, you get my point.
The good thing is that the RelativeDateTimeFormatter can handle some cases gracefully.
Let’s take a look at a couple of them:
RelativeDateTimeFormatter works great with future dates too! Just pass a date in the future, and the formatter will return a string like “in 2 days” or “in 6 hours.”
Zero Date Difference – Handling “Now” Cases
If the difference between the two dates you’re comparing is zero, the formatter will return “now.” or “in 0/zero seconds”, depending on your configuration. This can be helpful when displaying real-time information in your app.
Summary – Deep Dive into RelativeDateTimeFormatter
Today we studySwift’s
RelativeDateTimeFormatter. We found out that is a handy tool for working with dates and times. It makes it a breeze to compare and display dates relative to each other in a human-readable format.
You can customize a lot for your needs and add great native language support to your date manipulations.
Now that you’ve learned the ins and outs of this powerful tool, go ahead and give it a spin in your next project!
Fellow Apple Developers, that’s all.
I hope you liked reading this article as much as I enjoyed writing it. If you want to support this blog you can Buy Me a Coffee or say hello on Twitter. I’m available on LinkedIn or send me an e-mail through the contact page.
You can likewise sponsor this blog so I can get my blog free of ad networks.
Thanks for the reading and… That’s all folks.
Image credit: Featured Painting