From 9274f5c2634927e417d3f293379603c0b369dec7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Eustace?= Date: Sat, 4 Jul 2020 23:58:17 +0200 Subject: [PATCH] Fix the computation of total seconds with years and months --- pendulum/datetime.py | 11 ++------- pendulum/duration.py | 42 ++++++++++++++++++++------------ tests/duration/test_construct.py | 2 ++ 3 files changed, 31 insertions(+), 24 deletions(-) diff --git a/pendulum/datetime.py b/pendulum/datetime.py index 610af71e..d79257ca 100644 --- a/pendulum/datetime.py +++ b/pendulum/datetime.py @@ -754,7 +754,7 @@ def _add_timedelta_(self, delta): ) elif isinstance(delta, pendulum.Duration): return self.add( - years=delta.years, months=delta.months, seconds=delta.total_seconds() + years=delta.years, months=delta.months, seconds=delta._total ) return self.add(seconds=delta.total_seconds()) @@ -770,14 +770,7 @@ def _subtract_timedelta(self, delta): """ if isinstance(delta, pendulum.Duration): return self.subtract( - years=delta.years, - months=delta.months, - weeks=delta.weeks, - days=delta.remaining_days, - hours=delta.hours, - minutes=delta.minutes, - seconds=delta.remaining_seconds, - microseconds=delta.microseconds, + years=delta.years, months=delta.months, seconds=delta._total ) return self.subtract(seconds=delta.total_seconds()) diff --git a/pendulum/duration.py b/pendulum/duration.py index ff1a91b5..1ad9e19a 100644 --- a/pendulum/duration.py +++ b/pendulum/duration.py @@ -66,11 +66,19 @@ def __new__( raise ValueError("Float year and months are not supported") self = timedelta.__new__( - cls, days, seconds, microseconds, milliseconds, minutes, hours, weeks + cls, + days + years * 365 + months * 30, + seconds, + microseconds, + milliseconds, + minutes, + hours, + weeks, ) # Intuitive normalization - total = self.total_seconds() + total = self.total_seconds() - (years * 365 + months * 30) * SECONDS_PER_DAY + self._total = total m = 1 if total < 0: @@ -80,7 +88,7 @@ def __new__( self._seconds = abs(int(total)) % SECONDS_PER_DAY * m _days = abs(int(total)) // SECONDS_PER_DAY * m - self._days = _days + (years * 365 + months * 30) + self._days = _days self._remaining_days = abs(_days) % 7 * m self._weeks = abs(_days) // 7 * m self._months = months @@ -103,10 +111,18 @@ def total_weeks(self): if PYPY: def total_seconds(self): + days = 0 + + if hasattr(self, "_years"): + days += self._years * 365 + + if hasattr(self, "_months"): + days += self._months * 30 + if hasattr(self, "_remaining_days"): - days = self._weeks * 7 + self._remaining_days + days += self._weeks * 7 + self._remaining_days else: - days = self._days + days += self._days return ( (days * SECONDS_PER_DAY + self._seconds) * US_PER_SECOND @@ -125,9 +141,11 @@ def months(self): def weeks(self): return self._weeks - @property - def days(self): - return self._days + if PYPY: + + @property + def days(self) -> int: + return self._years * 365 + self._months * 30 + self._days @property def remaining_days(self): @@ -320,7 +338,7 @@ def __mul__(self, other): return self.__class__( years=self._years * other, months=self._months * other, - seconds=self.total_seconds() * other, + seconds=self._total * other, ) if isinstance(other, float): @@ -341,9 +359,6 @@ def __floordiv__(self, other): if isinstance(other, timedelta): return usec // other._to_microseconds() - # Removing years/months approximation - usec -= (self._years * 365 + self._months * 30) * SECONDS_PER_DAY * 1e6 - if isinstance(other, int): return self.__class__( 0, @@ -361,9 +376,6 @@ def __truediv__(self, other): if isinstance(other, timedelta): return usec / other._to_microseconds() - # Removing years/months approximation - usec -= (self._years * 365 + self._months * 30) * SECONDS_PER_DAY * 1e6 - if isinstance(other, int): return self.__class__( 0, diff --git a/tests/duration/test_construct.py b/tests/duration/test_construct.py index 8afb1bb3..31ca0ea2 100644 --- a/tests/duration/test_construct.py +++ b/tests/duration/test_construct.py @@ -17,12 +17,14 @@ def test_years(): pi = pendulum.duration(years=2) assert_duration(pi, years=2, weeks=0) assert 730 == pi.days + assert 63072000 == pi.total_seconds() def test_months(): pi = pendulum.duration(months=3) assert_duration(pi, months=3, weeks=0) assert 90 == pi.days + assert 7776000 == pi.total_seconds() def test_weeks():