22
33from datetime import timedelta
44
5- from .mixins .interval import WordableIntervalMixin , AbsoluteIntervalMixin
5+ from .mixins .interval import (
6+ WordableIntervalMixin
7+ )
8+
9+
10+ def _divide_and_round (a , b ):
11+ """divide a by b and round result to the nearest integer
12+
13+ When the ratio is exactly half-way between two integers,
14+ the even integer is returned.
15+ """
16+ # Based on the reference implementation for divmod_near
17+ # in Objects/longobject.c.
18+ q , r = divmod (a , b )
19+ # round up if either r / b > 0.5, or r / b == 0.5 and q is odd.
20+ # The expression r / b > 0.5 is equivalent to 2 * r > b if b is
21+ # positive, 2 * r < b if b negative.
22+ r *= 2
23+ greater_than_half = r > b if b > 0 else r < b
24+ if greater_than_half or r == b and q % 2 == 1 :
25+ q += 1
26+
27+ return q
628
729
830class BaseInterval (timedelta ):
@@ -19,17 +41,26 @@ class BaseInterval(timedelta):
1941 _s = None
2042 _invert = None
2143
22- def __init__ ( self , days = 0 , seconds = 0 , microseconds = 0 ,
44+ def __new__ ( cls , days = 0 , seconds = 0 , microseconds = 0 ,
2345 milliseconds = 0 , minutes = 0 , hours = 0 , weeks = 0 ):
46+ self = timedelta .__new__ (
47+ cls , days , seconds , microseconds ,
48+ milliseconds , minutes , hours , weeks
49+ )
50+
51+ # Intuitive normalization
2452 total = self .total_seconds ()
53+
2554 m = 1
2655 if total < 0 :
2756 m = - 1
2857
29- self ._microseconds = total - int ( total )
58+ self ._microseconds = round ( total % 1 * 1e6 )
3059 self ._seconds = abs (int (total )) % 86400 * m
3160 self ._days = abs (int (total )) // 86400 * m
3261
62+ return self
63+
3364 def total_minutes (self ):
3465 return self .total_seconds () / 60
3566
@@ -104,6 +135,10 @@ def seconds(self):
104135
105136 return self ._s
106137
138+ @property
139+ def microseconds (self ):
140+ return self ._microseconds
141+
107142 @property
108143 def invert (self ):
109144 if self ._invert is None :
@@ -138,12 +173,33 @@ def _sign(self, value):
138173
139174 return 1
140175
176+
177+ class Interval (WordableIntervalMixin , BaseInterval ):
178+ """
179+ Replacement for the standard timedelta class.
180+
181+ Provides several improvements over the base class.
182+ """
183+
184+ @classmethod
185+ def instance (cls , delta ):
186+ """
187+ Creates a Interval from a timedelta
188+
189+ :type delta: timedelta
190+
191+ :rtype: Interval
192+ """
193+ return cls (days = delta .days , seconds = delta .seconds , microseconds = delta .microseconds )
194+
141195 def __add__ (self , other ):
142196 if isinstance (other , timedelta ):
143197 return self .__class__ (seconds = self .total_seconds () + other .total_seconds ())
144198
145199 return NotImplemented
146200
201+ __radd__ = __add__
202+
147203 def __sub__ (self , other ):
148204 if isinstance (other , timedelta ):
149205 return self .__class__ (seconds = self .total_seconds () - other .total_seconds ())
@@ -153,27 +209,94 @@ def __sub__(self, other):
153209 def __neg__ (self ):
154210 return self .__class__ (seconds = - self .total_seconds ())
155211
212+ def _to_microseconds (self ):
213+ return ((self ._days * (24 * 3600 ) + self ._seconds ) * 1000000 +
214+ self ._microseconds )
156215
157- class Interval ( WordableIntervalMixin , BaseInterval ):
158- """
159- Replacement for the standard timedelta class.
216+ def __mul__ ( self , other ):
217+ if isinstance ( other , int ):
218+ return self . __class__ ( seconds = self . total_seconds () * other )
160219
161- Provides several improvements over the base class.
162- """
220+ if isinstance (other , float ):
221+ usec = self ._to_microseconds ()
222+ a , b = other .as_integer_ratio ()
163223
164- @classmethod
165- def instance (cls , delta ):
166- """
167- Creates a Interval from a timedelta
224+ return self .__class__ (0 , 0 , _divide_and_round (usec * a , b ))
168225
169- :type delta: timedelta
226+ return NotImplemented
170227
171- :rtype: Interval
172- """
173- return cls (days = delta .days , seconds = delta .seconds , microseconds = delta .microseconds )
228+ __rmul__ = __mul__
229+
230+ def __floordiv__ (self , other ):
231+ if not isinstance (other , (int , timedelta )):
232+ return NotImplemented
233+
234+ usec = self ._to_microseconds ()
235+ if isinstance (other , timedelta ):
236+ return usec // other ._to_microseconds ()
237+
238+ if isinstance (other , int ):
239+ return self .__class__ (0 , 0 , usec // other )
240+
241+ def __truediv__ (self , other ):
242+ if not isinstance (other , (int , float , timedelta )):
243+ return NotImplemented
244+
245+ usec = self ._to_microseconds ()
246+ if isinstance (other , timedelta ):
247+ return usec / other ._to_microseconds ()
248+
249+ if isinstance (other , int ):
250+ return self .__class__ (0 , 0 , _divide_and_round (usec , other ))
251+
252+ if isinstance (other , float ):
253+ a , b = other .as_integer_ratio ()
254+
255+ return self .__class__ (0 , 0 , _divide_and_round (b * usec , a ))
174256
257+ __div__ = __floordiv__
175258
176- class AbsoluteInterval (AbsoluteIntervalMixin , Interval ):
259+ def __mod__ (self , other ):
260+ if isinstance (other , timedelta ):
261+ r = self ._to_microseconds () % other ._to_microseconds ()
262+
263+ return self .__class__ (0 , 0 , r )
264+
265+ return NotImplemented
266+
267+ def __divmod__ (self , other ):
268+ if isinstance (other , timedelta ):
269+ q , r = divmod (self ._to_microseconds (),
270+ other ._to_microseconds ())
271+
272+ return q , self .__class__ (0 , 0 , r )
273+
274+ return NotImplemented
275+
276+ Interval .min = Interval (- 999999999 )
277+ Interval .max = Interval (days = 999999999 , hours = 23 ,
278+ minutes = 59 , seconds = 59 ,
279+ microseconds = 999999 )
280+ Interval .resolution = Interval (microseconds = 1 )
281+
282+
283+ class AbsoluteInterval (Interval ):
177284 """
178285 Interval that expresses a time difference in absolute values.
179286 """
287+
288+ def __new__ (cls , days = 0 , seconds = 0 , microseconds = 0 ,
289+ milliseconds = 0 , minutes = 0 , hours = 0 , weeks = 0 ):
290+ self = super (AbsoluteInterval , cls ).__new__ (
291+ cls , days , seconds , microseconds ,
292+ milliseconds , minutes , hours , weeks
293+ )
294+
295+ # Intuitive normalization
296+ total = self .total_seconds ()
297+
298+ self ._microseconds = abs (round (total % 1 * 1e6 ))
299+ self ._seconds = abs (int (total )) % 86400
300+ self ._days = abs (int (total )) // 86400
301+
302+ return self
0 commit comments