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 RelativeDateTimeFormatter.

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

Using 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 to .full. The unitsStyle property allows you to customize the format of the output. It supports four different styles: .full, .short, .abbreviated, and .spellOut.

Let’s take a closer look at each of these styles.

 

Full Style

This style spells out the unit of time in full. For example, “in 3 hours” or “5 days ago.”

 

Short Style

This style uses an abbreviated version of the time unit. For example, “in 3 hr” or “5 d ago.”

 

Abbreviated Style

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 CaseMeaning
.standaloneRepresents dates and times in standalone contexts, such as a date or time picker.
.dynamiccontext is automatically determined to be one of the following: Formatter.Context.standalone, Formatter.Context.beginningOfSentence, or Formatter.Context.middleOfSentence.
.listItemRepresents dates and times in list items, such as a list or menus.
.beginningOfSentenceRepresents dates and times at the beginning of a sentence.
.middleOfSentenceRepresents 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:

StyleExampleOutput
.full23 hours ago23 hours ago
.short23 hours ago23 hr. ago
.abbreviated23 hours ago23 hr. ago
.spellOut23 hours agotwenty-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:

 

Locale

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 Locale:

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.

 

Calendar

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:

 

Future Dates

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