Using DateFormatter to Format Dates and Times from APIs

TJ Carney
9 min readMar 15, 2017

--

I recently learned how to access APIs and parse JSON, unlocking a whole new skill set within Swift programming, and while it is very cool, it can also be extremely frustrating. Beyond figuring out how the data you get back is laid out, one of the first major problems I ran into was trying to grab a date and time from a certain API.

For more context, I was hitting the SeatGeek API in order to construct an app that displays the 2017 New York Yankees schedule. The SeatGeek API has a ton of information to parse through but I was mainly focused on grabbing the opponent, the location of the game, the date, and the time. Once I found where the pieces of data were located, parsing through them wasn’t too bad.

However, the biggest headache I encountered was that the date and time were bundled together into one key called “datetime_local.” I wanted to have the date and time separately so that I could eventually display them on two labels within my app. The format looked something like this:

“2017–04–02T13:10:00”

Looking at this, it would seem that the first part is the date (April 2nd, 2017) and the second part is the time (13:00:00, or 1:10pm in standard time). Okay, no problem I’ll just use the “components” method available on instances of strings. It looks like a “T” separates them so let’s use that as what we separate by:

If you’re unfamiliar, the “components” function just takes the separated strings and throws them into an array. Therefore, the date is the first item in the array (index 0) and the time is the second item (index 1) so we can access them as follows:

So now I have my separate date and time values but I’m looking to eventually display these to a user and I don’t know about you, but if I saw these values on an app, I would close it and delete it immediately. Instead, I want to convert these dates and times to a more readable, user-friendly format. And THAT is where the confusion comes in.

Let’s rewind for a second to that weird “datetime_local” format above. What I didn’t know was that this format is actually very common and referred to as ISO 8601. It is essentially an agreed upon format for dates and times so that information can be sent internationally with little confusion (ironic that this is what caused me the most confusion). It is formatted starting with the biggest chunk of time and working down (so year-month-day) with hyphens separating each one. After that, the “T” basically serves as a separator, saying “okay, we are transitioning to a time now” and the time is listed in a similar way as date, from large to small (hour:minute:second) with a colon separating each one. It’s also important to note that ISO 8601 defaults to military time (a 24 hour clock), which can be a bit frustrating. Those are the basics of ISO 8601 but if you’re interested in reading about the nitty gritty, you can find more information at https://en.wikipedia.org/wiki/ISO_8601.

Okay back to the current problem at hand — trying to convert the dates and times to readable, user-friendly values. There are actually a couple of ways to go about this, depending on what you want to do, but no matter what, you’ll have to use DateFormatter in Swift. So what is DateFormatter?

DateFormatter is actually a class defined by Apple that helps to convert dates and actually times as well. DateFormatters have a variety of methods on them that you can call to get/set values and that is where they become the most useful.

The first way we can approach the problem is by creating a function to to extract the date from the ISO 8601 format. In our case, let’s image that we haven’t separated the date and time and it is still coming down as the weird “datetime_local” key formatted in ISO 8601. We can actually just strip off the date from that if we want:

Okay, what is going on here? Well first we set up our own dateFormatter constant, which is an instance of DateFormatter. All instances of DateFormatter have a property on them called Calendar, which we can use to set what kind of calendar our data is going in as. And look at that! Apple actually accounts for one of the options being .iso8601! We then set the date format for this initial dateFormatter, which in our case is what ISO 8601 looks like — “yyyy-MM-dd’T’hh:mm:ss”.

After we have this initial dateFormatter set up, we actually need to create a new one. Think of it as saying “here is what the string I’m passing in is formatted as (our initial dateFormatter) and here is what I want it converted to (our newFormat).” So we declare another constant called newFormat, which is another instance of DateFormatter and set it’s dateFormat property to what we want our original string to become — “MMM dd.” This format will output the first three letters of the month followed by the day (so 2017–04–02 becomes Apr 02). See the end of this blog post for more information on date and time formats.

Now comes the tricky part. We need to send our string in to be converted and it is actually done twice (one with each formatter). So we first unwrap and send our string to be converted into a Date, which is actually another type in Swift. Now we have a date that is still in our original format (yyyy-MM-dd’T’hh:mm:ss) but we want to not only get it back in a different format, but also have it be a String, not a Date. So we send it through our second formatter, doing the reverse in that we make the Date into a String and formatting it to what we want (in this case MMM dd). If you run that code, you’ll see that our new String is now formatted in the way we want!

That’s one way to handle the problem but I felt as though that was a bit much in that I’d have to create another function to do it for the time and I already had my “datetime_local” data separated into two different Strings. So I took bits and pieces of the function above and rewrote some of it to give me exactly what I wanted:

Whoa. That looks like a lot, I know. But for my brain, it actually makes a bit more sense in that I’m explicitly telling both the date and the time what to do. Let’s break it down a little but more:

I start by declaring two empty Strings called convertedDate and convertedTime. Later on, these will become the actual date and time I want to use. I then set up four DateFormatters, two for the date and two for the time. As you can see, the first formatter for both the date and the time is the format that my string will go in as and since I’m going to separate it later on, I know that the format for the date will be “yyyy-MM-dd” and the format for the time will be “HH:mm:ss.” The second formatters for both the date and the time set what I want my converted values to be formatted as. In the case of the date, I’d like it to be “MMM d” (giving me the first three letter of the month and the day — again, see below for more info on the d vs dd formats) and for the time I’d like it to be “h:mm a” (just the hour and the minute since I’m not concerned with second — see below for what the “a” means but essentially, it puts in AM or PM).

With all of my converters set up, I can now pull down the data from the API, separate, and convert:

The guard let statement unwraps the “datetime_local” value for me and then I run my .components method to separate the strings by the “T” in the ISO 8601 format. Remember, this puts each String into an array so I set two constants (splitDate and splitTime) to equal to each one of my split values. I then run each of these split strings through my converters, doing exactly what we did in the function above — convert the String to a Date in the initial format and then turn that Date back into a String in our new format, setting our new values equal to the empty Strings we created above (convertedDate and convertedTime)

Now if I were to run my code and print out those values, I would see that for the original datetime_local string “2017–04–02T13:10:00” I get back “Apr 2” for the date and “1:10 PM” for the time. That is much easier for a user to read:

It can be a lot to sort through but once you understand the concept of using DateFormatters and essentially having to send things through twice, it becomes a lot easier to converts dates!

Finally, as promised, I’m going to briefly cover different date and time formats.

There are a lot of nuances when it comes to date and time formats, much of which center around if/how many zeros you want. For example, a date format with just one “M” will give you a month with at least one digit so “May” would be converted to “5” and October would be converted to “10.” By contrast, a date format with “MM” would convert “May” to “05” and October to “10.” See the difference? Basically any time you don’t want any of your output to be padded with zeros, use the single letter.

Another interesting nuance with date format is the ability to get the actual word of a month or day of the week. In fact, you can get many variations of a word based on the format you use. For example, if you are trying to get the day of the week, “EEE” will get you a three letter abbreviation (so “Monday” becomes “Mon), “EEEE” will get you the full name, and “EEEEE” will get you the first letter of the day.

Finally, the format of time can get you different results. If you remember in our example above, we used the format “h: mm” to get our standard 12-hour clock. This is because a lowercase “h” formats the time in a standard 12-hour clock whereas a capital “H” formats it in a 24-hour clock. Similar to the date, the number of letters you use dictates if the number will be padded with zeros or not. For more information on date and time formats, I recommend visiting http://www.codingexplorer.com/swiftly-getting-human-readable-date-nsdateformatter/.

Date and time formatting definitely takes practice but if you are going to parse JSON looking for a date and time, it is absolutely a necessary skill. I recommend playing around with different time formats and pulling data from different APIs to get some practice!

--

--