Creating periods#
Every way to build a Period, and what each one means.
Period is abstract - you never instantiate it directly. Instead, a set of static factory
methods are available to build a period in various ways. All of them return a fully-formed period.
From a named unit#
The most direct route. Each factory takes a count of that unit:
Factory |
Example |
Intervals start at |
|---|---|---|
|
00:00 on 1 January |
|
|
00:00 on the 1st |
|
|
midnight |
|
|
the top of the hour |
|
|
the top of the minute |
|
|
a whole second |
|
|
a whole microsecond |
The count must be greater than zero; anything else raises
PeriodValidationError.
From a string#
of() accepts three supported string formats and tries each in turn:
1. Plain ISO 8601 duration#
The ISO 8601 duration form: P, then a date part, then
T and a time part. Parsing is case-insensitive.
p1y = Period.of("P1Y") # 1 year
p3m = Period.of("P3M") # 3 months (a quarter)
p1d = Period.of("P1D") # 1 day
pt15m = Period.of("PT15M") # 15 minutes
pt25hz = Period.of("PT0.04S") # 40 ms - 25 Hz sampling
Components combine, and the period renders back as the string that built it:
p1y6m = Period.of("P1Y6M")
p1dt12h = Period.of("P1DT12H")
pt1h30m = Period.of("PT1H30M")
Warning
A period cannot mix calendar units (years, months) with clock units (days and below), because months have no
fixed length in seconds. "P1M1D" raises PeriodValidationError.
Note
of_iso_duration() parses this form and only this form - useful when you want to reject the
extended syntaxes explicitly.
2. Duration with an offset#
An extension of the ISO 8601 format, written <duration>+<offset>. The offset is itself a duration, and shifts
every interval boundary forwards by that amount:
Period.of("P1D+T9H") # Hydrological day: 09:00am - 09:00am
Period.of("PT15M+T5M") # Records measured at :05, :20, :35, :50
Period.of("P1Y+9MT9H") # UK Hydrological Year
There are also with_*_offset methods that apply an offset to an existing period (returning a new independent Period
object):
Period.of("P1D").with_hour_offset(9) # Hydrological day: 09:00am - 09:00am
Period.of("PT15M").with_minute_offset(5) # Records measured at :05, :20, :35, :50
Period.of("P1Y").with_month_offset(9).with_hour_offset(9) # UK Hydrological Year
Note
of_duration() accepts both this offset form and a plain duration (with no offset).
3. Duration with an origin#
Written <start>/<duration>. The start datetime becomes ordinal 0 and a boundary of the period, which
fixes the grid for periods that have no meaningful natural boundary:
p7d = Period.of("2024-01-01/P7D")
print(p7d.ordinal(datetime(2024, 1, 1)))
print(p7d.is_aligned(datetime(2024, 1, 1)))
print(p7d.datetime(1))
0
True
2024-01-08 00:00:00
The start may be a full datetime, or a reduced-precision date - a bare year or year-month is padded to its first instant, following ISO 8601:
print(Period.of("2024/P1Y"))
print(Period.of("2024-10/P1M"))
print(Period.of("1883-01-01T09:00/P1D"))
2024-01-01/P1Y
2024-10-01/P1M
1883-01-01T09:00:00/P1D
Note
of_date_and_duration() parses this form only.
From a timedelta#
of_timedelta() builds a period matching any fixed-length duration:
print(Period.of_timedelta(timedelta(minutes=30)))
print(Period.of_timedelta(timedelta(milliseconds=20)))
print(Period.of_timedelta(timedelta(days=1, hours=12)))
PT30M
PT0.02S
P1DT12H
There is no equivalent for months and years - a timedelta cannot represent them.
Handling bad input#
Two exceptions are raised when a period cannot be built, both subclasses of PeriodError:
PeriodParsingErrorThe string does not match any supported format at all.
PeriodValidationErrorThe string parsed, but does not describe a usable period - a zero-length duration, or one mixing calendar and clock units.
Catch PeriodError to handle both:
def parse_resolution(text: str) -> Period | None:
try:
return Period.of(text)
except PeriodError as err:
print(f"{text!r}: {type(err).__name__} - {err}")
return None
parse_resolution("PT15M")
parse_resolution("every 15 mins") # no supported format matches
parse_resolution("P1M1D") # parses, but mixes calendar and clock units
'every 15 mins': PeriodParsingError - Illegal period: every 15 mins
'P1M1D': PeriodValidationError - Illegal period: P1M1D