Skip to content

← All tools

Free tool for iOS and Mac developers

Swift DateFormatter pattern tester

Type a dateFormat pattern such as yyyy-MM-dd'T'HH:mm:ssZZZZZ and see, as you type, exactly what DateFormatter prints for the date, locale and time zone you choose. The page flags the patterns that cause real bugs (YYYY instead of yyyy, DD instead of dd, hh without a, a missing en_US_POSIX), lists every symbol with a live example, and gives you the Swift code, with ISO8601DateFormatter or Date.FormatStyle where they fit. Its output was checked against DateFormatter on a Mac. Nothing leaves your browser.

Letters are fields (yyyy, MM, HH). Put other text in single quotes: 'T', 'at'. Two quotes ('') print one quote.

Common formats

en_US_POSIX keeps English names, the Gregorian calendar and the pattern exactly as written, whatever the user picked in Settings. Use it for anything a machine reads: APIs, file names, logs.

Date

DateFormatter prints

…

Check the pattern

No known traps in this pattern.

Swift code

DateFormatter

Creating a DateFormatter is slow: make one, keep it (for example in a static let) and reuse it.

let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(identifier: "America/New_York")!
formatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ssZZZZZ"
let text = formatter.string(from: Date())
let parsed = formatter.date(from: text) // nil when the text does not match

ISO8601DateFormatter and Date.ISO8601FormatStyle

Made for exactly this format, no locale needed. Note: with fractional seconds, Date.ISO8601FormatStyle can print one millisecond less than DateFormatter (.123 as .122), because it cuts the binary value instead of rounding.

let iso = ISO8601DateFormatter()
iso.formatOptions = [.withInternetDateTime]
iso.timeZone = TimeZone(identifier: "America/New_York")!
let text = iso.string(from: Date())

// Or, without a formatter object (iOS 15, macOS 12 and later):
let style = Date.ISO8601FormatStyle(timeZoneSeparator: .colon, timeZone: TimeZone(identifier: "America/New_York")!)
let text2 = Date().formatted(style)

Every snippet shape here was compiled with swiftc and printed the same text as the page.

Pattern symbols

Every field DateFormatter knows, with what it prints for the date, locale and zone above. Tap a pattern to add it to yours.

Era

Era: abbreviated, full, narrow.

Year

Calendar year. yy is the last two digits; yyyy pads to four. Use this one.

Week-based year, for use with w. Differs from y in the last and first days of a year.

Extended year: like y, but yy is not cut to two digits.

U (cyclic year) and r (related Gregorian year) print the plain year in the Gregorian calendar.

Quarter

Quarter: number, padded, abbreviated, full, narrow.

Quarter on its own (stand-alone form).

Month

Month: 3, 03, then names. Use MMM and MMMM inside a date.

Month on its own, for headings. Some languages inflect names inside a date; L gives the plain form.

Week

Week of the year. The first weekday and the days needed for week 1 depend on the locale.

Week of the month (0 when the month starts with a short week in some locales).

Day

Day of the month. ddd is an Apple extension: an ordinal in English (7th).

Day of the year, 1 to 366. Not the day of the month.

Which occurrence of this weekday in the month (2 = second Saturday).

Julian day number of the local date.

Weekday

Weekday name: abbreviated, full, narrow, short.

Weekday as a number counted from the locale's first day (Sunday = 1 in the US, Monday = 1 in Germany), or a name from eee.

Weekday on its own (stand-alone form); c and cc are the local number.

AM, PM and day periods

AM or PM: abbreviated, wide, narrow.

Like a, but says noon at 12:00 where the language has a word for it.

Flexible day period: in the morning, at night, 凌晨, 下午.

Hour

Hour 1-12. Needs a.

Hour 0-23. The usual choice for fixed formats.

Hour 0-11. Needs a.

Hour 1-24 (midnight is 24).

Minute

Minute.

Second

Second.

Fraction of a second, cut to the number of S. Only milliseconds are real.

Milliseconds since local midnight.

Time zone

Zone name with daylight saving: EST and EDT. Falls back to GMT-5 where the locale has no short name.

Z: -0500; ZZZZ: GMT-05:00; ZZZZZ: -05:00, and Z for UTC.

Localized GMT offset, short and long.

Zone name without daylight saving: ET, Eastern Time.

Zone id: short code, IANA id, city, location name.

ISO 8601 offset that prints Z for UTC.

ISO 8601 offset that never prints Z.

Names (months, weekdays, day periods, time-zone names) are the ones DateFormatter printed on macOS 26. Older iOS and macOS versions have older locale data, so zone names (z, zzzz, v, vvvv, VVVV) and B day periods can differ there.

How it works

  • •The pattern is split the way Apple's ICU library splits it: runs of one letter are fields, text in single quotes is literal, '' is one quote, and anything else is printed as it is.
  • •Each field is printed from data taken from DateFormatter itself on macOS 26: month, weekday, quarter and era names, AM/PM and day periods, and time-zone names for each locale. Your browser's own names are not used, because Apple's differ in places ("China mainland Time", "GMT+0" instead of "GMT").
  • •The UTC offset at the chosen moment comes from your browser's time-zone rules. Week numbers follow each locale's rules as DateFormatter reports them: weeks start on Sunday in en_US, ja_JP and zh_Hans_CN and on Monday in de_DE, and the days needed for week 1 differ.
  • •Before publishing, 130,800 combinations were compared with DateFormatter on a Mac: every symbol and preset in five locales, thirty dates (the days around New Year for YYYY, leap days, daylight saving changes in New York and Berlin) and four time zones, plus the time-zone symbols in all ten zones. All of them matched. The Swift snippets were compiled and run in the same check.
  • •The checks list the traps that cause real bugs: the week-based year, day of year, 12-hour clocks without AM/PM, a quoted 'Z', zone formats without a colon, and patterns DateFormatter rejects or crashes on.

What it does not do

  • •It is not Apple's ICU. It is a separate implementation that matched DateFormatter on macOS 26 in every case tested. Older iOS and macOS versions ship older locale data, so on those the time-zone names (z, zzzz, v, vvvv, VVVV) and B day periods are approximations.
  • •Five locales and ten time zones only, for dates from 2000 to 2100. Other zones would need their names checked against Apple first, so they are not offered.
  • •It does not apply a device's settings. With any locale except en_US_POSIX, the user's 24-hour switch, calendar (Buddhist, Japanese) or first day of the week can change what DateFormatter prints on a real iPhone.
  • •It formats; it does not parse. date(from:) has its own rules (leniency, two-digit years, default fields), which this page does not imitate.
  • •Times have millisecond precision. DateFormatter also stops at milliseconds, so SSSS and longer end in zeros.
  • •It warns about the patterns that crash DateFormatter (six or more B, or six or more b at noon) instead of imitating the crash.

Frequently asked questions

Why does YYYY show the wrong year?
YYYY is the year of the week, not the calendar year. It only makes sense with w (week of year). In the days around New Year a date can belong to week 1 of the next year or the last week of the previous one: with en_US, 31 December 2024 prints 2025 with YYYY. Use yyyy for normal dates; the bug only shows up for a few days a year, which is why it reaches production.
Why should I set the locale to en_US_POSIX?
Because a fixed format must not depend on the user. With the device's locale, DateFormatter can switch to a Buddhist or Japanese calendar (the year 2569 instead of 2026), localize names, or rewrite hours to follow the user's 24-hour setting, and parsing then fails. en_US_POSIX is guaranteed to stay the same. Apple's Technical Q&A QA1480 recommends it for every fixed-format date, and the formatter's time zone should be set explicitly too.
How do I parse ISO 8601 dates with fractional seconds?
Use ISO8601DateFormatter with formatOptions = [.withInternetDateTime, .withFractionalSeconds]. That formatter then rejects strings without a fraction, so if your API sends both, try one formatter with fractions and one without. With DateFormatter, the pattern is yyyy-MM-dd'T'HH:mm:ss.SSSXXXXX with en_US_POSIX. JSONDecoder's .iso8601 strategy does not accept fractions at all, so use .custom with the formatter.
ISO8601DateFormatter or DateFormatter?
For ISO 8601 text, ISO8601DateFormatter: no locale to forget, it prints Z for UTC and the offset with a colon otherwise, and its options name the parts you want (.withFullDate, .withInternetDateTime). Use DateFormatter for any other fixed format, such as HTTP dates (EEE, dd MMM yyyy HH:mm:ss 'GMT'), and for dateStyle/timeStyle display text. Both are slow to create, so keep one instance and reuse it.
Should I use Date.FormatStyle instead?
On iOS 15 and macOS 12 or later, often yes. For text people read, date.formatted(date: .abbreviated, time: .shortened) follows the user's locale and settings with no pattern at all. For fixed formats, Date.ISO8601FormatStyle and Date.VerbatimFormatStyle replace the pattern string with typed fields; this page shows the verbatim version whenever every field of your pattern has an exact equivalent. The style values are cheap to create.
How do I respect the user's 12- or 24-hour setting?
Don't hard-code hh:mm a or HH:mm for display. Use timeStyle = .short, Date.FormatStyle, or setLocalizedDateFormatFromTemplate("jmm"): the j stands for the hour in whichever cycle the user chose. Keep fixed patterns with en_US_POSIX for data, where the user's setting must not apply.

Related