"""The delta module."""
from datetime import timedelta
from functools import wraps
from babel.core import default_locale
from . import parser
def _asdelta(func):
"""
Simple decorator to convert return from timedelta.__<math>__ methods to Delta object. This is
primarily needed because one cannot simply override the existing timedelta method due to the way
the class is implemented. Doing so results in slotted attributes not being available to the
subclassed methods. Therefore, we need to convert the returned timedelta to Delta instead.
Note:
We could reimplement all of the arithmetic magic methods but prefer not to. However, we do
end up creating timedelta objects twice (one from the timedelta result, another when we
create a new Delta) so there would be some performance gains from doing so though.
"""
# NOTE: We're setting assigned because in Python 2.7, @wraps fails for certain timedelta magic
# methods due to certain attributes missing from the timedelta class that @wraps looks for by
# default.
@wraps(func, assigned=("__name__", "__doc__"))
def decorated(*args, **kwargs):
result = func(*args, **kwargs)
if isinstance(result, timedelta):
return Delta.fromtimedelta(result)
elif isinstance(result, tuple): # pragma: no cover
# This handles __divmod__ return.
return tuple(
Delta.fromtimedelta(item) if isinstance(item, timedelta) else item
for item in result
)
else: # pragma: no cover
return result
return decorated
def get_locale(locale=None, default="en_US_POSIX"):
"""Return default locale to use if one is not provided."""
if not locale:
locale = default_locale("LC_TIME")
if not locale:
locale = default
return locale
[docs]
class Delta(timedelta):
"""An extension of ``datetime.timedelta`` that provides additional functionality."""
[docs]
@classmethod
def parse(cls, obj):
"""
Return :class:`.Delta` object parsed from `obj`.
Args:
obj (str|number|timedelta): Object to parse into a :class:`.Delta` object.
Returns:
:class:`.Delta`
"""
return cls.fromtimedelta(parser.parse_timedelta(obj))
[docs]
@classmethod
def fromtimedelta(cls, delta):
"""
Return :class:`.Delta` object from a native timedelta object.
Returns:
:class:`.Delta`
"""
return cls(seconds=delta.total_seconds())
def __float__(self):
"""Return class as float which returns the same as :meth:`total_seconds`."""
return self.total_seconds()
def __int__(self):
"""Return class as integer which returns the integer part of :meth:`total_seconds`."""
return int(float(self))
def __repr__(self): # pragma: no cover
"""Return representation of :class:`.Delta`."""
return f"<{self.__class__.__name__} [{self}]>"
# See _asdelta() docstring for details on why we are doing this.
Delta.__add__ = _asdelta(Delta.__add__)
Delta.__radd__ = _asdelta(Delta.__radd__)
Delta.__sub__ = _asdelta(Delta.__sub__)
Delta.__mul__ = _asdelta(Delta.__mul__)
Delta.__rmul__ = _asdelta(Delta.__rmul__)
Delta.__floordiv__ = _asdelta(Delta.__floordiv__)
Delta.__pos__ = _asdelta(Delta.__pos__)
Delta.__neg__ = _asdelta(Delta.__neg__)
Delta.__abs__ = _asdelta(Delta.__abs__)
Delta.__truediv__ = _asdelta(Delta.__truediv__)
Delta.__mod__ = _asdelta(Delta.__mod__)
Delta.__divmod__ = _asdelta(Delta.__divmod__)
# Override timedelta.min/max/resolution with equivalent Delta objects.
Delta.min = Delta(-999999999)
Delta.max = Delta(days=999999999, hours=23, minutes=59, seconds=59, microseconds=999999)
Delta.resolution = Delta(microseconds=1)
def to_seconds(*, microseconds=0, milliseconds=0, seconds=0, minutes=0, hours=0, days=0, weeks=0):
"""Return total number of seconds by combining values from each unit."""
return Delta(
microseconds=microseconds,
milliseconds=milliseconds,
seconds=seconds,
minutes=minutes,
hours=hours,
days=days,
weeks=weeks,
).total_seconds()