---
myst:
html_meta:
description: >-
Reference for ISO 8601 support: each type's canonical representation,
parse_iso() and format_iso(), the basic format, and what is accepted when
parsing.
---
(iso8601)=
# ISO 8601
```{eval-rst}
.. currentmodule:: whenever
```
[ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) is an international
standard for representing dates and times.
`whenever` provides support for parsing and formatting ISO 8601 strings
for all date/time and duration classes.
## How to convert
All classes have a canonical ISO 8601 representation.
Their constructors accept ISO 8601 strings as input,
and calling `str()` on an instance returns its ISO 8601 representation:
```python
>>> from whenever import Date, Instant
>>> d = Date("2026-01-23") # parsing
>>> str(Instant.now()) # formatting
"2026-01-23T05:30:15.149822Z"
```
This makes all types easily round-trippable to text,
and thus suitable for lossless serialization, e.g. in JSON or databases.
In addition to the constructor and `str()`,
there are also dedicated `parse_iso()` and `format_iso()` methods to customize things.
Below is a summary of the canonical ISO 8601 representations,
along with the corresponding formatter and parser methods which document the details for each class.
| class | Canonical string example | Formatter | Parser |
| ---- | ---- | ---- | ---- |
| {class}`Instant` | `2026-01-23T05:30:15Z` | {meth}`~Instant.format_iso` | {meth}`~Instant.parse_iso` |
| {class}`ZonedDateTime` | `2026-01-23T14:30:15+09:00[Asia/Tokyo]` [^1] | {meth}`~ZonedDateTime.format_iso` | {meth}`~ZonedDateTime.parse_iso` |
| {class}`OffsetDateTime` | `2026-01-23T14:30:15+09:00` | {meth}`~OffsetDateTime.format_iso` | {meth}`~OffsetDateTime.parse_iso` |
| {class}`PlainDateTime` | `2026-01-23T14:30:15` | {meth}`~PlainDateTime.format_iso` | {meth}`~PlainDateTime.parse_iso` |
| | | |
| {class}`Date` | `2026-01-23` | {meth}`~Date.format_iso` | {meth}`~Date.parse_iso` |
| {class}`Time` | `14:30:15` | {meth}`~Time.format_iso` | {meth}`~Time.parse_iso` |
| | | |
| {class}`TimeDelta` | `PT48H3M4S` | {meth}`~TimeDelta.format_iso` | {meth}`~TimeDelta.parse_iso` |
| {class}`ItemizedDelta` | `P2Y3M5DT4H30M` | {meth}`~ItemizedDelta.format_iso` | {meth}`~ItemizedDelta.parse_iso` |
| {class}`ItemizedDateDelta` | `P2Y3M5D` | {meth}`~ItemizedDateDelta.format_iso` | {meth}`~ItemizedDateDelta.parse_iso` |
| | | |
| {class}`YearMonth` | `2026-01` | {meth}`~YearMonth.format_iso` | {meth}`~YearMonth.parse_iso` |
| {class}`MonthDay` | `--01-23` | {meth}`~MonthDay.format_iso` | {meth}`~MonthDay.parse_iso` |
| {class}`IsoWeekDate` | `2026-W04-5` | {meth}`~IsoWeekDate.format_iso` | {meth}`~IsoWeekDate.parse_iso` |
[^1]: The IANA timezone identifier in square brackets is part of the recent RFC 9557 extension to RFC 3339.
It may not be supported by other systems.
The `repr()` of each class shows the ISO 8601 representation for easy debugging,
and can even be used to recreate the instance:
```python
>>> from whenever import OffsetDateTime
>>> odt = OffsetDateTime("2026-01-23T14:30:15+09:00")
OffsetDateTime("2026-01-23 14:30:15+09:00")
>>> eval(repr(odt)) == odt # of course, use eval() with caution
True
```
## The "basic" format
By default, the ISO 8601 format uses separators such as hyphens and colons
to improve human readability. This is called the "extended" format.
ISO 8601 also defines a "basic" format without separators.
This format is less human-readable, but commonly used in filenames and
identifiers where special characters may be problematic.
The basic format is supported by all parsers,
and can be produced by the formatters by passing `basic=True`.
```python
>>> from whenever import Instant
>>> i = Instant("2026-01-23T05:30:15Z")
>>> i.format_iso(basic=True)
"20260123T053015Z"
```
## What can be parsed?
As you may or may not know, ISO 8601 is a large and complex standard.
Asking whether something "is proper ISO" is like asking whether something
"is proper English"--there are many dialects and variations and people hold different opinions on what is "proper".
Like all datetime libraries, `whenever` has to make some choices about which
parts of the standard to support. `whenever` targets the most common
and widely-used subset of the standard, while avoiding the more obscure
and rarely-used parts, which are often the source of confusion and bugs.
`whenever`'s parsing behavior takes
mostly [after Temporal](https://tc39.es/proposal-temporal/#sec-temporal-iso8601grammar),
namely:
- Both "extended" (e.g. `2023-12-28`) and "basic" (e.g. `20231228`) formats are supported.
- Week and ordinal date formats are *not* supported by the {class}`Date` and datetime parsers:
e.g. `2023-W52-5` or `2023-365`. Week dates can be parsed separately with {class}`IsoWeekDate`.
- A space (``" "``) may be used instead of ``T`` to separate the date and time parts.
- The date, time, and offset parts may independently choose to use extended or basic formats,
so long as they are themselves consistent. e.g. `2023-12-28T113000+03` is OK, but
`2023-1228T11:23` is not.
- ISO designator characters may be lowercase or uppercase (e.g. `2023-12-28T11:30:00Z`
is the same as ``2023-12-28t11:30:00z``). IANA timezone identifiers remain case-sensitive.
- Only seconds may be fractional (e.g. `11:30:00.123456789Z` is OK but `11:30.5` is not).
- Fractional seconds may contain 1--9 digits (nanosecond precision).
- Both `.` and `,` may be used as decimal separators
- Hours range from `00` through `23`; the end-of-day notation `24:00` is not supported.
- The offset `-00:00` is allowed, and is equivalent to `+00:00`
- The ``Z`` suffix, which denotes UTC, is accepted for {class}`OffsetDateTime`
and treated as ``+00:00``.
- Offsets may be specified up to second-level precision (e.g. `2023-12-28T11:30:00+01:23:45`).
- An IANA timezone identifier may be included in square brackets,
like `2023-12-28T11:30:00+01[Europe/Paris]`. For {class}`ZonedDateTime`,
the identifier is required but the preceding offset is optional.
This annotation is part of the recent RFC 9557 extension to RFC 3339.
- In the duration format, the `W` unit may be used alongside other calendar units
(`Y`, `M`, `D`).
- A second value of ``60`` (leap second) is accepted and normalized to ``59``.
See [](faq-leap-seconds) for details.
:::{admonition} Why not support the full ISO 8601 spec?
:class: hint
The full ISO 8601 standard is not supported for several reasons:
- It allows for a lot of rarely-used flexibility:
e.g. fractional hours, week-based years, etc.
- There are different versions of the standard with different rules
- The full specification is not freely available
This isn't a problem in practice since people referring to "ISO 8601"
often mean the most common subset, which is what `whenever` supports.
It's rare for libraries to support the full standard.
If you do need to parse the full spectrum of ISO 8601, you can use
a specialized library such as [`dateutil.parser`](https://dateutil.readthedocs.io/en/stable/parser.html).
:::
## Formatting options
Where applicable, the outputs can be customized using these parameters:
- `unit` controls the smallest unit to include, ranging from `"hour"` to `"nanosecond"`.
The default is `"auto"`, which includes full precision, but without trailing zeros:
```python
>>> i = Instant.now()
>>> i.format_iso(unit="auto")
'2025-09-28T21:24:17.664328Z'
>>> d.format_iso(unit="minute")
'2025-09-28T21:24Z'
>>> d.format_iso(unit="nanosecond")
'2025-09-28T21:24:17.664328000Z' # fixed number of digits
```
- `basic` controls whether to use the "basic" format (i.e. no date and time separators).
By default, the "extended" format is used.
```python
>>> i.format_iso(basic=True)
'20250928T212417.664328Z'
>>> i.format_iso(basic=False)
'2025-09-28T21:24:17.664328Z'
```
- `sep` controls the separator between the date and time parts. `"T"` by default,
but a space (`" "`) may be used instead. Other separators may be allowed in the future.
```python
>>> i.format_iso(sep=" ")
'2025-09-28 21:24:17.664328Z'
```
- `tz` is supported by {meth}`ZonedDateTime.format_iso` and controls whether to
include the IANA timezone identifier in square brackets.
Default is `"always"` which will raise an error if there is no timezone identifier
(this may be the case for some system timezones). Use `"never"` to omit the timezone identifier,
or `"auto"` to include it if available.
```python
>>> d = ZonedDateTime.now("Europe/Amsterdam")
>>> d.format_iso(tz="auto")
'2025-09-28T23:24:17.664328+02:00[Europe/Amsterdam]'
>>> d.format_iso(tz="never")
'2025-09-28T23:24:17.664328+02:00'
```