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

Instant

2026-01-23T05:30:15Z

format_iso()

parse_iso()

ZonedDateTime

2026-01-23T14:30:15+09:00[Asia/Tokyo] [1]

format_iso()

parse_iso()

OffsetDateTime

2026-01-23T14:30:15+09:00

format_iso()

parse_iso()

PlainDateTime

2026-01-23T14:30:15

format_iso()

parse_iso()

Date

2026-01-23

format_iso()

parse_iso()

Time

14:30:15

format_iso()

parse_iso()

TimeDelta

PT48H3M4S

format_iso()

parse_iso()

ItemizedDelta

P2Y3M5DT4H30M

format_iso()

parse_iso()

ItemizedDateDelta

P2Y3M5D

format_iso()

parse_iso()

YearMonth

2026-01

format_iso()

parse_iso()

MonthDay

--01-23

format_iso()

parse_iso()

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-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).

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:

  • unit controls 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
    
  • basic controls 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'
    
  • 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.

    >>> i.format_iso(sep=" ")
    '2025-09-28 21:24:17.664328Z'
    
  • tz is supported by 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.

    >>> 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'