NSDateFormatter.com

Easy Skeezy Date Formatting for Swift and Objective-C

ISO 8601 format

...

Wednesday, Aug 5, 2020
EEEE, MMM d, yyyy
08/05/2020
MM/dd/yyyy
08-05-2020 18:58
MM-dd-yyyy HH:mm
Aug 5, 6:58 PM
MMM d, h:mm a
August 2020
MMMM yyyy
Aug 5, 2020
MMM d, yyyy
Wed, 5 Aug 2020 18:58:55 +0000
E, d MMM yyyy HH:mm:ss Z
2020-08-05T18:58:55+0000
yyyy-MM-dd'T'HH:mm:ssZ
05.08.20
dd.MM.yy
18:58:55.592
HH:mm:ss.SSS

The following table’s sample column are mostly based on the time December 14th, 2008 4:35 PM UTC.

Characters Example Description
Year
y 2008 Year, no padding
yy 08 Year, two digits (padding with a zero if necessary)
yyyy 2008 Year, minimum of four digits (padding with zeros if necessary)
Quarter
Q 4 The quarter of the year. Use QQ if you want zero padding.
QQQ Q4 Quarter including "Q"
QQQQ 4th quarter Quarter spelled out
Month
M 12 The numeric month of the year. A single M will use '1' for January.
MM 12 The numeric month of the year. A double M will use '01' for January.
MMM Dec The shorthand name of the month
MMMM December Full name of the month
MMMMM D Narrow name of the month
Day
d 14 The day of the month. A single d will use 1 for January 1st.
dd 14 The day of the month. A double d will use 01 for January 1st.
F 3rd Tuesday in December The day of week in the month
E Tue The abbreviation for the day of the week
EEEE Tuesday The wide name of the day of the week
EEEEE T The narrow day of week
EEEEEE Tu The short day of week
Hour
h 4 The 12-hour hour.
hh 04 The 12-hour hour padding with a zero if there is only 1 digit
H 16 The 24-hour hour.
HH 16 The 24-hour hour padding with a zero if there is only 1 digit.
a PM AM / PM for 12-hour time formats
Minute
m 35 The minute, with no padding for zeroes.
mm 35 The minute with zero padding.
Second
s 8 The seconds, with no padding for zeroes.
ss 08 The seconds with zero padding.
SSS 123 The milliseconds.
Time Zone
zzz CST The 3 letter name of the time zone. Falls back to GMT-08:00 (hour offset) if the name is not known.
zzzz Central Standard Time The expanded time zone name, falls back to GMT-08:00 (hour offset) if name is not known.
ZZZZ CST-06:00 Time zone with abbreviation and offset
Z -0600 RFC 822 GMT format. Can also match a literal Z for Zulu (UTC) time.
ZZZZZ -06:00 ISO 8601 time zone format
For the full reference of available format options, see Unicode Technical Reference #35.

The main fallacy to be aware of: dateFormat is not locale-aware

You should be aware that using a custom dateFormat comes with a risk of falling into some fallacies.

Especially, you need to realize that the user can use different Locales, and that date formats are different for different locales, regions and user settings. For example one date formatted with a dateFormat that fits the US might become confusing for anyone in Europe.

The TL;DR

  • Use dateStyle and timeStyle over dateFormat whenever you can. Explain the date fallacies to your designer if you need to.
  • If you can't find a fitting dateStyle/timeStyle to format your UI dates, then at least use dateFormatter.setLocalizedDateFormatFromTemplate(…) to account for the user's locale.
  • When parsing ISO8601 internet dates, always use ISO8601DateFormatter
  • If you can't because your API format doesn't fit ISO8601 and you still absolutely need to use a custom dateFormat, then be sure to also set your dateFormatter.locale to the special value Locale(identifier: "en_US_POSIX").

Formatting user-visible dates for your UI

Apple already has a dedicated paragraph in their documentation about best practices for formatting a date to present to the user in a locale-aware way. Below is just the TL;DR.

Using dateStyle and timeStyle

The main recommendation to follow is to prefer using dateStyle and timeStyle over dateFormat.

This is because those are locale-aware and account for a lot of edge cases that you can otherwise miss when using a custom and hardcoded dateFormat.

I would even advise you to convince your designer against using a custom format and explain them that Date and Time is a tricky subject with lots of edge cases and that it's generally not worth using a custom format that might fit their assumptions about the locale and region they're used to use but might not fit a lot of other's.

Using DateFormatter's templating methods to auto-adjust for the user's Locale

If you still need to use a custom dateFormat, be sure that you use dateFormatter.setLocalizedDateFormatFromTemplate(…) or dateFormatter.dateFormat = DateFormatter.dateFormat(fromTemplate:…options:…locale:…) instead of setting it to a hard-coded String.

Working with ISO8601 dates and fixed formats for APIs

Apple Documentation also has a dedicated section about those cases here.

Using ISO8601DateFormatter

If you need to parse ISO8601 dates, consider using ISODateFormatter instead of a DateFormatter with a custom dateFormat.

This class is dedicated to handle the ISO8601 standard and all its possible variants and edge cases better than using a custom dateFormat would.

Tip: One of the little-known options of ISO8601DateFormatter is that it is also able to handle fractional seconds if you set it up using formatter.formatOptions.insert(.withFractionalSeconds).

Always use the en_US_POSIX locale for fixed formats

In last resort, if you need to parse a date for an API that doesn’t fall into ISO8601 format, but that also isn’t intended for UI (and should thus not depend on the user’s locale/region/language), then that is the only case when you can use a fixed string as a value for dateFormatter.dateFormat.

BUT then ALWAYS also set your dateFormatter.locale to Locale(identifier: "en_US_POSIX") on your DateFormatter.

en_US_POSIX is a special locale that guarantees that the formatting and parsing won’t depend on the phone’s locale, and is designed exactly for parsing those “internet dates with fixed format”.

If you don't force the locale to en_US_POSIX, there are risks that your code might seem to work in some regions (like the US), but will fail to parse your API responses if the user has its phone set in another region (e.g. en_GB, en_ES or fr_FR), where date formatting is different, or use 12-hour time and not 24-hour time.

You can test this kind of edge case on device by setting your phone settings to use en_ES for example and set it to use 12-hour with am/pm, and try to parse a date like 2020-01-15T22:00:00Z.

For more information about those commonly overlooked cases, you can read Apple's TN1480.

This section was contributed by Olivier Halligon

About this Site

nsdateformatter.com is written with Swift 4.2, as a means to learn open-source Swift, the Swift Package Manager, and deploying to Linux. It uses the Vapor web application framework and deployed is to Heroku.

The site is open source, so if you want to fix a bug or submit an enhancement, feel free to submit a pull request.

About the Author

avatar
Ben Scheirman is a developer from Houston, TX. He is the founder of NSScreencast, where you can find over 350 screencasts on iOS development topics.

You can find Ben on Twitter or his blog.

Want to see how to use DateFormatter in Swift?

Check out this free screencast:

Parsing and Formatting Dates