ItemizedDelta

class whenever.ItemizedDelta(
*,
years: int = ...,
months: int = ...,
weeks: int = ...,
days: int = ...,
hours: int = ...,
minutes: int = ...,
seconds: int = ...,
nanoseconds: int = ...,
)

A delta that preserves the components it was given. It closely models the ISO 8601 duration format.

>>> d = ItemizedDelta(weeks=2, days=3, hours=14)
ItemizedDelta("P2w3dT14h")
>>> d = ItemizedDelta("P2w3dT14h")
>>> str(d)
'P2W3DT14H'

It behaves like a mapping where the keys are the unit names and the values are the amounts. Items are ordered from largest to smallest unit.

>>> d['weeks']
2
>>> print(d.get('minutes'))
None
>>> dict(d)
{"weeks": 2, "days": 3, "hours": 14}
>>> list(d.keys())
["weeks", "days", "hours"]
>>> weeks, days, hours = d.values()
(2, 3, 14)

ItemizedDelta also supports other dictionary-like operations:

>>> "months" in d  # check for presence of a component
False
>>> len(d)  # number of components present
3

An explicit zero is a present component, distinct from an absent one:

>>> d2 = ItemizedDelta(years=2, weeks=3, hours=0)
>>> dict(d2)
{"years": 2, "weeks": 3, "hours": 0}

Additionally, no normalization is performed. Months are not rolled into years, minutes into hours, etc.

>>> d3 = ItemizedDelta(months=24, minutes=90)
ItemizedDelta("P24mT90m")

Seconds and nanoseconds are one quantity in two components: nanoseconds is bounded to 999,999,999, and setting it also sets seconds. There are no millisecond or microsecond components; use total() for those units. See the subsecond rules.

An empty delta is not allowed: at least one component must be present, but it may be zero:

>>> ItemizedDelta()
ValueError: at least one component must be present
>>> ItemizedDelta(seconds=0)
ItemizedDelta("PT0s")

Negative deltas are supported, but all components must have the same sign:

>>> d4 = ItemizedDelta(years=-1, weeks=-2, days=0)
ItemizedDelta("-P1y2w0d")
>>> ItemizedDelta(years=1, days=-3)
ValueError: mixed sign in delta

Note

Unlike TimeDelta, ItemizedDelta does not normalize its components. This means that ItemizedDelta(hours=90) and ItemizedDelta(days=3, hours=18) are considered different values. To convert to a normalized form, use in_units(). See also the delta documentation.

classmethod parse_iso(s: str, /) → ItemizedDelta[source]

Parse the popular interpretation of the ISO 8601 duration format. Does not parse all possible ISO 8601 durations. See here for more information.

P4D        # 4 days
PT4H       # 4 hours
PT0M       # 0 minutes
PT3M40.5S  # 3 minutes and 40.5 seconds
P1W11DT90M # 1 week, 11 days, and 90 minutes
-PT7H400M  # -7 hours and -400 minutes
+PT7H4M    # 7 hours and 4 minutes (7:04:00)

Inverse of format_iso()

>>> ItemizedDelta.parse_iso("-P1W11DT4H")
ItemizedDelta("-P1w11dT4h")
__abs__() → ItemizedDelta[source]

If the components are negative, return the positive version

>>> d = ItemizedDelta(weeks=-2, days=-3)
>>> abs(d)
ItemizedDelta("P2w3d")
__add__(
other: ItemizedDelta | ItemizedDateDelta | ZonedDateTime | PlainDateTime | OffsetDateTime,
/,
) → ItemizedDelta | ZonedDateTime | PlainDateTime | OffsetDateTime[source]

Compose component-wise with another itemized delta, or shift a datetime by this delta.

>>> ItemizedDelta(hours=1) + ItemizedDelta(minutes=30)
ItemizedDelta("PT1h30m")
>>> ItemizedDelta(hours=1) + ZonedDateTime(2023, 1, 1, tz="UTC")
ZonedDateTime("2023-01-01 01:00:00+00:00[UTC]")

Nonzero years or months in either operand emit MonthCompositionWarning, which add() accepts with month_composition_ok=True.

__bool__() → bool[source]

An ItemizedDelta is considered False if its sign is 0.

>>> bool(ItemizedDelta(weeks=0))
False
>>> bool(ItemizedDelta(weeks=1))
True
__contains__(key: object, /) → bool[source]

Whether a specific component is present.

>>> d = ItemizedDelta(weeks=1, days=3)
>>> "weeks" in d
True
>>> "hours" in d
False
__eq__(other: object, /) → bool[source]

Compare for equality. Each component is individually compared. No normalization is performed. An explicit zero is equivalent to an absent component.

Thus, ItemizedDelta(weeks=1, seconds=0) == ItemizedDelta(weeks=1)

>>> d = ItemizedDelta(weeks=2, minutes=90)
>>> d == ItemizedDelta(weeks=2, minutes=90)
True
>>> d == ItemizedDelta(weeks=2, minutes=91)
False

An ItemizedDateDelta with the same components is equal:

>>> ItemizedDelta(weeks=2, hours=0) == ItemizedDateDelta(weeks=2)
True

If you want strict equality (including presence of components and the type), use strict_eq().

__getitem__(key: str, /) → int[source]

Get the value of a specific component by name.

>>> d = ItemizedDelta(weeks=1, days=3)
>>> d["weeks"]
1
>>> d["days"]
3
>>> d["hours"]
KeyError: 'hours'
__iter__() → Iterator[DeltaUnitStr][source]

Iterate over the present components, ordered from largest to smallest unit.

__len__() → int[source]

The number of present components.

>>> d = ItemizedDelta(weeks=1, days=3)
>>> len(d)
2
__neg__() → ItemizedDelta[source]

Invert the sign of the components

>>> d = ItemizedDelta(weeks=2, days=3)
>>> -d
ItemizedDelta("-P2w3d")
>>> --d
ItemizedDelta("P2w3d")
__str__() → str[source]

Return str(self).

__sub__(other: ItemizedDelta | ItemizedDateDelta, /) → ItemizedDelta[source]

Compose component-wise by subtracting another itemized delta.

>>> ItemizedDelta(hours=2, minutes=30) - ItemizedDelta(minutes=30)
ItemizedDelta("PT2h0m")

The same rules apply as for __add__().

add(
delta: ItemizedDelta | ItemizedDateDelta = ...,
/,
*,
years: int = ...,
months: int = ...,
weeks: int = ...,
days: int = ...,
hours: int = ...,
minutes: int = ...,
seconds: int = ...,
nanoseconds: int = ...,
month_composition_ok: bool = ...,
**kwargs: Any,
) → ItemizedDelta[source]

Add a delta to this one component-wise, returning a new delta.

Pass either a delta or its components as keywords, not both.

The result isn’t balanced: 1 hour plus 90 minutes is 1 hour 90 minutes, and 1 month plus 30 days is 1 month 30 days. To balance it, call in_units() on the result.

Deprecated since version 0.11: relative_to=, with in_units=, round_mode=, round_increment=, naive_arithmetic_ok=, and stale_offset_ok=. Call in_units() on the result instead. cal_unit_composition_ok= is now month_composition_ok=.

>>> ItemizedDelta(hours=1).add(minutes=30)
ItemizedDelta("PT1h30m")
>>> ItemizedDelta(months=1, hours=5).add(hours=3, month_composition_ok=True)
ItemizedDelta("P1mT8h")
Parameters:
  • delta – The delta to add, or else its components as keywords, named as in the constructor.

  • month_composition_ok – Accepts the MonthCompositionWarning that nonzero years or months in either operand emits.

date_and_time_parts() → tuple[ItemizedDateDelta | None, TimeDelta | None][source]

Split into the date part and the time part.

The date part is an ItemizedDateDelta; the time part is a normalized TimeDelta, since no itemized time-only type exists. A half with no component present is None, while a half given only as zero is a present zero. At least one half is present, since at least one component is.

>>> d = ItemizedDelta(
...     years=1,
...     months=2,
...     weeks=3,
...     days=4,
...     hours=5,
...     minutes=6,
...     seconds=7,
...     nanoseconds=8,
... )
>>> date_part, time_part = d.date_and_time_parts()
>>> date_part
ItemizedDateDelta("P1y2m3w4d")
>>> time_part
TimeDelta("PT5h6m7.000000008s")
>>> ItemizedDelta(weeks=2).date_and_time_parts()
(ItemizedDateDelta("P2w"), None)
exact_eq(other: ItemizedDelta, /) → bool[source]

Deprecated alias for strict_eq().

Deprecated since version 0.11: Use strict_eq() instead.

format_iso(*, lowercase_units: bool = False) → str[source]

Format as the popular interpretation of the ISO 8601 duration format. May not strictly adhere to (all versions of) the standard. See here for more information.

Inverse of parse_iso().

The format is:

P(nY)(nM)(nW)(nD)T(nH)(nM)(nS)
>>> d = ItemizedDelta(
...     weeks=1,
...     days=11,
...     hours=4,
...     seconds=1,
...     nanoseconds=12_000,
... )
>>> d.format_iso()
'P1W11DT4H1.000012S'
Parameters:

lowercase_units – Write the unit designators in lowercase, as repr() does; parse_iso() reads both cases.

get(key: DeltaUnitStr, /) → int | None[source]
get(key: DeltaUnitStr, default: int, /) → int

Get the value of a specific component by name, or return default if absent.

Part of the mapping protocol

in_units(
units: Sequence[DeltaUnitStr],
/,
*,
relative_to: ZonedDateTime | PlainDateTime | OffsetDateTime,
round_mode: RoundModeStr = 'trunc',
round_increment: int = 1,
naive_arithmetic_ok: bool = ...,
stale_offset_ok: bool = ...,
) → ItemizedDelta[source]

Convert this delta into the given units.

>>> d = ItemizedDelta(years=1, months=8, minutes=1000)
>>> d.in_units(["weeks", "hours"], relative_to=ZonedDateTime(2020, 6, 30, 12, tz="Asia/Tokyo"))
ItemizedDelta("P86wT160h")
Parameters:
items() → ItemsView[DeltaUnitStr, int][source]

The present components as (unit, value) pairs, ordered from largest to smallest unit.

>>> d = ItemizedDelta(years=3, hours=12, days=0)
>>> list(d.items())
[('years', 3), ('days', 0), ('hours', 12)]

Part of the mapping protocol

keys() → KeysView[DeltaUnitStr][source]

The names of the present components, in order of largest to smallest unit.

Part of the mapping protocol

replace(
*,
years: int | None = ...,
months: int | None = ...,
weeks: int | None = ...,
days: int | None = ...,
hours: int | None = ...,
minutes: int | None = ...,
seconds: int | None = ...,
nanoseconds: int | None = ...,
) → ItemizedDelta[source]

Create a new delta with the given components replaced

A component given as None is removed. Removing the last one raises ValueError, as does a mixed sign.

>>> d = ItemizedDelta(years=1, months=2, hours=3)
>>> d.replace(months=None, hours=2)
ItemizedDelta("P1yT2h")
sign() → Literal[1, 0, -1][source]

The sign of the delta, whether it’s positive, negative, or zero.

>>> ItemizedDelta(weeks=2).sign()
1
>>> ItemizedDelta(hours=-3).sign()
-1
>>> ItemizedDelta(weeks=0).sign()
0
strict_eq(other: ItemizedDelta, /) → bool[source]

Compare two deltas, including what == ignores.

== equates an ItemizedDelta with an ItemizedDateDelta of the same components, and ignores whether a component was given explicitly as zero. strict_eq() takes only an ItemizedDelta: another type raises TypeError.

>>> d = ItemizedDelta(weeks=2, hours=3)
>>> d == ItemizedDelta(weeks=2, hours=3, months=0)
True
>>> d.strict_eq(ItemizedDelta(weeks=2, hours=3, months=0))
False

See Strict equality for the rules on every type.

subtract(
delta: ItemizedDelta | ItemizedDateDelta = ...,
/,
*,
years: int = ...,
months: int = ...,
weeks: int = ...,
days: int = ...,
hours: int = ...,
minutes: int = ...,
seconds: int = ...,
nanoseconds: int = ...,
month_composition_ok: bool = ...,
**kwargs: Any,
) → ItemizedDelta[source]

Subtract a delta from this one component-wise, returning a new delta.

The inverse of add(), with the same parameters and rules.

>>> ItemizedDelta(months=1, hours=5).subtract(hours=3, month_composition_ok=True)
ItemizedDelta("P1mT2h")
total(
unit: DeltaTotalUnitStr,
/,
*,
relative_to: ZonedDateTime | PlainDateTime | OffsetDateTime,
naive_arithmetic_ok: bool = ...,
stale_offset_ok: bool = ...,
) → float[source]

The total duration in the given unit.

>>> d = ItemizedDelta(months=1, hours=6)
>>> d.total("days", relative_to=ZonedDateTime(2023, 1, 1, tz="UTC"))
31.25
>>> d.total("nanoseconds", relative_to=ZonedDateTime(2023, 1, 1, tz="UTC"))
2700000000000000
Parameters:
values() → ValuesView[int][source]

The values of the present components, in order of largest to smallest unit.

>>> d = ItemizedDelta(years=3, hours=12, days=0)
>>> years, days, hours = d.values()
(3, 0, 12)
>>> list(d.values())
[3, 0, 12]

Part of the mapping protocol