Formatting Relative Dates the Easy Way

Prior to iOS 13, converting a date from the Date object to a nicely formatted string such as “10 days ago” or “next month” required a lot of custom coding or maybe third-party libraries to manage the conversion. The existing DateFormatter is very versatile as well as necessary for easy date localizations but just can’t make a nicely formatted relative date across all date ranges. The DateFormatter.doesRelativeDateFormatting property (available since iOS 4) will output some simple relative date strings like “today” and “tomorrow” but can’t handle relative seconds, minutes, hours, etc. and complex cases. For example, take the following code:

let formatter = DateFormatter()
formatter.timeStyle = .none
formatter.dateStyle = .long
formatter.doesRelativeDateFormatting = true
print(formatter.string(from: Date().addingTimeInterval(-120)))  //"Today"
print(formatter.string(from: Date()))                           //"Today"
print(formatter.string(from: Date().addingTimeInterval(3601)))  //"Today"

Well, that’s ok, but wouldn’t you rather see “2 minutes ago”, “now”, and “in 1 hour” instead of just “Today”? Enter iOS 13 and the RelativeDateTimeFormatter class!

Using RelativeDateTimeFormatter

Using the new class is no different that using common DateFormatter or any other Formatter-derived class. Simply instantiate the formatter, set the appropriate styles and any other properties, and then get your results.

let formatter = RelativeDateTimeFormatter()
formatter.dateTimeStyle = .named
print(formatter.string(for: Date().addingTimeInterval(-120))!)  //"2 minutes ago"
print(formatter.string(for: Date())!)                           //"now"
print(formatter.string(for: Date().addingTimeInterval(3601))!)  //"in 1 hour"

In the above example, we just switched formatters to RelativeDateTimeFormatter and got much more interesting and useful results!

Note that for some reason the parameter name on the string method switched from from: to for: with the new class. Also note that forward looking relative time strings seem to currently be off by 1 second (as of iOS 13.3.1); so if you want an accurate forward-looking time string (e.g. ‘in 1 minute’ instead of ‘in 59 seconds’), you’ll need to be aware of that and compensate potentially.

What about if you don’t want the word “now” and always want numeric results? Use the dateTimeStyle of .numeric to make that happen

let formatter = RelativeDateTimeFormatter()
formatter.dateTimeStyle = .numeric
print(formatter.string(for: Date().addingTimeInterval(-120))!)  //"2 minutes ago"
print(formatter.string(for: Date())!)                           //"in 0 seconds"
print(formatter.string(for: Date().addingTimeInterval(3601))!)  //"in 1 hour"

With the unitStyle property you can also control whether the words are full-length or abbreviated as well as whether numbers are numeric or spelled out completely.

let formatter = RelativeDateTimeFormatter()
formatter.dateTimeStyle = .numeric
formatter.unitsStyle = .spellOut
print(formatter.string(for: Date().addingTimeInterval(-120))!)  //"two minutes ago"
print(formatter.string(for: Date())!)                           //"in zero seconds"
print(formatter.string(for: Date().addingTimeInterval(3601))!)  //"in one hour"

What About Localization?

One of the great things about the RelativeDateTimeFormatter (and why you should almost always use system formatters for user-facing numbers, dates, etc.) is that we can easily get locale-specific versions of these relative dates and times with just one more line of code using the locale property.

formatter.locale = <Locale that you want to use>

That’s it! So if we want the German language version of the above, we can just use

let formatter = RelativeDateTimeFormatter()
formatter.dateTimeStyle = .numeric
formatter.locale = Locale(identifier: "de_DE")
print(formatter.string(for: Date().addingTimeInterval(-120))!)  //"vor 2 Minuten"
print(formatter.string(for: Date())!)                           //"in 0 Sekunden"
print(formatter.string(for: Date().addingTimeInterval(3601))!)  //"in 1 Stunde"

Where To Go From Here?

There’s quite a bit to explore within this very cool class if you need relative dates in your iOS app. You can certainly check out the RelativeDateTimeFormatter documentation but at present, like a fair amount of the iOS 13 documentation, there are no overviews, descriptive text, etc. You’ll be better off using the code comments from the ‘Jump to Definition…’ context menu option in Xcode 11.

There are many different formatters out there as well for all kinds of things other than just numbers and dates. Check out the MeasurementFormatter, ListFormatter, and other classes for generating localized, user-friendly strings for all of your data.

2 thoughts on “Formatting Relative Dates the Easy Way

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: