Episode #566

Date formatted() - a Hidden Gem

14 minutes
Published on October 19, 2023

This video is only available to subscribers. Get access to this video and 578 others.

With many great features announced each year, it's easy for smaller changes to go without as much fanfare. In this episode we'll take a look at a new way to format dates using the .formatted() API. It's easy to use, expressive, and best of all we don't need to worry about caching formatters anymore!

This episode uses Swift 5.8, Xcode 15.0.

Okay, so I want to talk today about a hidden gem that sort of went under the radar.

In the past few years we've had quite a lot of really excellent announcements at WWDC

with these big, you know, groundbreaking new features and that's what people tend to focus

on.

And sometimes there are really awesome changes that just don't get quite as much fanfare.

And that's what I'd like to take a look at today.

And I'm specifically talking about date formatters.

There's a lot of information about date formatters both here on NSScreencast, on the NSDateFormatter.com

website that I created, and you know, elsewhere on the internet there's lots of standards

behind it.

The basic functionality of it is going to be represented here.

And I'm going to show sort of the wrong way to do it, then, you know, a better way to

do it, and then the new way to do it.

So what I've got here in my preview is a list of all of the locales on the system.

And I'm looping through all those locales and I want to print out the date in various

formats according to that locale.

So what I'm going to need to do is I have a model that I could use and I'll say localized

date and I can pass in the locale like that.

And that's going to return a string.

So let's go up to this model.

Let's make that function func localized date in locale locale.

And this needs to return a string.

So the naive way to do this is to create a date formatter and we will create a new date

formatter.

And then we can set the locale on the date formatter.

And then finally we can set the format or a style.

So I can set a date style of, let's say medium and a formatter dot time style is none.

So just the date, not the time.

And then we can return date formatter dot string from and my model has a date property,

which is set by the top of this form here.

So now when I look at this preview, we'll wait for it to refresh and it is pretty fast.

But if you were to do this on a slower device, you might notice that this could cause scrolling

hitches.

And the reason is that creating a brand new date formatter, setting the locale and the

format options on it, and then getting the string is a relatively expensive operation

comparatively to other things.

And the expensive parts of a date formatter are setting the locale and setting the either

the date format or the date template string or one of these data time styles.

So this is similar to if I were to just set the date format and then I go look at a specific

date formatter that I want.

Maybe I want to just get the year and maybe comment those things out.

Now at this point, I'm just going to get the year according to that locale.

And you can see in different languages, it'll be displayed differently in a different character

set perhaps.

We can look at the month number, we can look at the day number, but you can see that if

I were to do this, this date would be incorrect in those locales because this, while this

current date is not ambiguous because there is no 18th month, sometimes it is.

If it was like the 11th of October, you would want that to show 11 first in some locales

and 10 first in US.

And so there's these date format strings, which are useful to get precisely the format

that you want, but not often the right thing to do when using user specific or user facing

strings.

So what I want to do oftentimes is use these date styles or a date template string.

I have a video on that as well, and that will sort of give the most appropriate for that

locale.

The problem is that because this is somewhat slow and I'm calling this literally hundreds

of times, we may want to cache the date formatters.

And oftentimes if you're just displaying a date formatter, most common you're going to

be using in a single locale, very often you're going to have one or very few number of dates

that you want to format.

And so the idea is that you would want to cache this in some sort of reusable manner

so that you could reuse the date formatter for that locale and date and time style and

have that just sort of reuse.

We create it once, we pay the cost of creating it, and then we use it over and over again.

So that's kind of what we would do.

And to implement something like that, we would have some sort of, you know, maybe date formatters

where we have a, let's say we could use the locales identifier as the key and then a date

formatter here.

And so if we had something like this and say we initialize that to an empty dictionary,

we'll make it private.

Then here in our model, we can check first to see if we have a date formatter contains

dot keys dot contains locale dot identifier.

Then we'll use it.

And here we might say if we don't have one, then we're going to create one right now.

So we would do this and then set it in date formatter.

Date formatters for locale dot identifier equals date formatter.

Now in our particular case, this isn't going to be super useful except for scrolling back

through the list.

And here we could say date formatters.

And we know for a fact that we have one now, so we can force unwrap it here.

So this does work, but it's still not great because we're basically caching hundreds of

date formatters.

We're not getting a ton of use out of this one, but it is sort of a guideline that is

commonly practiced to cache date formatters.

And then critically, all of this stuff are the things that we don't want to change on

that date formatter.

You never want to change the locale of a cached date formatter, for instance.

So this is something that is easy to do wrong.

And there's a new way to do this, and that's what I want to cover now.

So I'm going to comment out this code.

And instead, we're going to use the new formatted API.

So we have a date, and now we have this new formatted option.

And I'm just going to call it like this.

And notice that we get a basic date in the current locale, which is ENUS.

And I can search for ENUS in this list to see it.

But all of them are getting the current locale because that's the current locale I'm running.

I can pass in a locale if we specify a format style.

And then I can say in locale locale.

So notice that the formatted method, let's go to the definition of that, takes a format

style.

And we have a few different ways of doing this.

We can pass one where we have formatted.

This is basically saying that the format is going to be a format style where the format

input is date.

Here we have one where we're specifying a specific format style for dates and a specific

format style for times.

And then we have the basic one we just saw.

So basically what we want to do here is we can pass in date time like this, or we could

do something like formatted.

We'll pass in date colon dot abbreviated and time colon shortened.

So if we look at the format style here, the format style for dates allow you to set the

locale, the time zone, the calendar, and we can create one with a time style and a date

style.

So if we're using the date and time method here with those two arguments, then we don't

have the option of passing in the locale because this format style is being created for us.

So instead, what I'm going to do is we're going to create a new date dot format style.

And when we do that, we can specify all of those things.

So I can say date, time, and locale.

So for the date style, I'm going to say long.

And for the time style, maybe I'm going to say standard.

And for the locale, I'll pass locale.

So this is how you would do it if you're passing in all of those different options.

And you can see that this gives us wildly different results that are more appropriate

or most appropriate for that locale.

So if we look at, for instance, Spanish from Mexico, we have this.

You can see that things are spelled out.

We have lower cases.

We have lower case for AM.

Some places won't use the AM and PM.

Some places will capitalize.

Some places will use, I want to say if it's, some of them when we use the date style of

short or abbreviated, we'll see period separators between the dates.

If we use numeric, for instance.

So the separators here are going to be different.

We have dashes, we have dots, we have slashes.

So this is going to do all the heavy lifting for you and it's going to cache this data

for us.

So we don't actually ever really need to think about caching this.

We just use the format style in the way that we want.

So let's say we wanted something very specific rather than saying, well, I want the date

and the time like this.

Maybe we wanted something a little bit different.

And we can use a builder style on that format style.

So notice that when I did the dot syntax and I said date time, this is a static method

that returns a format style.

And on that, we can specify that I just want the day.

And then I can specify that I want the day of year.

So this is kind of interesting.

It's like figuring out what we might want to display in that locale based on the things

that we have specified in this builder.

We can specify era.

We can specify, say, the weekday.

And then some of these have the option of passing in options for that particular style

or element of the date.

So I could say I want short, in which case I have just two digits.

We could see the narrow one, in which case we just have one character.

And this, of course, I still need to pass in locale.

Otherwise these are all going to be the same.

So we can see that we get different characters, different styles.

But the sort of intent is encoded here, that we need a narrow weekday.

So let's say this would be weekday, narrow.

Let's do...

We will do the day.

And here we can specify a format of, say, two digits.

If this was October 1st, for instance, it would be 01.

We can say that we want the month.

And for the format of the month, we want either two digits if we want the number, or we could

say abbreviated if we want the abbreviated word.

And so this is Wednesday, the 18th of October.

And then finally we can pass in the year.

And again, we can pass in...

I don't know what extended means, but we can look at two digits if we want to.

So we have quite a lot of options, and we have the same thing for time as well.

And I think this is a really great API that I don't see used a lot.

And this is actually usable as of iOS 15, so most of us can actually start using this

today in our apps.

So I hope you enjoyed this episode on this hidden gem, which is the formatted API on

dates.