They Look the Same. They're Not.
Compare these two timestamps:
2026-03-15T14:30:00Z // Valid in both
2026-03-15T14:30:00+05:30 // Valid in both
If RFC 3339 and ISO 8601 produce the same output most of the time, why do two standards exist? Because ISO 8601 covers far more ground, and RFC 3339 intentionally restricts it for internet protocols.
I've been in API design reviews where someone says "we use ISO 8601" and thinks the conversation is over. It's not. ISO 8601 is a massive standard with dozens of valid formats, many of which will break your parser. Saying "we use ISO 8601" is like saying "we use Unicode" — technically true, but hopelessly vague as a specification.
ISO 8601: The Kitchen Sink
ISO 8601, first published in 1988 and revised in 2019, is a broad international standard. It covers:
- Dates:
2026-03-15 - Times:
14:30:00 - Combined:
2026-03-15T14:30:00+05:30 - Durations:
P3Y6M4DT12H30M5S(3 years, 6 months, 4 days, 12 hours, 30 minutes, 5 seconds) - Intervals:
2026-01-01/2026-12-31 - Week dates:
2026-W11-3(Wednesday of week 11) - Ordinal dates:
2026-074(74th day of the year) - Reduced precision:
2026-03(just year-month)
It's comprehensive. It's also expensive — the full spec costs around CHF 200 from ISO. Most developers have never read it.
The 2019 revision (ISO 8601-1:2019 and ISO 8601-2:2019) actually split the standard into two parts. Part 1 covers the basic representations above. Part 2 adds extensions like uncertain dates (2026-03-15? — a date that might be wrong), approximate dates (2026-03-15~), and unspecified digits (2026-03-XX). These Part 2 features are used in archival and library sciences, where you might know a document is from March 2026 but not which day. You'll probably never encounter them in an API, but they exist.
RFC 3339: The Internet Profile
RFC 3339 (published 2002) defines "Date and Time on the Internet: Timestamps." It's a strict subset of ISO 8601, designed specifically for internet protocols. Key restrictions:
- Only the
YYYY-MM-DDTHH:MM:SSZformat (with optional fractional seconds) - Timezone is always present: either
Zor a numeric offset like+05:30 - No week dates, ordinal dates, durations, or intervals
- The
Tseparator is required (ISO allows a space in some contexts) - It's free to read (ietf.org/rfc/rfc3339)
The "timezone is always present" rule is the single most important difference for API design. An ISO 8601 timestamp without a timezone is technically valid: 2026-03-15T14:30:00. A consumer has no way to know whether that's UTC, the server's local time, the user's local time, or something else entirely. RFC 3339 eliminates this ambiguity by requiring either Z or an offset. That alone saves countless debugging hours.
Key Differences
| Feature | ISO 8601 | RFC 3339 |
|---|---|---|
Durations (P3Y6M) | Yes | No |
Intervals (start/end) | Yes | No |
Week dates (2026-W11) | Yes | No |
Ordinal dates (2026-074) | Yes | No |
| Timezone required | No | Yes |
Reduced precision (2026-03) | Yes | No |
| Fractional seconds | Yes (comma preferred) | Yes (period required) |
| Free to read | No (~CHF 200) | Yes (ietf.org) |
One subtle difference: ISO 8601 prefers a comma as the decimal separator for fractional seconds (14:30:00,500), while RFC 3339 requires a period (14:30:00.500). In practice, almost everyone uses periods regardless.
The "Basic" Format Trap
ISO 8601 actually defines two representations: "extended" (with hyphens and colons) and "basic" (without). Both of these are valid ISO 8601:
2026-03-15T14:30:00+05:30 // Extended format
20260315T143000+0530 // Basic format
That second one — the compact version — is technically valid ISO 8601. It's used in some legacy systems, iCalendar (.ics) files, and a few older file format specs. RFC 3339 only allows the extended format (with separators). If your parser accepts "ISO 8601" without specifying which variant, someone will eventually send you the basic format and your parsing will break.
I've seen this exact issue in webhook integrations. A payment processor documented their timestamps as "ISO 8601 format" and then started sending basic format strings in a subset of their webhook payloads. Every consumer that used a strict RFC 3339 parser started failing silently. The lesson: if you're consuming timestamps from external systems, be liberal in what you accept but canonical in what you produce.
Fractional Seconds: How Much Precision?
Both standards allow fractional seconds, but neither specifies a maximum precision. In practice, you'll see:
- Milliseconds (3 digits):
2026-03-15T14:30:00.123Z— most web APIs - Microseconds (6 digits):
2026-03-15T14:30:00.123456Z— database timestamps, Python'sisoformat() - Nanoseconds (9 digits):
2026-03-15T14:30:00.123456789Z— Go'stime.RFC3339Nano, some logging systems
If your API produces microsecond timestamps but your consumer only parses milliseconds, they'll either truncate silently or reject the input. Go is a common source of this friction — time.Format(time.RFC3339Nano) emits nanosecond precision with trailing zeros stripped, so you might get .123456789Z or .12Z from the same code path, depending on the value. Document your precision. If you say "RFC 3339 with millisecond precision," there's no ambiguity.
Which Should Your API Use?
For REST APIs, RFC 3339. It's simpler, requires explicit timezone information (preventing ambiguous local times), and is what the internet ecosystem expects. OpenAPI, JSON Schema, and most API design guides reference RFC 3339 specifically.
If you need to express durations, intervals, or week dates (like scheduling or calendar APIs), you'll need ISO 8601 for those specific features. But your timestamps should still follow RFC 3339.
The pragmatic answer: format your timestamps as RFC 3339 and you'll be compliant with both standards for the timestamp case. Say "RFC 3339" in your API docs because it's a tighter contract — it tells consumers exactly what to expect.
What Real-World APIs Actually Do
I surveyed a handful of major APIs to see what they actually document and return:
- GitHub API: Returns RFC 3339 timestamps in UTC with
Zsuffix. Documented as "ISO 8601 format." Millisecond precision. - Stripe API: Returns Unix timestamps (integers), not ISO/RFC strings. Documented clearly.
- Slack API: Returns Unix timestamps as floating-point numbers with microsecond precision. Unusual but unambiguous.
- Google APIs: Generally return RFC 3339 timestamps. The Calendar API uses ISO 8601 for durations in event data.
- AWS: Uses ISO 8601 timestamps, but different services have different precision. DynamoDB returns epoch milliseconds; CloudWatch returns ISO 8601 with variable precision.
Notice the inconsistency even among top-tier APIs. Stripe's choice to use Unix timestamps sidesteps the whole ISO-vs-RFC debate — there's no ambiguity in an integer. But it trades human readability for machine simplicity. For most APIs, RFC 3339 with UTC (ending in Z) is the sweet spot: human-readable, unambiguous, sortable as a string, and universally parseable.
Parsing Libraries and How They Handle the Differences
Different languages handle the ISO/RFC distinction differently, and it's worth knowing how your platform behaves:
JavaScript: Date.parse() and new Date(string) accept a subset of ISO 8601 as defined in the ECMAScript spec. They require the extended format (with hyphens) and the T separator. Date-only strings like "2026-03-15" are parsed as UTC, while datetime strings without Z are parsed as local time — a frustrating inconsistency. The upcoming Temporal API will have separate parsing for ISO 8601 and RFC 3339.
Python: datetime.fromisoformat() (Python 3.11+) handles most ISO 8601 timestamps including timezone offsets. It does not parse durations or week dates — those need separate handling. The dateutil.parser.isoparse() function from python-dateutil is more permissive and handles a wider range of ISO 8601 formats.
Go: Go doesn't parse format strings — it uses reference layouts. time.RFC3339 and time.RFC3339Nano are predefined constants for parsing. There's no built-in ISO 8601 duration parser, which catches people off guard when they try to parse Kubernetes or YouTube API durations.
Java: java.time has excellent ISO 8601 support. Instant.parse() handles RFC 3339 timestamps. Duration.parse() and Period.parse() handle ISO 8601 durations. LocalDate.parse() handles date strings. It's probably the most complete implementation in any mainstream language.
Frequently Asked Questions
Is RFC 3339 a subset of ISO 8601?
Almost. RFC 3339 is described as a "profile" of ISO 8601 for internet use. It restricts ISO 8601 to just timestamps (no durations, intervals, or week dates) and requires an explicit timezone. There are a couple of minor deviations — RFC 3339 technically allows a lowercase "t" separator, which ISO 8601 doesn't — but for all practical purposes, yes, valid RFC 3339 is valid ISO 8601.
Which format should I use for APIs?
RFC 3339 for timestamps. It's simpler, requires timezone information, and is the standard referenced by OpenAPI, JSON Schema, and most API design guides. Use ISO 8601 features (durations, intervals) only when you specifically need them.
What does "Z" mean in a timestamp?
"Z" stands for "Zulu time" and indicates UTC (zero offset from UTC). 2026-03-15T14:30:00Z means 2:30 PM UTC. It's equivalent to 2026-03-15T14:30:00+00:00.
What is an ISO 8601 duration format?
ISO 8601 durations use the format PnYnMnDTnHnMnS, where P marks the start of the duration. For example, P3Y6M4DT12H30M5S means 3 years, 6 months, 4 days, 12 hours, 30 minutes, and 5 seconds. RFC 3339 does not support this format — it only covers timestamps.
Can I use a space instead of "T" between date and time?
ISO 8601 allows a space separator by mutual agreement of the communicating parties, but RFC 3339 requires the "T" separator. For APIs, always use "T" to ensure maximum compatibility. Some parsers accept a space, but it is not guaranteed across all implementations.
What is an ISO week date?
An ISO week date represents a date using its ISO year, week number, and day of the week, formatted as YYYY-Www-D. For example, 2026-W11-3 means Wednesday of week 11 in 2026. This format is part of ISO 8601 but is not supported by RFC 3339.
Should I store timestamps with or without timezone offsets?
Always include a timezone offset or use UTC (the "Z" suffix). A timestamp without timezone information is ambiguous — 2026-03-15T14:30:00 could be any timezone. RFC 3339 requires an explicit offset for exactly this reason. Store in UTC and convert to local time only at the display layer.
Sources
- RFC 3339 — Date and Time on the Internet: Timestamps (ietf.org/rfc/rfc3339)
- ISO 8601:2019 — Date and time format (iso.org)
- IETF — "Use of the 'datetime' Profile of ISO 8601" (datatracker.ietf.org)