Pattern format¶
Custom format and parse patterns allow you to format datetime values into strings and parse strings into datetime values, using a pattern string that describes the expected format.
Quick example¶
>>> from whenever import Date, Time, OffsetDateTime, hours
>>> Date(2024, 3, 15).format("YYYY/MM/DD")
'2024/03/15'
>>> Date.parse("2024/03/15", format="YYYY/MM/DD")
Date("2024-03-15")
>>> OffsetDateTime(2024, 3, 15, 14, 30, offset=+2).format(
... "ddd, DD MMM YYYY hh:mm:ssxxx"
... )
'Fri, 15 Mar 2024 14:30:00+02:00'
Specifiers¶
Each pattern is a string containing specifiers and literal text. Specifiers are sequences of the same letter that are replaced by the corresponding value.
Date specifiers¶
Pattern |
Meaning |
Example output |
Parse support |
|---|---|---|---|
|
4-digit year, zero-padded |
|
✅ |
|
2-digit year |
|
❌ format only |
|
Month number (01–12), zero-padded |
|
✅ (exactly 2 digits) |
|
Month number (1–12), no padding |
|
✅ (1–2 digits) |
|
Abbreviated month name |
|
✅ (case-insensitive) |
|
Full month name |
|
✅ (case-insensitive) |
|
Day of month (01–31), zero-padded |
|
✅ (exactly 2 digits) |
|
Day of month (1–31), no padding |
|
✅ (1–2 digits) |
|
Abbreviated weekday name |
|
✅ (validated) |
|
Full weekday name |
|
✅ (validated) |
Note
When parsing, weekday names (ddd/dddd) are validated against the
parsed date. A mismatch raises ValueError.
Note
YY is only supported for formatting. When parsing, use YYYY to
avoid ambiguity.
Time specifiers¶
Pattern |
Meaning |
Example output |
Parse support |
|---|---|---|---|
|
24-hour (00–23), zero-padded |
|
✅ (exactly 2 digits) |
|
24-hour (0–23), no padding |
|
✅ (1–2 digits) |
|
12-hour (01–12), zero-padded |
|
⚠️ (should pair with |
|
12-hour (1–12), no padding |
|
⚠️ (should pair with |
|
Minute (00–59), zero-padded |
|
✅ (exactly 2 digits) |
|
Minute (0–59), no padding |
|
✅ (1–2 digits) |
|
Second (00–59), zero-padded |
|
✅ (exactly 2 digits) |
|
Second (0–59), no padding |
|
✅ (1–2 digits) |
|
Second, optional (see below) |
|
✅ |
|
Fractional seconds, exact digits |
|
✅ |
|
Fractional seconds, trimmed |
|
✅ |
|
AM/PM first character |
|
✅ |
|
AM/PM full |
|
✅ |
Important
hh/hare the 24-hour formats.ii/iare the 12-hour formats.Using
h/hhwitha/aa(AM/PM) raises an error—usei/iiinstead.Using
i/iiwithouta/aaemits a warning about ambiguity.The double-letter forms (
hh,ii,mm,ss) always zero-pad and require exactly 2 digits when parsing. The single-letter forms (h,i,m,s) skip zero-padding and accept 1–2 digits when parsing.
Optional seconds (SS):
SS omits the seconds component entirely when both seconds and
nanoseconds are zero, allowing compact times like 14:30 alongside full
times like 14:30:05 in the same format string.
When seconds or nanoseconds are non-zero,
SSwrites two zero-padded digits;:SSadditionally writes the preceding colon.When both are zero, nothing is written. The colon disappears with
:SS(unlike a bare:literal, which would always be written).During parsing,
SSreads two digits if the next character is a digit;:SSreads:followed by two digits if the next character is:. In both cases, if the expected character is absent, seconds is set to zero.
Important
Omission requires both seconds and nanoseconds to be zero. If you have
fractional seconds (e.g. 14:30:00.5), SS will write 00 (not omit).
If you want compact times and don’t need fractional seconds, call
round() first.
>>> Time(14, 30, 0).format("hh:mm:SS")
'14:30'
>>> Time(14, 30, 5).format("hh:mm:SS")
'14:30:05'
>>> Time(14, 30, 0, nanosecond=500_000_000).format("hh:mm:SS")
'14:30:00'
>>> Time(14, 30, 0).format("hh:mm:SS.FFF")
'14:30'
>>> Time(14, 30, 0, nanosecond=500_000_000).format("hh:mm:SS.FFF")
'14:30:00.5'
Fractional seconds:
fspecifies the exact number of digits.fffalways writes 3 digits.Fspecifies the maximum digits, with trailing zeros trimmed.FFFwrites 1–3 digits, or nothing if the value is zero (and also trims a preceding.).
>>> Time(14, 30, 5, nanosecond=120_000_000).format("hh:mm:ss.fff")
'14:30:05.120'
>>> Time(14, 30, 5, nanosecond=120_000_000).format("hh:mm:ss.FFF")
'14:30:05.12'
>>> Time(14, 30, 5).format("hh:mm:ss.FFF")
'14:30:05'
Offset and timezone specifiers¶
See Timezones for background on timezones, offsets, and abbreviations.
Offset specifiers (x/X):
Pattern |
Meaning |
Example output |
Parse support |
|---|---|---|---|
|
Offset hours only |
|
✅ |
|
Offset hours+minutes, compact |
|
✅ |
|
Offset hours:minutes |
|
✅ |
|
Compact, optional seconds |
|
✅ |
|
With colons, optional seconds |
|
✅ |
|
Same as |
|
✅ |
Lowercase x always produces a numeric offset.
Uppercase X substitutes Z when the offset is exactly zero.
For widths 4 and 5, seconds are only displayed when non-zero.
Timezone specifiers:
Pattern |
Meaning |
Example output |
Parse support |
|---|---|---|---|
|
IANA timezone ID |
|
✅ |
|
Timezone abbreviation |
|
❌ format only |
Choosing between x and X
Use uppercase X when you want Z for zero offset
(e.g. Instant formatting).
Use lowercase x when you always want a numeric offset
(e.g. OffsetDateTime formatting).
Both accept Z when parsing with uppercase X.
Note
zz (timezone abbreviation) is only supported for formatting—abbreviations
are ambiguous and cannot be reliably used for parsing. Use VV (IANA timezone
ID) instead. See Timezones for why abbreviations are unreliable.
>>> ZonedDateTime(2024, 7, 15, 14, 30, tz="Europe/Paris").format(
... "YYYY-MM-DD hh:mm zz"
... )
'2024-07-15 14:30 CEST'
>>> ZonedDateTime.parse(
... "2024-07-15 14:30+02:00[Europe/Paris]",
... format="YYYY-MM-DD hh:mmxxx'['VV']'",
... )
ZonedDateTime("2024-07-15 14:30:00+02:00[Europe/Paris]")
Supported specifiers per type¶
Type |
Date |
Time |
|
|
|---|---|---|---|---|
✅ |
❌ |
❌ |
❌ |
|
❌ |
✅ |
❌ |
❌ |
|
✅ |
✅ |
❌ |
❌ |
|
✅ |
✅ |
✅ |
❌ |
|
✅ |
✅ |
✅ |
✅ |
|
✅ |
✅ |
✅ |
❌ |
Literal text¶
Common non-letter characters (:, -, /, ., ,, ;,
_, (, ), digits, spaces, and other ASCII
punctuation) are treated as literals by default:
>>> Date(2024, 3, 15).format("YYYY/MM/DD")
'2024/03/15'
Letters must be quoted with single quotes to be used as literals. This prevents accidental use of reserved characters and keeps options open for future specifiers:
>>> Date(2024, 3, 15).format("YYYY'xx'MM")
'2024xx03'
To include a literal single quote, use '':
>>> Date(2024, 3, 15).format("YYYY''MM")
"2024'03"
Restrictions¶
ASCII-only: Pattern strings must contain only ASCII characters. Non-ASCII characters raise
ValueError.Reserved characters:
<,>,[,],{,}, and#are reserved for future use and cannot appear unquoted.No duplicate fields: A pattern cannot contain two specifiers that set the same value. For example,
MMandMMMboth set the month, so"DD MM MMM YYYY"is invalid.
Parsing requirements¶
Some types require specific fields in the parse pattern:
OffsetDateTime.parse()requires an offset (x/X)ZonedDateTime.parse()requiresVV(timezone ID). An offset (x/X) is optional but recommended for DST disambiguation.Instant.parse()requires an offset (x/X)
All types that include date fields require YYYY, MM, and DD.
Comparison with strftime¶
The parse_strptime() methods on OffsetDateTime and
PlainDateTime are deprecated in favor of
parse(). Here’s a migration guide:
strftime |
Pattern |
Notes |
|---|---|---|
|
|
|
|
|
Format only |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Note: |
|
|
Note: |
|
|
|
|
|
|
|
|
microseconds (6 digits) |
|
|
|
|
|
|
|
|
|
|
— |
Abbreviations are not supported for parsing. See Timezones. |