9

How to use DateFormatter in Swift

 3 years ago
source link: https://sarunw.com/posts/how-to-use-dateformatter/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

How to use DateFormatter in Swift

Learn how to use this expensive DateFormatter.


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.

The only part that I'm not sure about is when they mention "legacy formatter behavior" is not thread-safe. I don't know what is legacy formatter behavior is.

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.

Practical Sign in with Apple

Learn everything you need to know about Sign in with Apple to be able to integrate it in your existing app or a new one.

Get it now

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.

  1. 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.
  2. 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.
  3. 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())

When I say no updating afterward, I mean by design because the compiler does not enforce that.

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

Practical Sign in with Apple

Learn everything you need to know about Sign in with Apple to be able to integrate it in your existing app or a new one.

Get it now

Related Resources #


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

Tweet

Share

Previous
How to loop in Swift

Learn different ways of using for loop for each scenario.

Related Posts

Browse all content by tag

How expensive is DateFormatter

If you are working on iOS for long enough, there is a chance that you might have known that DateFormatter is expensive, but what is costly about DateFormatter? Let's find out in this article.

DateFormatter Optimization Swift
Sign in with Apple Tutorial, Part 3: Backend – Token verification

Part 3 in a series Sign in with Apple. In this part, we will see how backend can use the token to sign up/sign in users.

Swift

Get new posts weekly

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 — entirely for free.

← Home


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK