How to use DateFormatter in Swift
Learn how to use this expensive DateFormatter.
Table of Contents
This is the second part in a series on DateFormatter performance. In the first part, we learn that DateFormatter is expensive. In this article, we will learn how to use it properly.
Before we jump right into the implementation, I want to point out a myth about DateFormatter that is going around in Stackoverflow that DateFormatter is not a thread-safe. It might not be a thread-safe in the past, but that is no longer the case now.
Thread Safety
The following statement is extracted from Apple Documentation
On iOS 7 and later NSDateFormatter is thread safe.
In macOS 10.9 and later NSDateFormatter is thread safe so long as you are using the modern behavior in a 64-bit app.
On earlier versions of the operating system, or when using the legacy formatter behavior or running in 32-bit in macOS, NSDateFormatter is not thread safe, and you therefore must not mutate a date formatter simultaneously from multiple threads.
So, this article will write based on this fact and not considering thread-safety. If I misinterpret the above statement about thread safety. Please let me know here.
You can easily support sarunw.com by checking out this sponsor.
Debug 10x faster with Proxyman: Your ultimate tool to capture HTTPs requests/ responses, natively built for your iPhone and macOS. Special deal for Black Friday: Get 30% off for all Proxyman licenses with code “BLACKFRIDAY2024”.
Caching
Everything in programming is all about a trade-off. In this case, we will trade memory with time, and that means caching.
For me, I would go with the most straightforward approach, DateFormatter extension.
extension DateFormatter {
static let mediumDateFormatter: DateFormatter = {
let df = DateFormatter()
df.dateStyle = .medium
df.timeStyle = .none
return df
}()
static let mediumTimeFormatter: DateFormatter = {
let df = DateFormatter()
df.dateStyle = .none
df.timeStyle = .medium
return df
}()
static let mediumDateTimeFormatter: DateFormatter = {
let df = DateFormatter()
df.dateStyle = .medium
df.timeStyle = .medium
return df
}()
}
Since both creating and updating DateFormatter are expensive, we create a static DateFormatter for every possible style in our app here.
There are hidden bonus benefits from this.
- Code consistency and maintainability. This extension will be a centralized place for all of the DateFormatter. So, you can point your teammate to this file when they want to introduce a new style to see whether it already exists or not.
- One point of failure. Date manipulation is not an easy task, and you might find some bugs. With these shared styles, you only need to fix it in one spot.
- Better design decision. If this class keeps growing in styles, it might be a good time to talk with your designers for a chance to clean up your design system.
Using it
We create a static variable for the style to ensure it has one instance and no updating afterward. You can use it wherever you see fit.
let dateString = DateFormatter.mediumDateTimeFormatter.string(from: Date())
Is that a SINGLETON?
Yes, but calm down. In this case, we treat our shared DateFormatter as a constant and don't mean anyone to mutate it. Even though declare DateFormatter as such won't prevent people from mutating it.
You can update the DateFormatter like this if you want.
DateFormatter.mediumDateTimeFormatter.dateFormat = "dd"
If you want to make it immutable, you might need to wrap it in a new class with no exposure to underlying implementation detail.
class AppDateFormatter {
static let shared = AppDateFormatter()
private init() {}
private let mediumDateFormatter: DateFormatter = {
let df = DateFormatter()
df.dateStyle = .medium
df.timeStyle = .none
return df
}()
private let mediumTimeFormatter: DateFormatter = {
let df = DateFormatter()
df.dateStyle = .none
df.timeStyle = .medium
return df
}()
private let mediumDateTimeFormatter: DateFormatter = {
let df = DateFormatter()
df.dateStyle = .medium
df.timeStyle = .medium
return df
}()
func mediumDateString(from date: Date) -> String {
return mediumDateFormatter.string(from: date)
}
func mediumTimeString(from date: Date) -> String {
return mediumTimeFormatter.string(from: date)
}
func mediumDateTimeString(from date: Date) -> String {
return mediumDateTimeFormatter.string(from: date)
}
}
In the above example, we create a new singleton class that exposes only functions to retrieve a date string from different styles without exposing any DateFormatter. This is one of a way to prevent people from mutating our DateFormatter.
Localization
In iOS, most changes in languages and locale preference would cause an app restart. So, our DateFormatter will get re-instantiate with an updated locale, so most of the time, you won't notice an outdated format after languages and locales change. But there is some configuration that doesn't require an app restart, such as a calendar change. In that case, our DateFormatter might not sync with the user preference. To make sure your DateFormatter always up to date with the user's current locale, you need to set locale
to autoupdatingCurrent
.
static let mediumDateFormatter: DateFormatter = {
let df = DateFormatter()
df.dateStyle = .medium
df.timeStyle = .none
df.locale = Locale.autoupdatingCurrent
return df
}()
This should be enough to keep DateFormatter getting the latest change from what I test, but Apple Documentation state otherwise. So, if you found any inconsistency, please establish an observer for currentLocaleDidChangeNotification.
Although the locale obtained here automatically follows the latest region settings, it provides no indication when the settings change. To receive notification of locale changes, add your object as an observer of currentLocaleDidChangeNotification.
Conclusion
The example in this article doesn't mean to be your silver bullet, but a guideline to adapt to your own need. If you have any question, please let me know on Twitter at @sarunw
You can easily support sarunw.com by checking out this sponsor.
Debug 10x faster with Proxyman: Your ultimate tool to capture HTTPs requests/ responses, natively built for your iPhone and macOS. Special deal for Black Friday: Get 30% off for all Proxyman licenses with code “BLACKFRIDAY2024”.
Related Resources
Read more article about DateFormatter, Optimization, Swift, or see all available topic
Enjoy the read?
If you enjoy this article, you can subscribe to the weekly newsletter.
Every Friday, you'll get a quick recap of all articles and tips posted on this site. No strings attached. Unsubscribe anytime.
Feel free to follow me on Twitter and ask your questions related to this post. Thanks for reading and see you next time.
If you enjoy my writing, please check out my Patreon https://www.patreon.com/sarunw and become my supporter. Sharing the article is also greatly appreciated.
Become a patron Buy me a coffee Tweet ShareHow to specify fractional digits for formatted number string in Swift
Learn how to format a Float and Double string.