Date and time formats — working reference
On this page
- ISO 8601 — the format you should default to
- RFC 3339 — ISO 8601’s stricter cousin
- RFC 2822 — the email and HTTP date format
- Unix timestamps — the developer favourite
- US format — MM/DD/YYYY
- EU / UK format — DD/MM/YYYY and DD.MM.YYYY
- SQL DATETIME / TIMESTAMP
- Cookie expiry — RFC 1123
- Programming-language quirks
- What format to actually use
- Tools
- TL;DR
There are at least nine date formats in common use, three of them ambiguous, and most software gets at least one of them wrong on edge cases. This page is the reference I keep rebuilding by hand — every format, with an example, the systems that emit it, and the gotchas.
ISO 8601 — the format you should default to
2026-04-28T14:30:00Z
2026-04-28T14:30:00+09:00
2026-04-28T14:30:00.123456Z
The international standard. Year-month-day, time, then a timezone
suffix (Z for UTC or ±HH:MM for an offset). Sortable as plain
strings — alphabetical sort matches chronological sort. Unambiguous
across locales.
Use it as your default. Every modern API, log format, and programming language supports it.
Common variants:
- Date only:
2026-04-28— no time component, treated as “the whole day” or sometimes as midnight UTC depending on parser. - Calendar week:
2026-W17-2— year, ISO week, day. Tuesday of week 17, 2026. Used in scheduling and supply-chain systems. - Ordinal day:
2026-118— year and day-of-year. Astronomy, some business systems. - No timezone:
2026-04-28T14:30:00— local time. Avoid. This is the source of half of all timezone bugs. Always include the suffix.
Gotcha: the T separator is required by the spec, but many
systems accept a space (2026-04-28 14:30:00Z) for human readability.
Don’t write parsers that depend on this — the spec requires T.
RFC 3339 — ISO 8601’s stricter cousin
2026-04-28T14:30:00Z
2026-04-28T14:30:00.123-05:00
A profile of ISO 8601 used in JSON / HTTP / many APIs. Stricter than ISO 8601 in two ways:
- The
Tseparator is required (no space). - The timezone is required (no naïve datetimes).
Practically, if you emit RFC 3339 you also emit valid ISO 8601. If you emit ISO 8601, you might not be RFC 3339 compliant.
Use it for: JSON APIs, OpenAPI specs, log files. JavaScript’s
new Date().toISOString() produces RFC 3339 output.
RFC 2822 — the email and HTTP date format
Tue, 28 Apr 2026 14:30:00 +0000
Tue, 28 Apr 2026 14:30:00 GMT
Originally for email headers (Date:) but now also in HTTP headers
(Date:, Last-Modified:, Expires: — though HTTP technically uses
RFC 7231, which is RFC 2822 with restrictions).
The day-of-week prefix is derivable from the date — including it is redundant, but the spec requires it.
Gotcha: named timezones (GMT, EST, PST) are technically
allowed but discouraged. RFC 2822 prefers numeric offsets (+0000,
-0500). Some parsers reject named timezones; some accept them but
guess wrong (does “EST” mean Eastern Standard Time or Australian
Eastern Standard Time?).
Use it for: email headers, HTTP headers, legacy systems that require it. Avoid for new APIs.
Unix timestamps — the developer favourite
1745855400 (seconds)
1745855400123 (milliseconds)
1745855400123456 (microseconds)
1745855400123456789 (nanoseconds)
Seconds since 1970-01-01T00:00:00Z. The most compact, sortable, and unambiguous representation. No timezone needed (it’s always UTC).
The big four resolutions:
- Seconds: 10 digits today. Used by Unix utilities, JWT (
exp,iat,nbf), most file systems. - Milliseconds: 13 digits. Used by JavaScript’s
Date.now(), most databaseBIGINTtimestamp columns. - Microseconds: 16 digits. Some databases, profiling tools.
- Nanoseconds: 19 digits. Go’s
time.UnixNano(), kernel-level timing.
Spotting which one you have: count digits. If a “timestamp” has 13 digits, it’s milliseconds. If 16, microseconds. The conversion is trivial — just divide by 1000.
The epoch converter auto-detects which resolution you’ve pasted.
Gotcha 1: Y2038. Signed 32-bit Unix timestamps overflow on 2038-01-19. Most modern systems use 64-bit, but check yours if you have any expiry dates >30 years out.
Gotcha 2: leap seconds. Strict POSIX time ignores leap seconds — the timestamp jumps backward by 1 on insertion. Most operating systems smear the leap second across nearby seconds (Google’s “leap smear”) to avoid this. If you compare timestamps across leap-second boundaries on incompatible systems, you can be off by 1 second.
US format — MM/DD/YYYY
4/28/2026
04/28/2026
4-28-2026
US default. Used in form inputs, news copy, banking statements.
Gotcha: ambiguous against EU format. 04/05/2026 is April 5 (US)
or May 4 (EU). Without context you can’t tell. Never use slash-format
dates in APIs or logs. Reserve them for human-formatted output where
the locale is known.
EU / UK format — DD/MM/YYYY and DD.MM.YYYY
28/04/2026 (UK, France, Australia)
28.04.2026 (Germany, Russia)
28-04-2026 (Netherlands)
Day-month-year. Aligns with how dates are spoken in most European languages.
Gotcha: same ambiguity as US format. Reserve for human display.
SQL DATETIME / TIMESTAMP
2026-04-28 14:30:00 (MySQL DATETIME)
2026-04-28 14:30:00.123456+00 (Postgres TIMESTAMPTZ)
2026-04-28 14:30:00.000 (SQL Server)
Most relational databases accept ISO 8601 input but emit a locale-specific or vendor-specific format on display.
Critical distinction: TIMESTAMP in Postgres is timezone-aware
and stored as UTC; TIMESTAMP WITHOUT TIME ZONE is naïve and stored
literally. MySQL DATETIME is always naïve; MySQL TIMESTAMP is
auto-converted to UTC on insert and back to the connection’s timezone
on read. Know which one your column is.
Use it for: database columns. Convert to ISO 8601 at the API boundary.
Cookie expiry — RFC 1123
Tue, 28 Apr 2026 14:30:00 GMT
Looks identical to RFC 2822 except GMT is mandatory. Browsers’
Set-Cookie parsers vary on what they accept; always emit exactly
this form.
Programming-language quirks
| Language | Common output | Notes |
|---|---|---|
| JavaScript | new Date().toISOString() | RFC 3339 with millisecond precision |
| Python | datetime.isoformat() | ISO 8601, optional timezone |
| Java | Instant.toString() | RFC 3339, nanosecond precision |
| Go | time.Now().Format(time.RFC3339) | Strict RFC 3339 |
| Ruby | Time.now.iso8601 | ISO 8601, optional fractional seconds |
| C# | DateTime.UtcNow.ToString("o") | ”round-trip” format ≈ RFC 3339 |
| Rust | chrono::Utc::now().to_rfc3339() | RFC 3339 |
| SQL | varies — see above | varies |
The “default toString” of each language often emits something different
— Date.toString() in JavaScript gives a human-locale form
(Tue Apr 28 2026 ...) that is not ISO 8601 and not parseable
back to a Date in all engines. Always use the explicit ISO method.
What format to actually use
A short decision tree:
- API request/response: ISO 8601 / RFC 3339, UTC, millisecond precision. Fight for this in code review.
- Log files: ISO 8601 with timezone. Sortable as text, debuggable from anywhere.
- Database: language-native timestamp type, configured to UTC. Convert on the way out.
- Cookies: RFC 1123 (browser-mandated).
- Email or HTTP headers (legacy): RFC 2822.
- Filename or URL: ISO 8601 date-only, no spaces or colons.
2026-04-28-myreport.jsonsorts and reads cleanly. - Human-facing display: format for the user’s locale (
Intl.DateTimeFormatin JavaScript,babel.datesin Python). Never expose ISO 8601 directly to an end user — they want “April 28, 2026,” not “2026-04-28T14:30:00Z.”
Tools
- Epoch converter — auto-detects which resolution your timestamp is in (sec/ms/µs/ns/ISO/RFC) and shows every other format.
- Days-between calculator — for date math without DST drift.
date -u +"%Y-%m-%dT%H:%M:%SZ"(POSIX shell) — emit current ISO 8601 UTC.
TL;DR
- Default to ISO 8601 (or RFC 3339 if you want to be strict).
- Always include a timezone. Always.
- For storage: 64-bit Unix milliseconds is the simplest; ISO 8601 is the most readable. Both are fine.
- For display: convert with
Intl.DateTimeFormator your language’s locale-aware formatter. - Avoid: naïve datetimes, US/EU short forms in APIs, anything with named (rather than offset) timezones.
When in doubt, paste the value into the epoch converter to see what it actually decodes as.