Skip to content

Commit 4a8fe84

Browse files
committed
Improves adding/subtracting time performance.
1 parent 4494133 commit 4a8fe84

File tree

2 files changed

+108
-5
lines changed

2 files changed

+108
-5
lines changed

pendulum/helpers.py

Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,111 @@
11
# -*- coding: utf-8 -*-
22

3+
from math import copysign
4+
from datetime import timedelta
5+
36
try:
47
from ._extensions._helpers import local_time
58
except ImportError:
69
from ._extensions.helpers import local_time
10+
11+
from .constants import DAYS_PER_MONTHS
12+
13+
14+
def is_leap(year):
15+
return year % 4 == 0 and (year % 100 != 0 or year % 400 == 0)
16+
17+
18+
def add_duration(dt, years=0, months=0, weeks=0, days=0,
19+
hours=0, minutes=0, seconds=0, microseconds=0):
20+
"""
21+
Adds a duration to a datetime instance.
22+
23+
:param dt: The datetime instance
24+
:type dt: datetime.datetime
25+
26+
:param years: The number of years
27+
:type years: int
28+
29+
:param months: The number of months
30+
:type months: int
31+
32+
:param weeks: The number of weeks
33+
:type weeks: int
34+
35+
:param days: The number of days
36+
:type days: int
37+
38+
:param hours: The number of hours
39+
:type hours: int
40+
41+
:param minutes: The number of minutes
42+
:type minutes: int
43+
44+
:param seconds: The number of seconds
45+
:type seconds: int
46+
47+
:param microseconds: The number of microseconds
48+
:type microseconds: int
49+
50+
:rtype: datetime.datetime
51+
"""
52+
days += weeks * 7
53+
54+
# Normalizing
55+
if abs(microseconds) > 999999:
56+
s = _sign(microseconds)
57+
div, mod = divmod(microseconds * s, 1000000)
58+
microseconds = mod * s
59+
seconds += div * s
60+
61+
if abs(seconds) > 59:
62+
s = _sign(seconds)
63+
div, mod = divmod(seconds * s, 60)
64+
seconds = mod * s
65+
minutes += div * s
66+
67+
if abs(minutes) > 59:
68+
s = _sign(minutes)
69+
div, mod = divmod(minutes * s, 60)
70+
minutes = mod * s
71+
hours += div * s
72+
73+
if abs(hours) > 23:
74+
s = _sign(hours)
75+
div, mod = divmod(hours * s, 24)
76+
hours = mod * s
77+
days += div * s
78+
79+
if abs(months) > 11:
80+
s = _sign(months)
81+
div, mod = divmod(months * s, 12)
82+
months = mod * s
83+
years += div * s
84+
85+
year = dt.year + years
86+
month = dt.month
87+
88+
if months:
89+
month += months
90+
if month > 12:
91+
year += 1
92+
month -= 12
93+
elif month < 1:
94+
year -= 1
95+
month += 12
96+
97+
day = min(DAYS_PER_MONTHS[int(is_leap(year))][month], dt.day)
98+
99+
dt = dt.replace(year=year, month=month, day=day)
100+
101+
return dt + timedelta(
102+
days=days,
103+
hours=hours,
104+
minutes=minutes,
105+
seconds=seconds,
106+
microseconds=microseconds
107+
)
108+
109+
110+
def _sign(x):
111+
return int(copysign(1, x))

pendulum/pendulum.py

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,14 @@
55
import calendar
66
import datetime
77

8-
from dateutil.relativedelta import relativedelta
9-
108
from .date import Date
119
from .time import Time
1210
from .period import Period
1311
from .exceptions import PendulumException
1412
from .tz import Timezone, UTC, FixedTimezone, local_timezone
1513
from .tz.timezone_info import TimezoneInfo
1614
from .parsing import parse
15+
from .helpers import add_duration
1716
from .constants import (
1817
YEARS_PER_CENTURY, YEARS_PER_DECADE,
1918
MONTHS_PER_YEAR,
@@ -1125,14 +1124,13 @@ def add(self, years=0, months=0, weeks=0, days=0,
11251124
11261125
:rtype: Pendulum
11271126
"""
1128-
delta = relativedelta(
1127+
dt = add_duration(
1128+
self._datetime,
11291129
years=years, months=months, weeks=weeks, days=days,
11301130
hours=hours, minutes=minutes, seconds=seconds,
11311131
microseconds=microseconds
11321132
)
11331133

1134-
dt = self._datetime + delta
1135-
11361134
if any([years, months, weeks, days]):
11371135
# If we specified any of years, months, weeks or days
11381136
# we will not apply the transition (if any)

0 commit comments

Comments
 (0)