Formatting and parsing¶
whenever reads and writes the standard formats: ISO 8601 as the canonical,
round-trippable representation, plus RFC 2822 for email and HTTP, and
patterns for everything else.
ISO 8601¶
All types in whenever use ISO 8601 as their canonical string representation. This representation is round-trippable except in the uncommon case of a zoned datetime backed by a system time zone without a time zone ID (see The system time zone). You can even instantiate objects directly from their ISO 8601 string representation:
>>> Instant("2023-12-28T11Z")
Instant("2023-12-28 11:00:00Z")
>>> PlainDateTime("20231228T1130")
PlainDateTime("2023-12-28 11:30:00")
Below are the default ISO string formats produced by each type:
Type |
ISO 8601 string |
|---|---|
|
|
|
|
|
|
|
See the reference documentation for more details on formatting and parsing ISO 8601 strings.
RFC 2822¶
RFC 2822 is
another common format for representing datetimes, used in email headers and
HTTP headers. It looks like Tue, 13 Jul 2021 09:45:00 -0900: a weekday,
a date, a time, and a numeric offset.
format_rfc2822() and
parse_rfc2822() convert to and from
this format:
>>> d = OffsetDateTime(2023, 12, 28, 11, 30, offset=hours(5))
>>> d.format_rfc2822()
'Thu, 28 Dec 2023 11:30:00 +0500'
>>> OffsetDateTime.parse_rfc2822('Tue, 13 Jul 2021 09:45:00 -0900')
OffsetDateTime("2021-07-13 09:45:00-09:00")
format_rfc2822() writes the GMT form, which is also
the HTTP date (IMF-fixdate, RFC 9110), and
parse_rfc2822() applies the offset and gives the
UTC instant:
>>> Instant.from_utc(2023, 12, 28, 11, 30).format_rfc2822()
'Thu, 28 Dec 2023 11:30:00 GMT'
>>> Instant.parse_rfc2822('Tue, 13 Jul 2021 09:45:00 -0900')
Instant("2021-07-13 18:45:00Z")
Both parsers accept the RFC 2822 zone names UT and GMT and the North American
abbreviations EST through PDT; any other name, including a military
letter, is read as +0000, and comments in folding whitespace are rejected.
ZonedDateTime has neither method: the format carries a
numeric offset and no time zone ID, so a parse can never yield a
ZonedDateTime, and formatting one is to_fixed_offset() first.
PlainDateTime has no offset to write.
RFC 2822 only represents whole seconds and minute-precision offsets.
Formatting therefore discards nanoseconds and rounds any seconds in the
offset to the nearest minute, as the xx pattern does; use
ISO 8601 when those values must round-trip exactly.
Parsing reads the obsolete two- and three-digit years as RFC 5322 section 4.3
says: 00 to 49 are 2000 to 2049, 50 to 99 are 1950 to 1999, and a
three-digit year adds 1900.
Patterns¶
Date, Time, Instant,
OffsetDateTime, ZonedDateTime, and
PlainDateTime format and parse with patterns via
their format() and parse() methods—for example,
format() and
parse().
Patterns use specifiers like YYYY, MM, DD, HH, mm, ss.
>>> OffsetDateTime(2024, 3, 15, 14, 30, offset=hours(2)).format(
... "EEE, DD MMM YYYY HH:mm:ssxxx"
... )
'Fri, 15 Mar 2024 14:30:00+02:00'
>>> Date.parse("15 Mar 2024", pattern="DD MMM YYYY")
Date("2024-03-15")
>>> ZonedDateTime.parse(
... "2024-03-15 14:30+01:00[Europe/Paris]",
... pattern="YYYY-MM-DD HH:mmxxx'['VV']'",
... )
ZonedDateTime("2024-03-15 14:30:00+01:00[Europe/Paris]")
See the pattern format reference for the full list of specifiers and details.
Zoned parsing policies¶
The ZonedDateTime ISO-string constructor,
parse_iso(), and patterned
parse() accept disambiguation= and
offset_mismatch=. See
Resolving local times in a time zone for the complete decision flow, including
matching offsets, conflicts, Z, repeated and skipped local times, and
offset precision.
Pydantic integration¶
whenever types support serialization and deserialization with
Pydantic 2. Existing instances are preserved;
strings are validated with each type’s parse_iso() method and serialized as
its ISO 8601 string. Other input types and invalid strings raise
Pydantic’s ValidationError. The JSON schema of every whenever field is a
string.
>>> from pydantic import BaseModel
>>> from whenever import ZonedDateTime, TimeDelta
...
>>> class Event(BaseModel):
... start: ZonedDateTime
... duration: TimeDelta
...
>>> event = Event(
... start=ZonedDateTime(2023, 2, 23, hour=20, tz="Europe/Amsterdam"),
... duration=TimeDelta(hours=2, minutes=30),
... )
>>> d = event.model_dump_json()
'{"start":"2023-02-23T20:00:00+01:00[Europe/Amsterdam]","duration":"PT2H30M"}'
Note
Parsing is ISO 8601 only, stricter than Pydantic’s own datetime parsing.
In a union, Pydantic takes the first type that validates, so put the more
specific type first. Instant | ZonedDateTime reads
2023-02-23T20:00:00+01:00[Europe/Amsterdam] as an Instant,
which discards the time zone ID; ZonedDateTime | Instant keeps it.