Skip to content

Commit caaabe8

Browse files
committed
Adds the Period class
1 parent d4e6fd9 commit caaabe8

File tree

9 files changed

+237
-69
lines changed

9 files changed

+237
-69
lines changed

docs/index.rst

Lines changed: 62 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -726,7 +726,7 @@ Each method returns a new ``Pendulum`` instance.
726726
Difference
727727
==========
728728

729-
The ``diff()`` method returns a `Interval`_ instance that represents the total duration
729+
The ``diff()`` method returns a `Period`_ instance that represents the total duration
730730
between two ``Pendulum`` instances. This interval can be then expressed in various units.
731731
These interval methods always return *the total difference expressed* in the specified time requested.
732732
All values are truncated and not rounded.
@@ -1052,7 +1052,6 @@ level.
10521052
Interval
10531053
========
10541054

1055-
When you subtract a ``Pendulum`` instance to another, it will return a ``Interval`` instance.
10561055
The ``Interval`` class is inherited from the native ``timedelta`` class.
10571056
It has many improvements over the base class.
10581057

@@ -1189,3 +1188,64 @@ It also has a handy ``in_words()``, which determines the interval representation
11891188
11901189
it.in_words(locale='de')
11911190
'168 Wochen 1 Tag 2 Stunden 1 Minute 24 Sekunden'
1191+
1192+
1193+
Period
1194+
======
1195+
1196+
When you subtract a ``Pendulum`` instance to another, or use the ``diff()`` method, it will return a ``Period`` instance.
1197+
it inherits from the `Interval`_ class with the added benefit that it is aware of the
1198+
instances that generated it, so that it can give access to more methods and properties:
1199+
1200+
.. code-block:: python
1201+
1202+
from pendulum import Pendulum
1203+
1204+
start = Pendulum(2000, 1, 1)
1205+
end = Pendulum(2000, 1, 31)
1206+
1207+
period = end - start
1208+
period.in_weekdays()
1209+
21
1210+
1211+
period.in_weekend_days()
1212+
10
1213+
1214+
1215+
Instantiation
1216+
-------------
1217+
1218+
You can create an instance in the following ways:
1219+
1220+
.. code-block:: python
1221+
1222+
import pendulum
1223+
1224+
start = pendulum.Pendulum(2000, 1, 1)
1225+
end = pendulum.Pendulum(2000, 1, 31)
1226+
1227+
period = pendulum.Period(start, end)
1228+
period = pendulum.period(start, end)
1229+
1230+
You can also make an inverted period:
1231+
1232+
.. code-block:: python
1233+
1234+
period = pendulum.period(end, start)
1235+
period.in_weekdays()
1236+
-21
1237+
1238+
period.in_weekend_days()
1239+
-10
1240+
1241+
If you have inverted dates but want to make sure that the period is positive,
1242+
you set the ``absolute`` keyword argument to ``True``:
1243+
1244+
.. code-block:: python
1245+
1246+
period = pendulum.period(end, start, absolute=True)
1247+
period.in_weekdays()
1248+
21
1249+
1250+
period.in_weekend_days()
1251+
10

pendulum/__init__.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from .pendulum import Pendulum
44
from .interval import Interval
5+
from .period import Period
56

67
# Constants
78
MONDAY = Pendulum.MONDAY
@@ -56,3 +57,6 @@
5657

5758
# Interval
5859
interval = Interval
60+
61+
# Period
62+
period = Period

pendulum/interval.py

Lines changed: 0 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -177,63 +177,3 @@ class AbsoluteInterval(AbsoluteIntervalMixin, Interval):
177177
"""
178178
Interval that expresses a time difference in absolute values.
179179
"""
180-
181-
182-
class DatetimeAwareInterval(WordableIntervalMixin, BaseInterval):
183-
"""
184-
Interval class that is aware of the datetimes that generated the
185-
time difference.
186-
"""
187-
188-
def __new__(cls, dt1, dt2, days=0, seconds=0, microseconds=0,
189-
milliseconds=0, minutes=0, hours=0, weeks=0):
190-
return super(DatetimeAwareInterval, cls).__new__(
191-
cls, days, seconds, microseconds, milliseconds, minutes, hours, weeks
192-
)
193-
194-
def __init__(self, dt1, dt2, days=0, seconds=0, microseconds=0,
195-
milliseconds=0, minutes=0, hours=0, weeks=0):
196-
super(DatetimeAwareInterval, self).__init__(
197-
days, seconds, microseconds, milliseconds, minutes, hours, weeks
198-
)
199-
200-
self._dt1 = dt1
201-
self._dt2 = dt2
202-
203-
@classmethod
204-
def instance(cls, delta, dt1, dt2):
205-
return cls(dt1, dt2, days=delta.days, seconds=delta.seconds, microseconds=delta.microseconds)
206-
207-
208-
class DatetimeAwareAbsoluteInterval(AbsoluteIntervalMixin, DatetimeAwareInterval):
209-
"""
210-
Interval class that is aware of the datetimes that generated the
211-
time difference and expresses it in absolute values.
212-
"""
213-
214-
215-
class IntervalFactory(object):
216-
"""
217-
Factory to get the poper interval class.
218-
"""
219-
220-
@classmethod
221-
def get(cls, dt1, dt2, absolute=False):
222-
"""
223-
Gets the proper interval class for two given datetimes.
224-
225-
:param dt1: The first datetime
226-
:type dt1: Pendulum or datetime
227-
228-
:param dt2: The second datetime
229-
:type dt2: Pendulum or datetime
230-
231-
:rtype: Interval
232-
"""
233-
delta = dt1 - dt2
234-
235-
klass = DatetimeAwareInterval
236-
if absolute:
237-
klass = DatetimeAwareAbsoluteInterval
238-
239-
return klass.instance(delta, dt1, dt2)

pendulum/pendulum.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from dateutil.relativedelta import relativedelta
1717
from dateutil import parser as dateparser
1818

19-
from .interval import IntervalFactory
19+
from .period import Period
2020
from .exceptions import PendulumException
2121
from .mixins.default import TranslatableMixin
2222
from ._compat import PY33, basestring
@@ -1171,7 +1171,7 @@ def maximum(self, dt=None):
11711171
"""
11721172
return self.max_(dt)
11731173

1174-
def is_week_day(self):
1174+
def is_weekday(self):
11751175
"""
11761176
Determines if the instance is a weekday.
11771177
@@ -1457,12 +1457,12 @@ def diff(self, dt=None, abs=True):
14571457
:param abs: Whether to return an absolute interval or not
14581458
:type abs: bool
14591459
1460-
:rtype: Interval
1460+
:rtype: Period
14611461
"""
14621462
if dt is None:
14631463
dt = self.now(self._tz)
14641464

1465-
return IntervalFactory.get(self._get_datetime(dt), self._datetime, absolute=abs)
1465+
return Period(self, self._get_datetime(dt, pendulum=True), absolute=abs)
14661466

14671467
def diff_for_humans(self, other=None, absolute=False, locale=None):
14681468
"""

pendulum/period.py

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from .mixins.interval import WordableIntervalMixin
4+
from .interval import BaseInterval
5+
6+
7+
class Period(WordableIntervalMixin, BaseInterval):
8+
"""
9+
Interval class that is aware of the datetimes that generated the
10+
time difference.
11+
"""
12+
13+
def __new__(cls, start, end, absolute=False):
14+
from .pendulum import Pendulum
15+
16+
if absolute and start > end:
17+
end, start = start, end
18+
19+
if isinstance(start, Pendulum):
20+
start = start._datetime
21+
22+
if isinstance(end, Pendulum):
23+
end = end._datetime
24+
25+
delta = end - start
26+
27+
return super(Period, cls).__new__(
28+
cls, seconds=delta.total_seconds()
29+
)
30+
31+
def __init__(self, start, end, absolute=False):
32+
from .pendulum import Pendulum
33+
34+
super(Period, self).__init__()
35+
36+
if not isinstance(start, Pendulum):
37+
start = Pendulum.instance(start)
38+
39+
if not isinstance(end, Pendulum):
40+
end = Pendulum.instance(end)
41+
42+
self._invert = False
43+
if start > end:
44+
self._invert = True
45+
46+
if absolute:
47+
end, start = start, end
48+
49+
self._absolute = absolute
50+
self._start = start
51+
self._end = end
52+
53+
@property
54+
def start(self):
55+
return self._start
56+
57+
@property
58+
def end(self):
59+
return self._end
60+
61+
def in_weekdays(self):
62+
start, end = self.start.start_of('day'), self.end.start_of('day')
63+
if not self._absolute and self.invert:
64+
start, end = self.end.start_of('day'), self.start.start_of('day')
65+
66+
days = 0
67+
while start <= end:
68+
if start.is_weekday():
69+
days += 1
70+
71+
start = start.add(days=1)
72+
73+
return days * (-1 if not self._absolute and self.invert else 1)
74+
75+
def in_weekend_days(self):
76+
start, end = self.start.start_of('day'), self.end.start_of('day')
77+
if not self._absolute and self.invert:
78+
start, end = self.end.start_of('day'), self.start.start_of('day')
79+
80+
days = 0
81+
while start <= end:
82+
if start.is_weekend():
83+
days += 1
84+
85+
start = start.add(days=1)
86+
87+
return days * (-1 if not self._absolute and self.invert else 1)
88+
89+

tests/pendulum_tests/test_diff.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,30 @@ def test_diff_in_days_ensure_is_truncated(self):
7979
dt = Pendulum.create_from_date(2000, 1, 1)
8080
self.assertEqual(1, dt.diff(dt.copy().add(days=1).add(hours=13)).in_days())
8181

82+
def test_diff_in_weekdays_positive(self):
83+
dt = Pendulum.create_from_date(2000, 1, 1)
84+
self.assertEqual(21, dt.diff(dt.end_of('month')).in_weekdays())
85+
86+
def test_diff_in_weekdays_negative_no_sign(self):
87+
dt = Pendulum.create_from_date(2000, 1, 31)
88+
self.assertEqual(21, dt.diff(dt.start_of('month')).in_weekdays())
89+
90+
def test_diff_in_weekdays_negative_with_sign(self):
91+
dt = Pendulum.create_from_date(2000, 1, 31)
92+
self.assertEqual(-21, dt.diff(dt.start_of('month'), False).in_weekdays())
93+
94+
def test_diff_in_weekend_days_positive(self):
95+
dt = Pendulum.create_from_date(2000, 1, 1)
96+
self.assertEqual(10, dt.diff(dt.end_of('month')).in_weekend_days())
97+
98+
def test_diff_in_weekend_days_negative_no_sign(self):
99+
dt = Pendulum.create_from_date(2000, 1, 31)
100+
self.assertEqual(10, dt.diff(dt.start_of('month')).in_weekend_days())
101+
102+
def test_diff_in_weekend_days_negative_with_sign(self):
103+
dt = Pendulum.create_from_date(2000, 1, 31)
104+
self.assertEqual(-10, dt.diff(dt.start_of('month'), False).in_weekend_days())
105+
82106
def test_diff_in_weeks_positive(self):
83107
dt = Pendulum.create_from_date(2000, 1, 1)
84108
self.assertEqual(52, dt.diff(dt.copy().add(years=1)).in_weeks())

tests/pendulum_tests/test_getters.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -151,11 +151,11 @@ def test_timezone_name(self):
151151
d = Pendulum.create_from_date(2000, 1, 1, -5)
152152
self.assertEqual(None, d.timezone_name)
153153

154-
def test_is_week_day(self):
154+
def test_is_weekday(self):
155155
d = Pendulum.now().next(Pendulum.MONDAY)
156-
self.assertTrue(d.is_week_day())
156+
self.assertTrue(d.is_weekday())
157157
d = d.next(Pendulum.SATURDAY)
158-
self.assertFalse(d.is_week_day())
158+
self.assertFalse(d.is_weekday())
159159

160160
def test_is_weekend(self):
161161
d = Pendulum.now().next(Pendulum.MONDAY)

tests/period_tests/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# -*- coding: utf-8 -*-
2+
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
# -*- coding: utf-8 -*-
2+
3+
from datetime import datetime
4+
from pendulum import Period, Pendulum
5+
6+
from .. import AbstractTestCase
7+
8+
9+
class ConstructTest(AbstractTestCase):
10+
11+
def test_with_datetimes(self):
12+
dt1 = datetime(2000, 1, 1)
13+
dt2 = datetime(2000, 1, 31)
14+
15+
p = Period(dt1, dt2)
16+
self.assertIsInstanceOfPendulum(p.start)
17+
self.assertIsInstanceOfPendulum(p.end)
18+
self.assertPendulum(p.start, 2000, 1, 1)
19+
self.assertPendulum(p.end, 2000, 1, 31)
20+
21+
def test_with_pendulum(self):
22+
dt1 = Pendulum(2000, 1, 1)
23+
dt2 = Pendulum(2000, 1, 31)
24+
25+
p = Period(dt1, dt2)
26+
self.assertIsInstanceOfPendulum(p.start)
27+
self.assertIsInstanceOfPendulum(p.end)
28+
self.assertPendulum(p.start, 2000, 1, 1)
29+
self.assertPendulum(p.end, 2000, 1, 31)
30+
31+
def test_inverted(self):
32+
dt1 = Pendulum(2000, 1, 1)
33+
dt2 = Pendulum(2000, 1, 31)
34+
35+
p = Period(dt2, dt1)
36+
self.assertIsInstanceOfPendulum(p.start)
37+
self.assertIsInstanceOfPendulum(p.end)
38+
self.assertPendulum(p.start, 2000, 1, 31)
39+
self.assertPendulum(p.end, 2000, 1, 1)
40+
41+
def test_inverted_and_absolute(self):
42+
dt1 = Pendulum(2000, 1, 1)
43+
dt2 = Pendulum(2000, 1, 31)
44+
45+
p = Period(dt2, dt1, True)
46+
self.assertIsInstanceOfPendulum(p.start)
47+
self.assertIsInstanceOfPendulum(p.end)
48+
self.assertPendulum(p.start, 2000, 1, 1)
49+
self.assertPendulum(p.end, 2000, 1, 31)

0 commit comments

Comments
 (0)