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); 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 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 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.
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 functions, loops, strings, sets, arrays, and the Swift Package Manager.)