Skip to content

Commit c3e6ef2

Browse files
committed
Implement basic ItemizedDelta
1 parent e369a81 commit c3e6ef2

File tree

10 files changed

+1240
-73
lines changed

10 files changed

+1240
-73
lines changed

docs/api.rst

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -102,18 +102,6 @@ Concrete classes
102102
Deltas
103103
------
104104

105-
.. autofunction:: whenever.years
106-
.. autofunction:: whenever.months
107-
.. autofunction:: whenever.weeks
108-
.. autofunction:: whenever.days
109-
110-
.. autofunction:: whenever.hours
111-
.. autofunction:: whenever.minutes
112-
.. autofunction:: whenever.seconds
113-
.. autofunction:: whenever.milliseconds
114-
.. autofunction:: whenever.microseconds
115-
.. autofunction:: whenever.nanoseconds
116-
117105
.. autoclass:: whenever.TimeDelta
118106
:members:
119107
:special-members: __eq__, __neg__, __add__, __sub__, __mul__, __truediv__, __bool__, __abs__, __gt__
@@ -132,6 +120,11 @@ Deltas
132120
:special-members: __eq__, __neg__, __abs__, __add__, __sub__, __bool__, __mul__
133121
:member-order: bysource
134122

123+
.. autoclass:: whenever.ItemizedDelta
124+
:members:
125+
:special-members: __eq__, __neg__, __abs__, __add__, __sub__, __bool__, __mul__
126+
:member-order: bysource
127+
135128
.. _date-and-time-api:
136129

137130
Date and time components

docs/deltas.rst

Lines changed: 36 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -13,37 +13,48 @@ For example, you might want to reuse a particular duration,
1313
or perform arithmetic on it.
1414
For this, **whenever** provides an API
1515
designed to help you avoid common pitfalls.
16-
The type annotations and descriptive errors should guide you
17-
to the correct usage.
1816

19-
Durations are created using the duration units provided.
20-
Here is a quick demo:
17+
There are two main kind of deltas:
18+
19+
- :class:`~whenever.TimeDelta` for precise time durations
20+
(hours, minutes, seconds, microseconds). This type supports mathematical
21+
operations like addition, subtraction, multiplication, and division.
22+
Arithmetic on time units is straightforward.
23+
It behaves similarly to the :class:`~datetime.timedelta`
24+
of the standard library.
25+
The drawback is that `TimeDelta` doesn't support calender units
26+
like months or years, as their duration varies depending on context.
27+
That's where the second type comes in:
28+
- :class:`~whenever.ItemizedDelta` for calendar-based durations.
29+
They don't have a precise duration, as this depends on the context.
30+
For example, the number of days in a month varies, and a day may be
31+
longer or shorter than 24 hours due to Daylight Saving Time.
32+
This makes arithmetic on calendar units less intuitive.
33+
In general, arithmetic on these delta's on their own is not supported,
34+
a local date/time is needed to provide context.
2135

22-
>>> from whenever import years, months, days, hours, minutes
23-
>>> # Precise units create a TimeDelta, supporting broad arithmetic
24-
>>> movie_runtime = hours(2) + minutes(9)
25-
TimeDelta(02:09:00)
36+
.. note::
37+
38+
There also is a third type, :class:`~whenever.ItemizedDateDelta`.
39+
It behaves similarly to `ItemizedDelta`, but contains *only* calendar units.
40+
41+
>>> movie_runtime = TimeDelta(hours=2, minutes=9)
42+
TimeDelta("PT2h9m")
2643
>>> movie_runtime.in_minutes()
2744
129.0
2845
>>> movie_runtime / 1.2 # what if we watch it at 1.2x speed?
29-
TimeDelta(01:47:30)
30-
...
31-
>>> # Calendar units create a DateDelta, with more limited arithmetic
32-
>>> project_estimate = months(1) + days(10)
33-
DateDelta("P1M10D")
34-
>>> Date(2023, 1, 29) + project_estimate
35-
Date("2023-03-10")
36-
>>> project_estimate * 2 # make it pessimistic
37-
DateDelta("P2M20D")
38-
...
39-
>>> # Mixing date and time units creates a generic DateTimeDelta
40-
>>> project_estimate + movie_runtime
41-
DateTimeDelta("P1M10DT2H9M")
42-
...
46+
TimeDelta("Pt1h47m30s")
47+
>>> Instant.now() + movie_runtime # when does it end?
48+
49+
50+
>>> # If needed, composite, unnormalized durations can be created:
51+
>>> room_occupied = ItemizedDelta(months=1, days=10, minutes=90)
52+
ItemizedDelta("P1m10dT90m")
53+
4354
>>> # API ensures common mistakes are caught early:
44-
>>> project_estimate * 1.3 # Impossible arithmetic on calendar units
45-
>>> project_estimate.in_hours() # Resolving calendar units without context
46-
>>> Date(2023, 1, 29) + movie_runtime # Adding time to a date
55+
>>> room_occupied * 1.3 # Impossible arithmetic on calendar units
56+
>>> room_occupied.in_hours() # Resolving calendar units without context
57+
>>> Date(2023, 1, 29) + room_occupied # Adding time to a date
4758

4859
Types of deltas
4960
---------------
@@ -53,18 +64,10 @@ There are three duration types in **whenever**:
5364
- :class:`~whenever.TimeDelta`, created by precise units
5465
:func:`~whenever.hours`, :func:`~whenever.minutes`, :func:`~whenever.seconds`,
5566
and :func:`~whenever.microseconds`.
56-
Their duration is always the same and independent of the calendar.
57-
Arithmetic on time units is straightforward.
58-
It behaves similarly to the :class:`~datetime.timedelta`
59-
of the standard library.
6067

6168
- :class:`~whenever.DateDelta`, created by the calendar units
6269
:func:`~whenever.years`, :func:`~whenever.months`, :func:`~whenever.weeks`,
6370
and :func:`~whenever.days`.
64-
They don't have a precise duration, as this depends on the context.
65-
For example, the number of days in a month varies, and a day may be
66-
longer or shorter than 24 hours due to Daylight Saving Time.
67-
This makes arithmetic on calendar units less intuitive.
6871

6972
- :class:`~whenever.DateTimeDelta`, created when you mix
7073
time and calendar units.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ maintainers = [
77
{name = "Arie Bovenberg", email = "a.c.bovenberg@gmail.com"},
88
]
99
readme = "README.md"
10-
version = "0.9.4"
10+
version = "0.10.0"
1111
license = "MIT"
1212
description = "Modern datetime library for Python"
1313
requires-python = ">=3.9"

pysrc/whenever/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232

3333
# Yes, we could get the version with importlib.metadata,
3434
# but we try to keep our import time as low as possible.
35-
__version__ = "0.9.4"
35+
__version__ = "0.10.0"
3636

3737
reset_tzpath() # populate the tzpath once at startup
3838

pysrc/whenever/__init__.pyi

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ from typing import (
1414
Iterable,
1515
Literal,
1616
TypeAlias,
17+
TypedDict,
1718
final,
1819
overload,
1920
type_check_only,
@@ -253,6 +254,38 @@ class TimeDelta(_DeltaMixin, _OrderMixin):
253254
def in_microseconds(self) -> float: ...
254255
def in_nanoseconds(self) -> int: ...
255256
def in_hrs_mins_secs_nanos(self) -> tuple[int, int, int, int]: ...
257+
def in_units(
258+
self,
259+
*units: Literal[
260+
"days",
261+
"hours",
262+
"minutes",
263+
"seconds",
264+
"nanoseconds",
265+
],
266+
round_mode: Literal[
267+
"ceil", "floor", "half_ceil", "half_floor", "half_even"
268+
] = ...,
269+
round_increment: int = ...,
270+
round_unit: Literal[
271+
"day",
272+
"hour",
273+
"minute",
274+
"second",
275+
"millisecond",
276+
"microsecond",
277+
"nanosecond",
278+
] = ...,
279+
) -> float: ...
280+
def itemize(
281+
self,
282+
*,
283+
units: tuple[str, ...],
284+
round_mode: Literal[
285+
"ceil", "floor", "half_ceil", "half_floor", "half_even"
286+
] = ...,
287+
round_increment: int = ...,
288+
) -> ItemizedDelta: ...
256289
def py_timedelta(self) -> _timedelta: ...
257290
@classmethod
258291
def from_py_timedelta(cls, td: _timedelta, /) -> Self: ...
@@ -305,6 +338,53 @@ class DateDelta(_DeltaMixin):
305338
def __sub__(self, other: TimeDelta, /) -> DateTimeDelta: ...
306339
def __rsub__(self, other: TimeDelta, /) -> DateTimeDelta: ...
307340

341+
class DeltaDict(TypedDict, total=False):
342+
years: int
343+
months: int
344+
weeks: int
345+
days: int
346+
hours: int
347+
minutes: int
348+
seconds: int
349+
nanoseconds: int
350+
351+
@final
352+
class ItemizedDelta:
353+
def __init__(
354+
self,
355+
*,
356+
years: int = ...,
357+
months: int = ...,
358+
weeks: int = ...,
359+
days: int = ...,
360+
hours: int = ...,
361+
minutes: int = ...,
362+
seconds: int = ...,
363+
nanoseconds: int = ...,
364+
) -> None: ...
365+
@property
366+
def sign(self) -> Literal[1, 0, -1]: ...
367+
@property
368+
def years(self) -> int: ...
369+
@property
370+
def months(self) -> int: ...
371+
@property
372+
def weeks(self) -> int: ...
373+
@property
374+
def days(self) -> int: ...
375+
@property
376+
def hours(self) -> int: ...
377+
@property
378+
def minutes(self) -> int: ...
379+
@property
380+
def seconds(self) -> int: ...
381+
def float_seconds(self) -> float: ...
382+
def values(self) -> tuple[int, ...]: ...
383+
def as_dict(self) -> DeltaDict: ...
384+
def format_iso(self, *, lowercase_units: bool = False) -> str: ...
385+
@classmethod
386+
def parse_iso(cls, s: str, /) -> Self: ...
387+
308388
@final
309389
class DateTimeDelta(_DeltaMixin):
310390
@overload

pysrc/whenever/_core.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
_unpkl_tdelta,
4949
_unpkl_time,
5050
_unpkl_utc,
51+
_unpkl_vdelta,
5152
_unpkl_ym,
5253
_unpkl_zoned,
5354
)

0 commit comments

Comments
 (0)