RFC 3339 vs ISO 8601 — what's the difference?
April 6, 2026
5 min read
Written by ConvertTime Team
rfc-3339iso-8601date-formatapi-designdevelopers
RFC 3339 and ISO 8601 are both date and time formats. They look almost identical. The differences are in strictness — RFC 3339 is essentially a stricter subset of ISO 8601 with mandatory components and tighter rules. For internet protocols (APIs, JSON, HTTP headers), RFC 3339 is what you want.
For converting between any of these formats and Unix timestamps, the [epoch converter](/calculators/epoch) handles them.
## The core difference
ISO 8601 is broad — supporting many date and time formats:
```
2026-05-21
2026-05-21T14:30:00
2026-05-21T14:30:00Z
2026-05-21T14:30:00+05:30
2026-05-21T14:30:00.123Z
20260521T143000Z (compact form)
2026-W21-3 (week date)
2026-141 (ordinal date)
```
RFC 3339 is narrow — specifying a strict subset:
```
2026-05-21T14:30:00Z
2026-05-21T14:30:00+05:30
2026-05-21T14:30:00.123Z
```
Other ISO 8601 formats (compact, week-date, ordinal) are NOT RFC 3339 compliant.
## Specific RFC 3339 rules
What RFC 3339 requires:
1. **Always full datetime** — no date-only or time-only forms
2. **Always 4-digit year, 2-digit month, day, etc.** — leading zeros required
3. **Always 'T' separator** between date and time (uppercase)
4. **Always include seconds** — no `2026-05-21T14:30Z` (without seconds)
5. **Always specify time zone** — either `Z` or numeric offset
6. **Numeric offset format** — `+HH:MM` (with colon, exactly 2 digits each)
7. **Lowercase 't' and 'z'** are tolerated but uppercase preferred
What RFC 3339 explicitly forbids:
- Date without time
- Time without date
- Compact form (no separators)
- Week-date format
- Ordinal date format
- Time zone abbreviations
- Times without offset/Z marker
## Why the strictness
RFC 3339 was designed for internet protocols where ambiguity costs:
- **Email date headers** must be parseable by all email clients
- **HTTP date headers** must be parseable by all servers
- **JSON API responses** should be parseable without configuration
- **Logs** should be machine-readable consistently
A stricter subset means parsers can be smaller, faster, and less error-prone. Every RFC 3339 timestamp is unambiguously parseable.
## Examples — which is which
```
2026-05-21 ISO 8601 only (no time)
2026-05-21T14:30:00Z Both
2026-05-21T14:30:00.123Z Both
2026-05-21T14:30:00+05:30 Both
20260521T143000Z ISO 8601 only (compact form)
2026-W21-3 ISO 8601 only (week date)
2026-141 ISO 8601 only (ordinal date)
2026-05-21 14:30:00 ISO 8601 only (space instead of T)
2026-05-21T14:30:00 EDT Neither (time zone name)
2026-05-21T14:30Z ISO 8601 only (no seconds)
```
For machine-to-machine communication (APIs), use RFC 3339 strict format. For internal storage flexibility, ISO 8601 broad format is fine.
## Format choice in different contexts
**HTTP/RFC 7231 headers** (e.g., `Last-Modified`): use IMF-fixdate format, which is `Sun, 06 Nov 1994 08:49:37 GMT`. Different from both ISO 8601 and RFC 3339.
**JSON API responses**: usually RFC 3339. JSON has no native date type; strings are the standard, and RFC 3339 strings are universally parseable.
**Database storage**: typically internal binary formats (PostgreSQL `timestamptz`, MySQL `TIMESTAMP`). ISO 8601 strings for input/output.
**Configuration files** (YAML, TOML): typically ISO 8601, sometimes with type hints. YAML 1.2 has native ISO 8601 support.
**Log files**: typically RFC 3339 (or close to it). Some legacy logs use older formats.
**Programming language defaults**: most modern languages output close-to-RFC-3339 by default with their `toISOString()` or equivalent.
## Common mistakes
**Confusing the two**. People often use "ISO 8601" loosely to mean "the YYYY-MM-DD with T format." That's actually a small subset of ISO 8601 and is closer to RFC 3339.
**Mixing valid forms in the same dataset**. If your API returns `2026-05-21T14:30:00Z` for some records and `2026-05-21T14:30:00.000Z` for others, parsers can handle it but consistency is better.
**Sending non-RFC-3339 to RFC-3339-strict parsers**. Some strict APIs reject `2026-05-21 14:30:00` (with space) even though ISO 8601 allows it.
**Forgetting time zone in API responses**. `2026-05-21T14:30:00` (no Z, no offset) is ambiguous. RFC 3339 requires you specify.
## API design recommendations
If you're designing an API:
**1. Use RFC 3339 for all timestamps.**
```json
{
"created_at": "2026-05-21T14:30:00Z",
"updated_at": "2026-05-21T14:32:15Z"
}
```
**2. Document the format explicitly** in API docs:
> "All timestamps are in RFC 3339 format with UTC time zone (Z suffix)."
**3. Always include time zone**. Either UTC ('Z') or offset.
**4. Be consistent with precision**. If milliseconds, always milliseconds. Don't mix formats.
**5. Don't use language-specific date formats**. Don't return `Date(1234567890)` (JavaScript) or `\Date(1234567890000)\` (Microsoft). Use RFC 3339.
## When to deviate
Some situations where deviation is OK:
**Internal-only systems** with controlled clients: any reasonable format works.
**Legacy interoperability**: if a partner's API only accepts a specific format, match it.
**Display dates** (different from machine-readable timestamps): localized formats are fine.
**Compact storage**: in extremely high-volume systems, consider Unix timestamps (integers) instead of ISO 8601 strings to save space. But this is a niche optimization.
## Parsing RFC 3339 in different languages
```javascript
// JavaScript
const date = new Date("2026-05-21T14:30:00Z");
const iso = date.toISOString(); // "2026-05-21T14:30:00.000Z"
```
```python
# Python (Python 3.7+)
from datetime import datetime
date = datetime.fromisoformat("2026-05-21T14:30:00+00:00")
# Note: Python's fromisoformat is strict; replace 'Z' with '+00:00' first
```
```ruby
# Ruby
require 'time'
date = Time.parse("2026-05-21T14:30:00Z")
```
```go
// Go
import "time"
date, err := time.Parse(time.RFC3339, "2026-05-21T14:30:00Z")
```
```rust
// Rust (using chrono)
use chrono::DateTime;
let date: DateTime = "2026-05-21T14:30:00Z".parse().unwrap();
```
All these libraries handle the strict RFC 3339 format consistently.
## RFC 3339 in HTTP headers
HTTP headers like `Last-Modified` and `Expires` use a different format (IMF-fixdate, RFC 7231):
```
Sun, 06 Nov 1994 08:49:37 GMT
```
This is NOT RFC 3339. It's a separate format historically used in email (RFC 5322) and HTTP. When designing APIs, prefer RFC 3339 in JSON bodies, even though HTTP headers use the older format.
## FAQ
### Should I use ISO 8601 or RFC 3339 for my API?
Use RFC 3339. It's the strict subset that's universally parseable. ISO 8601's broader range allows formats that some parsers reject.
### Are RFC 3339 timestamps valid ISO 8601?
Yes — RFC 3339 is a subset of ISO 8601. Every RFC 3339 timestamp is also valid ISO 8601.
### Can I use timezone names like "EDT" in RFC 3339?
No. RFC 3339 requires numeric offsets (`-04:00`) or 'Z'. Time zone abbreviations are not allowed.
### What's the difference between `Z` and `+00:00`?
Both mean UTC. `Z` is the "Zulu" indicator. `+00:00` is the explicit offset. Both are RFC 3339 compliant.
### Why do my API timestamps fail validation?
Common causes: missing 'T' separator, missing time zone marker, time zone name instead of offset, lowercase 'z' (some strict parsers reject), missing seconds.
### Should I use space or 'T' between date and time?
For RFC 3339: 'T' (uppercase). For ISO 8601: either is technically valid but 'T' is preferred. Always use 'T' for safety.
## Bottom line
RFC 3339 is a strict subset of ISO 8601 designed for internet protocols. Every RFC 3339 timestamp is valid ISO 8601, but not vice versa. For APIs, JSON, and any machine-readable format, use RFC 3339. For internal storage flexibility, ISO 8601 broad format is fine.
For converting timestamps between ISO 8601 / RFC 3339 strings and Unix epoch, the [epoch converter](/calculators/epoch) handles both directions.