Skip to content

Commit d4e6fd9

Browse files
committed
Refactores intervals
1 parent 93306ef commit d4e6fd9

File tree

5 files changed

+239
-213
lines changed

5 files changed

+239
-213
lines changed

pendulum/interval.py

Lines changed: 82 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,13 @@
22

33
from datetime import timedelta
44

5-
from .translator import Translator
5+
from .mixins.interval import WordableIntervalMixin, AbsoluteIntervalMixin
66

77

8-
class Interval(timedelta):
8+
class BaseInterval(timedelta):
9+
"""
10+
Base class for all inherited interval classes.
11+
"""
912

1013
_y = None
1114
_m = None
@@ -16,8 +19,6 @@ class Interval(timedelta):
1619
_s = None
1720
_invert = None
1821

19-
_translator = None
20-
2122
def __init__(self, days=0, seconds=0, microseconds=0,
2223
milliseconds=0, minutes=0, hours=0, weeks=0):
2324
total = self.total_seconds()
@@ -29,68 +30,6 @@ def __init__(self, days=0, seconds=0, microseconds=0,
2930
self._seconds = abs(int(total)) % 86400 * m
3031
self._days = abs(int(total)) // 86400 * m
3132

32-
@classmethod
33-
def instance(cls, delta):
34-
"""
35-
Creates a Interval from a timedelta
36-
37-
:type delta: timedelta
38-
39-
:rtype: Interval
40-
"""
41-
return cls(days=delta.days, seconds=delta.seconds, microseconds=delta.microseconds)
42-
43-
# Localization
44-
45-
@classmethod
46-
def translator(cls):
47-
"""
48-
Initialize the translator instance if necessary.
49-
50-
:rtype: Translator
51-
"""
52-
if cls._translator is None:
53-
cls._translator = Translator('en')
54-
cls.set_locale('en')
55-
56-
return cls._translator
57-
58-
@classmethod
59-
def set_translator(cls, translator):
60-
"""
61-
Set the translator instance to use.
62-
63-
:param translator: The translator
64-
:type translator: Translator
65-
"""
66-
cls._translator = translator
67-
68-
@classmethod
69-
def get_locale(cls):
70-
"""
71-
Get the current translator locale.
72-
73-
:rtype: str
74-
"""
75-
return cls.translator().locale
76-
77-
@classmethod
78-
def set_locale(cls, locale):
79-
"""
80-
Set the current translator locale and
81-
indicate if the source locale file exists.
82-
83-
:type locale: str
84-
85-
:rtype: bool
86-
"""
87-
if not cls.translator().register_resource(locale):
88-
return False
89-
90-
cls.translator().locale = locale
91-
92-
return True
93-
9433
def total_minutes(self):
9534
return self.total_seconds() / 60
9635

@@ -199,90 +138,102 @@ def _sign(self, value):
199138

200139
return 1
201140

202-
def in_words(self, locale=None):
203-
"""
204-
Get the current interval in words in the current locale.
205-
206-
Ex: 6 jours 23 heures 58 minutes
207-
208-
:rtype: str
209-
"""
210-
periods = [
211-
('week', self.weeks),
212-
('day', self.days_exclude_weeks),
213-
('hour', self.hours),
214-
('minute', self.minutes),
215-
('second', self.seconds)
216-
]
217-
218-
parts = []
219-
for period in periods:
220-
unit, count = period
221-
if abs(count) > 0:
222-
parts.append(
223-
self.translator().transchoice(unit, count, {'count': count}, locale=locale)
224-
)
225-
226-
return ' '.join(parts)
227-
228-
def __str__(self):
229-
return self.in_words()
230-
231-
def __repr__(self):
232-
return '<{0} [{1}]>'.format(self.__class__.__name__, str(self))
233-
234141
def __add__(self, other):
235142
if isinstance(other, timedelta):
236-
return Interval(seconds=self.total_seconds() + other.total_seconds())
143+
return self.__class__(seconds=self.total_seconds() + other.total_seconds())
237144

238145
return NotImplemented
239146

240147
def __sub__(self, other):
241148
if isinstance(other, timedelta):
242-
# for CPython compatibility, we cannot use
243-
# our __class__ here, but need a real timedelta
244-
return Interval(seconds=self.total_seconds() - other.total_seconds())
149+
return self.__class__(seconds=self.total_seconds() - other.total_seconds())
245150

246151
return NotImplemented
247152

248153
def __neg__(self):
249-
# for CPython compatibility, we cannot use
250-
# our __class__ here, but need a real timedelta
251-
return Interval(seconds=-self.total_seconds())
154+
return self.__class__(seconds=-self.total_seconds())
252155

253156

254-
class AbsoluteInterval(Interval):
157+
class Interval(WordableIntervalMixin, BaseInterval):
158+
"""
159+
Replacement for the standard timedelta class.
255160
256-
def total_seconds(self):
257-
return abs(super(AbsoluteInterval, self).total_seconds())
161+
Provides several improvements over the base class.
162+
"""
258163

259-
@property
260-
def weeks(self):
261-
return abs(super(AbsoluteInterval, self).weeks)
164+
@classmethod
165+
def instance(cls, delta):
166+
"""
167+
Creates a Interval from a timedelta
262168
263-
@property
264-
def days(self):
265-
return abs(super(AbsoluteInterval, self).days)
169+
:type delta: timedelta
266170
267-
@property
268-
def hours(self):
269-
return abs(super(AbsoluteInterval, self).hours)
171+
:rtype: Interval
172+
"""
173+
return cls(days=delta.days, seconds=delta.seconds, microseconds=delta.microseconds)
270174

271-
@property
272-
def minutes(self):
273-
return abs(super(AbsoluteInterval, self).minutes)
274175

275-
@property
276-
def seconds(self):
277-
return abs(super(AbsoluteInterval, self).seconds)
176+
class AbsoluteInterval(AbsoluteIntervalMixin, Interval):
177+
"""
178+
Interval that expresses a time difference in absolute values.
179+
"""
278180

279-
@property
280-
def microseconds(self):
281-
return abs(super(AbsoluteInterval, self).microseconds)
282181

283-
@property
284-
def invert(self):
285-
return super(AbsoluteInterval, self).total_seconds() < 0
182+
class DatetimeAwareInterval(WordableIntervalMixin, BaseInterval):
183+
"""
184+
Interval class that is aware of the datetimes that generated the
185+
time difference.
186+
"""
286187

287-
def _sign(self, value):
288-
return 1
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/mixins/__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+

pendulum/mixins/default.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# -*- coding: utf-8 -*-
2+
3+
import re
4+
from ..translator import Translator
5+
6+
7+
class TranslatableMixin(object):
8+
9+
_translator = None
10+
11+
@classmethod
12+
def translator(cls):
13+
"""
14+
Initialize the translator instance if necessary.
15+
16+
:rtype: Translator
17+
"""
18+
if cls._translator is None:
19+
cls._translator = Translator('en')
20+
cls.set_locale('en')
21+
22+
return cls._translator
23+
24+
@classmethod
25+
def set_translator(cls, translator):
26+
"""
27+
Set the translator instance to use.
28+
29+
:param translator: The translator
30+
:type translator: Translator
31+
"""
32+
cls._translator = translator
33+
34+
@classmethod
35+
def get_locale(cls):
36+
"""
37+
Get the current translator locale.
38+
39+
:rtype: str
40+
"""
41+
return cls.translator().locale
42+
43+
@classmethod
44+
def set_locale(cls, locale):
45+
"""
46+
Set the current translator locale and
47+
indicate if the source locale file exists.
48+
49+
:type locale: str
50+
51+
:rtype: bool
52+
"""
53+
locale = cls.format_locale(locale)
54+
55+
if not cls.translator().register_resource(locale):
56+
return False
57+
58+
cls.translator().locale = locale
59+
60+
return True
61+
62+
@classmethod
63+
def format_locale(cls, locale):
64+
"""
65+
Properly format locale.
66+
67+
:param locale: The locale
68+
:type locale: str
69+
70+
:rtype: str
71+
"""
72+
m = re.match('([a-z]{2})[-_]([a-z]{2})', locale, re.I)
73+
if m:
74+
return '{}_{}'.format(m.group(1).lower(), m.group(2).lower())
75+
else:
76+
return locale.lower()

0 commit comments

Comments
 (0)