Skip to content

Commit

Permalink
add floordiv and mod operators to TimeDelta
Browse files Browse the repository at this point in the history
  • Loading branch information
ariebovenberg committed Feb 21, 2025
1 parent 2e2f8fa commit dc3ed42
Show file tree
Hide file tree
Showing 8 changed files with 233 additions and 318 deletions.
8 changes: 6 additions & 2 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
🚀 Changelog
============

0.7.0 (2025-02-??)
0.7.0 (2025-02-20)
------------------

This release adds rounding functionality,
along with a small breaking change (see below).

**Breaking changes**

- ``TimeDelta.py_timedelta()`` now truncates nanoseconds to microseconds
Expand All @@ -12,7 +15,8 @@

**Added**

- Added ``round()`` to time and ``TimeDelta`` classes
- Added ``round()`` to all datetime, ``Instant``, and ``TimeDelta`` classes
- Add floor division and modulo operators to ``TimeDelta``
- Add ``is_ambiguous()``, ``day_length()`` and ``start_of_day()`` to ``SystemDateTime``,
for consistency with ``ZonedDateTime``.
- Improvements to documentation
Expand Down
17 changes: 17 additions & 0 deletions docs/overview.rst
Original file line number Diff line number Diff line change
Expand Up @@ -650,6 +650,23 @@ Here is a summary of the arithmetic features for each type:
restrictions to get the result they want, **whenever** provides the
``ignore_dst`` option to at least make it explicit when this is happening.

Rounding
~~~~~~~~

It's often useful to truncate or round a datetime to a specific unit.
For example, you might want to round a datetime to the nearest hour,
or truncate it into 15-minute intervals.

The :class:`~whenever._KnowsLocal.round` method allows you to do this:

.. code-block:: python
>>> d = LocalDateTime(2023, 12, 28, 11, 30)
>>> d.round(hours=1)
LocalDateTime(2023-12-28 12:00:00)
>>> d.round(minutes=15)
LocalDateTime(2023-12-28 11:30:00)
Formatting and parsing
----------------------

Expand Down
25 changes: 22 additions & 3 deletions generate_docstrings.py
Original file line number Diff line number Diff line change
Expand Up @@ -149,10 +149,29 @@ def method_doc(method):
"\u0028cls", "\u0028$type"
)
)
# OPTIMIZE: for 0 and 1-argument function, we might not need to define
# the __text_signature__ manually, potentially saving space.
doc = method.__doc__.replace('"', '\\"')
return f"{method.__name__}{sig}\n--\n\n{doc}"
sig_prefix = f"{method.__name__}{sig}\n--\n\n"
return sig_prefix * _needs_text_signature(method) + doc


# In some basic cases, such as 0 or 1-argument functions, Python
# will automatically generate an adequate signature.
def _needs_text_signature(method):
sig = inspect.signature(method)
params = list(sig.parameters.values())
if len(params) == 0:
return False
if params[0].name in {"self", "cls"}:
params.pop(0)
if len(params) > 1:
return True
elif len(params) == 0:
return False
else:
return (
params[0].kind != inspect.Parameter.POSITIONAL_ONLY
or params[0].default is not inspect.Parameter.empty
)


def print_everything():
Expand Down
44 changes: 23 additions & 21 deletions pysrc/whenever/__init__.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -174,11 +174,11 @@ class Time:
"millisecond",
"microsecond",
"nanosecond",
] = ...,
increment: int = ...,
] = "second",
increment: int = 1,
mode: Literal[
"ceil", "floor", "half_ceil", "half_floor", "half_even"
] = ...,
] = "half_even",
) -> Time: ...
def __lt__(self, other: Time) -> bool: ...
def __le__(self, other: Time) -> bool: ...
Expand Down Expand Up @@ -224,11 +224,11 @@ class TimeDelta:
"millisecond",
"microsecond",
"nanosecond",
] = ...,
increment: int = ...,
] = "second",
increment: int = 1,
mode: Literal[
"ceil", "floor", "half_ceil", "half_floor", "half_even"
] = ...,
] = "half_even",
) -> TimeDelta: ...
def __hash__(self) -> int: ...
def __lt__(self, other: TimeDelta) -> bool: ...
Expand All @@ -246,6 +246,8 @@ class TimeDelta:
def __truediv__(self, other: float) -> TimeDelta: ...
@overload
def __truediv__(self, other: TimeDelta) -> float: ...
def __floordiv__(self, other: TimeDelta) -> int: ...
def __mod__(self, other: TimeDelta) -> TimeDelta: ...
def __abs__(self) -> TimeDelta: ...

@final
Expand Down Expand Up @@ -443,11 +445,11 @@ class Instant(_KnowsInstant):
"millisecond",
"microsecond",
"nanosecond",
] = ...,
increment: int = ...,
] = "second",
increment: int = 1,
mode: Literal[
"ceil", "floor", "half_ceil", "half_floor", "half_even"
] = ...,
] = "half_even",
) -> Instant: ...
def __add__(self, delta: TimeDelta) -> Instant: ...
@overload
Expand Down Expand Up @@ -574,11 +576,11 @@ class OffsetDateTime(_KnowsInstantAndLocal):
"millisecond",
"microsecond",
"nanosecond",
] = ...,
increment: int = ...,
] = "second",
increment: int = 1,
mode: Literal[
"ceil", "floor", "half_ceil", "half_floor", "half_even"
] = ...,
] = "half_even",
*,
ignore_dst: Literal[True],
) -> OffsetDateTime: ...
Expand Down Expand Up @@ -734,11 +736,11 @@ class ZonedDateTime(_KnowsInstantAndLocal):
"millisecond",
"microsecond",
"nanosecond",
] = ...,
increment: int = ...,
] = "second",
increment: int = 1,
mode: Literal[
"ceil", "floor", "half_ceil", "half_floor", "half_even"
] = ...,
] = "half_even",
) -> ZonedDateTime: ...
# FUTURE: disable date components in strict stubs version
def __add__(self, delta: Delta) -> ZonedDateTime: ...
Expand Down Expand Up @@ -891,11 +893,11 @@ class SystemDateTime(_KnowsInstantAndLocal):
"millisecond",
"microsecond",
"nanosecond",
] = ...,
increment: int = ...,
] = "second",
increment: int = 1,
mode: Literal[
"ceil", "floor", "half_ceil", "half_floor", "half_even"
] = ...,
] = "half_even",
) -> SystemDateTime: ...
# FUTURE: disable date components in strict stubs version
def __add__(self, delta: Delta) -> SystemDateTime: ...
Expand Down Expand Up @@ -1021,11 +1023,11 @@ class LocalDateTime(_KnowsLocal):
"millisecond",
"microsecond",
"nanosecond",
] = ...,
increment: int = ...,
] = "second",
increment: int = 1,
mode: Literal[
"ceil", "floor", "half_ceil", "half_floor", "half_even"
] = ...,
] = "half_even",
) -> LocalDateTime: ...
def __add__(self, delta: DateDelta) -> LocalDateTime: ...
def __sub__(self, other: DateDelta) -> LocalDateTime: ...
Expand Down
31 changes: 31 additions & 0 deletions pysrc/whenever/_pywhenever.py
Original file line number Diff line number Diff line change
Expand Up @@ -1682,13 +1682,44 @@ def __truediv__(self, other: float | TimeDelta) -> TimeDelta | float:
TimeDelta(00:36:00)
>>> d / TimeDelta(minutes=30)
3.0
Note
----
Because TimeDelta is limited to nanosecond precision, the result of
division may not be exact.
"""
if isinstance(other, TimeDelta):
return self._total_ns / other._total_ns
elif isinstance(other, (int, float)):
return TimeDelta(nanoseconds=int(self._total_ns / other))
return NotImplemented

def __floordiv__(self, other: TimeDelta) -> int:
"""Floor division by another delta
Example
-------
>>> d = TimeDelta(hours=1, minutes=39)
>>> d // time_delta(minutes=15)
6
"""
if not isinstance(other, TimeDelta):
return NotImplemented
return self._total_ns // other._total_ns

def __mod__(self, other: TimeDelta) -> TimeDelta:
"""Modulo by another delta
Example
-------
>>> d = TimeDelta(hours=1, minutes=39)
>>> d % TimeDelta(minutes=15)
TimeDelta(00:09:00)
"""
if not isinstance(other, TimeDelta):
return NotImplemented
return TimeDelta(nanoseconds=self._total_ns % other._total_ns)

def __abs__(self) -> TimeDelta:
"""The absolute value
Expand Down
Loading

0 comments on commit dc3ed42

Please sign in to comment.