Date Formatting Cheat Sheet — ISO 8601, RFC 3339, and Format Tokens Across Languages

Dates look simple until you have to move them between a browser in Mumbai, a server in Frankfurt, and a database that thinks in UTC. This cheat sheet covers the one format every developer should default to — ISO 8601 — plus its stricter cousin RFC 3339, the neat trick that makes ISO dates sortable as plain strings, the golden rule of storage vs display, and a side-by-side reference of format tokens in JavaScript, Python, and SQL.

Anatomy of an ISO 8601 Timestamp

ISO 8601 arranges every component from largest to smallest — year, month, day, hour, minute, second — which is exactly what makes it unambiguous and sortable. A full timestamp breaks down like this:

2026-07-16T14:30:05.250+05:30
└──┬─────┘│└──┬──────────┘└─┬──┘
   date    T   time          offset

2026-07-16    date part: YYYY-MM-DD (always zero-padded)
T             literal separator between date and time
14:30:05      time part: HH:mm:ss (24-hour clock)
.250          optional fractional seconds
+05:30        UTC offset (IST) — or "Z" meaning UTC itself

2026-07-16T09:00:05Z        same instant, expressed in UTC
2026-07-16                  date only — also valid ISO 8601
14:30:05                    time only — also valid ISO 8601
  • Z stands for "Zulu time" and means UTC — a zero offset.
  • +05:30 means the local clock is 5 hours 30 minutes ahead of UTC (India Standard Time).
  • A timestamp without a Z or an offset is a "local" time — its actual instant is ambiguous, which causes real bugs. Always include the offset in data you exchange.

RFC 3339: The Strict Internet Profile

ISO 8601 is a large standard that also permits exotic forms — week dates (2026-W29-4), ordinal dates (2026-197), and timestamps with no offset at all. RFC 3339 is the subset used on the internet: it requires a complete date and time with an explicit offset, and drops the exotic variants. When an API says "ISO 8601", it almost always means RFC 3339 in practice.

  • Every RFC 3339 timestamp is valid ISO 8601; the reverse is not true.
  • RFC 3339 allows a space instead of T as a readability concession — but for maximum compatibility, always emit the T.
  • JSON Schema's date-time format, OpenAPI, and Kubernetes manifests all specify RFC 3339.

The Superpower: ISO Dates Sort Correctly as Strings

Because components run from largest to smallest with fixed zero-padded widths, lexicographic (alphabetical) order equals chronological order — as long as all values share the same timezone (use UTC). You can sort ISO timestamps with a plain string sort, no date parsing needed:

// String sort = chronological sort for ISO 8601 UTC strings
const events = [
  '2026-07-16T09:00:00Z',
  '2025-12-31T23:59:59Z',
  '2026-01-05T08:15:00Z',
];
events.sort(); // ['2025-12-31...', '2026-01-05...', '2026-07-16...']

// This is why ISO dates work in filenames, S3 keys, and log lines:
backup-2026-07-16.sql.gz   // ls sorts these chronologically
backup-2026-07-17.sql.gz

// dd/mm/yyyy fails the same test:
['16/07/2026', '05/01/2026'].sort()
// → ['05/01/2026', '16/07/2026'] only by luck — compare
//   ['02/01/2026', '15/12/2025'] which sorts WRONG

Storage vs Display: Never Store dd/mm/yyyy

The golden rule: store in one canonical machine format, format for humans only at the last moment. Locale formats like 16/07/2026 (India, UK) and 7/16/2026 (US) are display concerns. Stored, they are ambiguous — is 03/04/2026 the 3rd of April or the 4th of March? — and unsortable.

  • Store: UTC ISO 8601 strings, or native TIMESTAMP/DATETIME columns, or Unix epoch integers.
  • Transmit: RFC 3339 strings in JSON ("2026-07-16T09:00:00Z").
  • Display: convert to the user's locale and timezone in the UI layer.
// JavaScript: locale display with Intl (no library needed)
const d = new Date('2026-07-16T09:00:00Z');

new Intl.DateTimeFormat('en-IN', {
  dateStyle: 'medium',
  timeStyle: 'short',
  timeZone: 'Asia/Kolkata',
}).format(d);
// → "16 Jul 2026, 2:30 pm"

new Intl.DateTimeFormat('en-US', { dateStyle: 'short' }).format(d);
// → "7/16/26"  (same stored value, different display)

Format Token Cheat Sheet: JavaScript vs Python vs SQL

Every ecosystem invented its own placeholder tokens, and mixing them up is a classic bug (Python's %m is month, but many JS libraries use mmfor minutes). Here is the mapping for the most common pieces:

Part          Python        MySQL          PostgreSQL   Example
              strftime      DATE_FORMAT    to_char
------------------------------------------------------------------
Year (4)      %Y            %Y             YYYY         2026
Year (2)      %y            %y             YY           26
Month (01)    %m            %m             MM           07
Month name    %B            %M             Month        July
Day (01)      %d            %d             DD           16
Hour 24h      %H            %H             HH24         14
Hour 12h      %I            %h             HH12         02
Minute        %M            %i             MI           30
Second        %S            %s             SS           05
AM/PM         %p            %p             AM           PM
Weekday       %A            %W             Day          Thursday

Python:      dt.strftime('%Y-%m-%d %H:%M:%S')
MySQL:       DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s')
PostgreSQL:  to_char(created_at, 'YYYY-MM-DD HH24:MI:SS')
JavaScript:  no tokens built in — use date.toISOString() for
             machines and Intl.DateTimeFormat for humans

Watch the traps: MySQL uses %i for minutes (not %M, which is month name), and PostgreSQL's MM is month while MI is minutes. When output looks right but values are subtly wrong — minutes showing as months — a token mix-up is almost always the cause. To sanity-check a conversion, run the same instant through the timestamp converter and compare.

Quick Rules to Remember

  • Default to UTC ISO 8601 / RFC 3339 for storage, logs, filenames, and APIs.
  • Always include the Z or offset — a timestamp without one is a bug waiting to happen.
  • Format for locale only at display time, using Intl.DateTimeFormat in JS or babel/ICU in Python.
  • Never parse dd/mm/yyyy or mm/dd/yyyy from user input without knowing the locale explicitly.
  • Zero-pad everything — 2026-7-16 is not valid ISO 8601 and breaks string sorting.

Frequently Asked Questions

What is the ISO 8601 date format?

ISO 8601 is the international standard for writing dates and times: YYYY-MM-DD for dates and YYYY-MM-DDTHH:mm:ssZ for timestamps. Components go from largest to smallest (year first), the letter T separates date from time, and Z or a numeric offset like +05:30 indicates the timezone.

What is the difference between ISO 8601 and RFC 3339?

RFC 3339 is a stricter internet profile of ISO 8601. It requires the full date and time with an explicit timezone offset, and disallows loose ISO variants like week dates, ordinal dates, or omitting the offset. Every valid RFC 3339 timestamp is valid ISO 8601, but not the reverse.

Why should I never store dates as dd/mm/yyyy?

dd/mm/yyyy is ambiguous (03/04/2026 is 3 April in India but 4 March in the US), and it does not sort correctly as a string. Store dates in ISO 8601 (YYYY-MM-DD) or as UTC timestamps, then format them per locale only at display time using Intl.DateTimeFormat or equivalent.

Try the Free Timestamp Converter

Convert Unix timestamps to ISO 8601 and human-readable dates — and back — with instant timezone conversion. No signup, no cost.

Related articles