ISO 8601¶
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:
>>> 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 |
|---|---|---|---|
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
|||
|
The repr() of each class shows the ISO 8601 representation for easy debugging,
and can even be used to recreate the instance:
>>> 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.
>>> 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,
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-5or2023-365.A space (
" ") may be used instead ofTto 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+03is OK, but2023-1228T11:23is not.Characters may be lowercase or uppercase (e.g.
2023-12-28T11:30:00Zis the same as2023-12-28t11:30:00z).Only seconds may be fractional (e.g.
11:30:00.123456789Zis OK but11:30.5is not).Seconds may be precise up to 9 digits (nanoseconds).
Both
.and,may be used as decimal separatorsThe offset
-00:00is allowed, and is equivalent to+00:00Offsets 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
Wunit may be used alongside other calendar units (Y,M,D).
Why not support the full ISO 8601 spec?
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.
Formatting options¶
Where applicable, the outputs can be customized using these parameters:
unitcontrols the smallest unit to include, ranging from"hour"to"nanosecond". The default is"auto", which includes full precision, but without trailing zeros:>>> 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
basiccontrols whether to use the “basic” format (i.e. no date and time separators). By default, the “extended” format is used.>>> i.format_iso(basic=True) '20250928T212417.664328Z' >>> i.format_iso(basic=False) '2025-09-28T21:24:17.664328Z'
sepcontrols 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.>>> i.format_iso(sep=" ") '2025-09-28 21:24:17.664328Z'
tzis supported byZonedDateTime.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.>>> 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'