Formatting Relative Dates for iOS 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 RelativeDateTimeFormatterclass!

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); 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, for example, 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 RelativeDateTimeFormatterdocumentation, 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 for all kinds of things other than just numbers and dates. Check out the MeasurementFormatterListFormatter, and other classes for generating localized, user-friendly strings for all of your data.

Mark Thormann is a senior mobile software engineer for DHI Group. This article originally appeared in eMpTy Theory.

(If you’re just joining us, we have a variety of Swift micro-tutorials: check out how to work with functionsloopsstringssetsarrays, and the Swift Package Manager.)