DateFormatter Date and Time Cheatsheet

A cheatsheet and a manager?

Image for post
Image for post
Photo by Jonathan J. Castellon on Unsplash

There is little that annoys me as much as the way that dates require a dateformatter that just hangs around in the parent class. There must be a better way!

Also the formats: dd DD ddd mean, what exactly? Perhaps there is a way to actually let Swift help us out and provide readable code?

Indeed, and this article is it.

Difficulty: Beginner | Easy | Normal | Challenging

Prerequisites:

Terminology

Date: A specific point in time, independent of any calendar or time zone

DateFormatter: A formatter that converts between dates and their textual representations

DataComponents: A date or time specified in terms of units (such as year, month, day, hour, and minute) to be evaluated in a calendar system and time zone

Motivation

Much like the difference between NSData and Data, the difference between them being…just use the non NS version OK (or look at that link).

Due to the different versions of Date, there can be some confusion in using DateFormatter and, well — let’s get started.

The relevant objects

Date

A struct that represents a particular point in time. Now this does not relate to a time zone, rather it is a particular date.

Now — getting back to the link with NSDateDate automatically bridges to the latter so…we kind of don’t need to worry about this.

Since a Date is simply a moment in time, it doesn’t do anything interesting or fantastic with Strings (sorry). That comes with the…

DateFormatter

A Class that lets us move from dates to textual representations of those dates. That means you can represent your point in time as an easy-to-read String that might be useful to humans using your App.

More on this later! One thing that might be important though is that we are converting a Date which is ignorant of your calendar and timezone into something readable by humans, who are invariably interested in a calendar and timezone.

DateComponents

A Struct that is the various parts of a date (year, hour etc.) for a calendar and time-zone.

Basic code

Today!

We can retrieve the current system time as a Date in one line. However, since we are dealing with Date objects here the representation is rather arbitrary

let now: Date = Date()
print (now)

This gives the following output (if you are writing this code now, before I’ve published it. Which is impressive. In any other case the time (and probably date) will be different).

Apr 30, 2020 at 2:34 PM
2020-04-30 13:34:39 +0000

Which is a problem. My Mac says it’s 14:34 so…wait, what?

The answer is formatting. The time might is 13:34 in GMT but I’m in the UK so my calendar is set at GMT +1.

What good is a time and date if you don’t know where you are?

Well, that sounds like a case for UTC but that isn’t what this article is about so I’ll ignore your impetulant behaviour.

Formatting Today!

The basics

So we have the time now, and we want to display it in nice human-readable form.

Isn’t there a way to print the full date, like we might expect. Yes, yes there is. For this we can use DateFormatter

let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .full
dateFormatter.timeStyle = .full
let dateString = dateFormatter.string(from: now)
print (dateString)

That final print, up there? It writes Thursday, April 30, 2020 at 2:42:19 PM British Summer Time to the console (once again, you might not be reading this now, and might not be in the UK. But you might be, I don’t know).

The formatting

DateFormatter, perhaps surprisingly, allows you to format dates. This means that we can use readableDateFormatter.dateFormat to specify our date format.

If you don’t give the date format a style OR a format? It just won’t display.

let readableDateFormatter = DateFormatter()
readableDateFormatter.dateFormat = "M-dd-yyyy HH:mm"
let readableDateString = readableDateFormatter.string(from: now)
print (readableDateString)

Which on my machine prints 4–30–2020 15:35 to the console. What does the M-dd-yyyy HH:mm indicate?

The examples

Image for post
Image for post

This isn’t enough. What do these mean?

Hasn’t some kind soul produced a fun table?

Image for post
Image for post

Well, that formatting is quite complicated. But formatting is only so good if you can create a date!

dateFormatter Locale

There are a whole host of Locales available on your machine. These are available by listing them using NSLocale.isoCountryCodes or we can obtain the current Locale by using Locale.current.

If you want to use the correct, UK Locale you can or use the backwards American way by doing the following:

let usLocaleFormatter = DateFormatter()
usLocaleFormatter.dateStyle = .medium
usLocaleFormatter.locale = Locale(identifier: "en_US")
print (usLocaleFormatter.string(from: now))
let ukLocaleFormatter = DateFormatter()
ukLocaleFormatter.dateStyle = .medium
ukLocaleFormatter.locale = Locale(identifier: "en_UK")
print (ukLocaleFormatter.string(from: now))

with the second obviously being the right way. Other Locales can vary.

Create a Date from a Date component

One rather nice way of calculating

var components = DateComponents()
components.day = 30
components.month = 04
components.year = 2020
components.hour = 4
components.minute = 21
let newDate = Calendar.current.date(from: components) // OPTIONAL

This gives us a new date which outputs 2020–04–30 03:21:00 +0000 when printed, note the lack of formatting in this answer. Also the newDate here is an optional Date.

The other interesting component is the calendar. By using Calendar.current we obtain the current calendar, but there are alternatives. We could use autoupdatingCurrent which tracks changes to the user’s calendar.

But what about calendars that aren’t the user’s current version? No problem; here is a list of calendars in which you might be interested:

buddist Identifier for the Buddhist calendar.
chinese Identifier for the Chinese calendar.
coptic Identifier for the Coptic calendar.
ethiopicAmeteAlem Identifier for the Ethiopic (Amete Alem) calendar.
ethiopicAmeteMihret Identifier for the Ethiopic (Amete Mihret) calendar.
gregorian Identifier for the Gregorian calendar.
hebrew Identifier for the Hebrew calendar.
indian Identifier for the Indian calendar.
islamic Identifier for the Islamic calendar.
islamicCivil Identifier for the Islamic civil calendar.
islamicTabular Identifier for the tabular Islamic calendar.
islamicUmmAlQura Identifier for the Islamic Umm al-Qura calendar, as used in Saudi Arabia.
iso86010 Identifier for the ISO8601 calendar.
japanese Identifier for the Japanese calendar.
persian Identifier for the Persian calendar.
republicOfChina Identifier for the Republic of China (Taiwan) calendar.

Which can then be initialized in a Calendar using: Calendar(identifier: Calendar.Identifier.gregorian).

The calendar also has lots of great methods that can help you out in a pinch.

Want the timezone ? Calendar.autoupdatingCurrent.timeZone will help us out. The time between two dates? func compare(_ date1: Date, to date2: Date, toGranularity component: Calendar.Component) -> ComparisonResult will help you out. For the full list you can have a look at the documentation from Apple.

Date comparisons

Date conforms to the comparable protocol, which means that <, > and = can be used to compare dates exactly how you would expect. That is, return a boolean when the expression is resolved.

newDate < now

= and > are handled in similar ways.

Date components from a Date

A date can be separated out into the components that make up that date. The list goes right down to the nanosecond unit — so small!

let nowComponents = Calendar.autoupdatingCurrent.dateComponents([.year, .month, .day, .hour], from: now)nowComponents.year // The current year Int?
nowComponents.month // The current month Int?
nowComponents.day // The current day Int?
nowComponents.hour // The current hour Int?

These components are useful — more about them later!

Putting it all together: Days between two dates

Wouldn’t it be nice to have a function that calculates the days between dates. This uses a dateformatter and two dates that are represented as Strings (which would only make sense if they are in the String format yyyy-MM-dd of course).

This doesn’t really perform any validation (although if the date isn’t of the format yyyy-MM-dd the function should return nil)

Validating dates

If we want to validate a String with the format yyyy-MM-dd (in the current Locale) we can make benefit from the date initializer returning an optional date and ensure that a date is valid.

The date manager

A singleton!

To create a DateManager, we can set up a DateFormatter as a lazy var and all will be rather splendid if we access the manager through a Singleton instance.

But before we go that far, let us look at…

Testing

These tests work in the Playground, providing you import XCTest, and ManagerTests.defaultTestSuite.run().

Pardon the force-unwrapping, and the testing that is rather lacking (just one date per function?). Still…

we can then create a date manager that runs these tests sucessfully.

If you don’t like calling the Singleton directly and think it should be called from Date instead? That sounds like an extension to me!

Conclusion

Phew! That’s quite a lot of text for dates. It is interesting to take the journey from making simple Dates and Strings, and then moving onto a singleton manager and all the way to an extension

It’s quite a lot — but I hope that this article has given you some way to understand this important set of functions in Swift.

Thanks for reading!

Extend your knowledge

The Twitter contact:

Any questions? You can get in touch with me HERE

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store