--- myst: html_meta: description: >- Conceptual guide to whenever's three delta types: normalized versus itemized durations, why calendar units need context, and how to balance units. --- (guide-deltas)= # Working with deltas ```{eval-rst} .. currentmodule:: whenever ``` This page gives a conceptual overview of the delta types in `whenever`. For the full API reference, see {ref}`durations`. ## Three types for three use cases `whenever` provides three delta types because durations have fundamentally different arithmetic rules depending on the units involved (see the {ref}`FAQ ` for the reasoning): | Type | Units | When to use | |---|---|---| | {class}`TimeDelta` | hours, minutes, seconds, … | Measuring exact elapsed time | | {class}`ItemizedDateDelta` | years, months, weeks, days | Calendar arithmetic (e.g. "3 months from now") | | {class}`ItemizedDelta` | all of the above | Display, ISO 8601 round-tripping, mixed durations | Most of the time you won't create delta objects directly—you'll use `add()`, `subtract()`, `since()`, and `until()` on datetime and date objects. But deltas become useful when you need to *reuse* a duration, pass it around, or inspect its components. ## Normalized vs. itemized {class}`TimeDelta` **normalizes** its components: `90 minutes` automatically becomes `1 hour 30 minutes`. This makes comparison and arithmetic straightforward. {class}`ItemizedDateDelta` and {class}`ItemizedDelta` keep their components **itemized**: `1 month` stays `1 month`, not `30 days`. This is essential because calendar units have variable lengths—a month can be 28, 29, 30, or 31 days depending on when you start. ```python >>> TimeDelta(hours=1, minutes=90) TimeDelta("PT2h30m") # normalized: 2 hours 30 minutes >>> ItemizedDelta(hours=1, minutes=90) ItemizedDelta("PT1h90m") # itemized: components kept as-is ``` ## Calendar units need context Calendar units are not fixed durations. `1 month` may be 28, 29, 30, or 31 days, and applying it can clamp at month end. As a result, calendar units need a **reference date** for operations that convert them to other units or combine them in a calendar-aware way. ```python >>> d = ItemizedDateDelta(months=1) >>> d.total("days", relative_to=Date(2024, 1, 15)) # January → February 31 >>> d.total("days", relative_to=Date(2024, 2, 15)) # February → March 29 # 2024 is a leap year ``` The same applies to {meth}`~ItemizedDateDelta.in_units`, {meth}`~ItemizedDateDelta.add`, and {meth}`~ItemizedDateDelta.subtract` when calendar units are involved. The same rule also means that calendar units do not reliably compose. Adding `1 month` twice can differ from adding `2 months` once, because the first step may change the reference date for the second step. When you call `add()` or `subtract()` on itemized deltas **without** a `relative_to` reference, the operation is field-wise and emits {class}`~whenever.CalendarUnitCompositionWarning` when nonzero calendar units are involved. Exact-only composition does not warn. Field-wise composition is literal and sometimes useful, but it should not be confused with sequential application to a date or datetime. For example, month-end clamping makes the two operations differ: ```python >>> one_month = ItemizedDateDelta(months=1) >>> start = Date("2023-01-31") >>> start + one_month + one_month Date("2023-03-28") >>> # Summing fieldwise first applies two months in a single step >>> summed = one_month + one_month # P2M >>> start + summed Date("2023-03-31") ``` ## Balancing into different units "Balancing" means redistributing a delta's value across a new set of units. Use `in_units()`: ```python >>> td = TimeDelta(minutes=150) >>> td.in_units(["hours", "minutes"]).values() (2, 30) ``` For itemized deltas with calendar units, balancing requires a reference date: ```python >>> d = ItemizedDateDelta(days=400) >>> d.in_units(["years", "months", "days"], relative_to=Date(2024, 1, 1)).values() (1, 1, 3) ``` ## Sign All deltas carry a single sign that applies to every component. There are no mixed-sign deltas: ```python >>> -ItemizedDateDelta(years=1, months=6) ItemizedDateDelta("-P1y6m") ``` See {ref}`delta-sign` for more details. ## Next steps - {ref}`arithmetic` — adding and subtracting time from datetimes - {ref}`durations` — full API reference for all three delta types