(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 an 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` | [^1]: The IANA timezone identifier in square brackets is part of the recent RFC 9557 extension to ISO 8601. 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. - Weekday and ordinal date formats are *not* supported: e.g. `2023-W52-5` or `2023-365`. - 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. - Characters may be lowercase or uppercase (e.g. `2023-12-28T11:30:00Z` is the same as ``2023-12-28t11:30:00z``). - Only seconds may be fractional (e.g. `11:30:00.123456789Z` is OK but `11:30.5` is not). - Seconds may be precise up to 9 digits (nanoseconds). - Both `.` and `,` may be used as decimal separators - The offset `-00:00` is allowed, and is equivalent to `+00:00` - Offsets may be specified up to second-level precision (e.g. `2023-12-28T11:30:00+01:23:45`). - A IANA timezone identifier may be included in square brackets after the offset, like `2023-12-28T11:30:00+01[Europe/Paris]`. This is part of the recent RFC 9557 extension to ISO 8601. - In the duration format, the `W` unit may be used alongside other calendar units (`Y`, `M`, `D`). :::{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' ```