Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/pendulum/duration.py : 35%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from __future__ import absolute_import
2from __future__ import division
4from datetime import timedelta
6import pendulum
8from pendulum.utils._compat import PYPY
9from pendulum.utils._compat import decode
11from .constants import SECONDS_PER_DAY
12from .constants import SECONDS_PER_HOUR
13from .constants import SECONDS_PER_MINUTE
14from .constants import US_PER_SECOND
17def _divide_and_round(a, b):
18 """divide a by b and round result to the nearest integer
20 When the ratio is exactly half-way between two integers,
21 the even integer is returned.
22 """
23 # Based on the reference implementation for divmod_near
24 # in Objects/longobject.c.
25 q, r = divmod(a, b)
26 # round up if either r / b > 0.5, or r / b == 0.5 and q is odd.
27 # The expression r / b > 0.5 is equivalent to 2 * r > b if b is
28 # positive, 2 * r < b if b negative.
29 r *= 2
30 greater_than_half = r > b if b > 0 else r < b
31 if greater_than_half or r == b and q % 2 == 1:
32 q += 1
34 return q
37class Duration(timedelta):
38 """
39 Replacement for the standard timedelta class.
41 Provides several improvements over the base class.
42 """
44 _y = None
45 _m = None
46 _w = None
47 _d = None
48 _h = None
49 _i = None
50 _s = None
51 _invert = None
53 def __new__(
54 cls,
55 days=0,
56 seconds=0,
57 microseconds=0,
58 milliseconds=0,
59 minutes=0,
60 hours=0,
61 weeks=0,
62 years=0,
63 months=0,
64 ):
65 if not isinstance(years, int) or not isinstance(months, int):
66 raise ValueError("Float year and months are not supported")
68 self = timedelta.__new__(
69 cls, days, seconds, microseconds, milliseconds, minutes, hours, weeks
70 )
72 # Intuitive normalization
73 total = self.total_seconds()
75 m = 1
76 if total < 0:
77 m = -1
79 self._microseconds = round(total % m * 1e6)
80 self._seconds = abs(int(total)) % SECONDS_PER_DAY * m
82 _days = abs(int(total)) // SECONDS_PER_DAY * m
83 self._days = _days + (years * 365 + months * 30)
84 self._remaining_days = abs(_days) % 7 * m
85 self._weeks = abs(_days) // 7 * m
86 self._months = months
87 self._years = years
89 return self
91 def total_minutes(self):
92 return self.total_seconds() / SECONDS_PER_MINUTE
94 def total_hours(self):
95 return self.total_seconds() / SECONDS_PER_HOUR
97 def total_days(self):
98 return self.total_seconds() / SECONDS_PER_DAY
100 def total_weeks(self):
101 return self.total_days() / 7
103 if PYPY:
105 def total_seconds(self):
106 if hasattr(self, "_remaining_days"):
107 days = self._weeks * 7 + self._remaining_days
108 else:
109 days = self._days
111 return (
112 (days * SECONDS_PER_DAY + self._seconds) * US_PER_SECOND
113 + self._microseconds
114 ) / US_PER_SECOND
116 @property
117 def years(self):
118 return self._years
120 @property
121 def months(self):
122 return self._months
124 @property
125 def weeks(self):
126 return self._weeks
128 @property
129 def days(self):
130 return self._days
132 @property
133 def remaining_days(self):
134 return self._remaining_days
136 @property
137 def hours(self):
138 if self._h is None:
139 seconds = self._seconds
140 self._h = 0
141 if abs(seconds) >= 3600:
142 self._h = (abs(seconds) // 3600 % 24) * self._sign(seconds)
144 return self._h
146 @property
147 def minutes(self):
148 if self._i is None:
149 seconds = self._seconds
150 self._i = 0
151 if abs(seconds) >= 60:
152 self._i = (abs(seconds) // 60 % 60) * self._sign(seconds)
154 return self._i
156 @property
157 def seconds(self):
158 return self._seconds
160 @property
161 def remaining_seconds(self):
162 if self._s is None:
163 self._s = self._seconds
164 self._s = abs(self._s) % 60 * self._sign(self._s)
166 return self._s
168 @property
169 def microseconds(self):
170 return self._microseconds
172 @property
173 def invert(self):
174 if self._invert is None:
175 self._invert = self.total_seconds() < 0
177 return self._invert
179 def in_weeks(self):
180 return int(self.total_weeks())
182 def in_days(self):
183 return int(self.total_days())
185 def in_hours(self):
186 return int(self.total_hours())
188 def in_minutes(self):
189 return int(self.total_minutes())
191 def in_seconds(self):
192 return int(self.total_seconds())
194 def in_words(self, locale=None, separator=" "):
195 """
196 Get the current interval in words in the current locale.
198 Ex: 6 jours 23 heures 58 minutes
200 :param locale: The locale to use. Defaults to current locale.
201 :type locale: str
203 :param separator: The separator to use between each unit
204 :type separator: str
206 :rtype: str
207 """
208 periods = [
209 ("year", self.years),
210 ("month", self.months),
211 ("week", self.weeks),
212 ("day", self.remaining_days),
213 ("hour", self.hours),
214 ("minute", self.minutes),
215 ("second", self.remaining_seconds),
216 ]
218 if locale is None:
219 locale = pendulum.get_locale()
221 locale = pendulum.locale(locale)
222 parts = []
223 for period in periods:
224 unit, count = period
225 if abs(count) > 0:
226 translation = locale.translation(
227 "units.{}.{}".format(unit, locale.plural(abs(count)))
228 )
229 parts.append(translation.format(count))
231 if not parts:
232 if abs(self.microseconds) > 0:
233 unit = "units.second.{}".format(locale.plural(1))
234 count = "{:.2f}".format(abs(self.microseconds) / 1e6)
235 else:
236 unit = "units.microsecond.{}".format(locale.plural(0))
237 count = 0
238 translation = locale.translation(unit)
239 parts.append(translation.format(count))
241 return decode(separator.join(parts))
243 def _sign(self, value):
244 if value < 0:
245 return -1
247 return 1
249 def as_timedelta(self):
250 """
251 Return the interval as a native timedelta.
253 :rtype: timedelta
254 """
255 return timedelta(seconds=self.total_seconds())
257 def __str__(self):
258 return self.in_words()
260 def __repr__(self):
261 rep = "{}(".format(self.__class__.__name__)
263 if self._years:
264 rep += "years={}, ".format(self._years)
266 if self._months:
267 rep += "months={}, ".format(self._months)
269 if self._weeks:
270 rep += "weeks={}, ".format(self._weeks)
272 if self._days:
273 rep += "days={}, ".format(self._remaining_days)
275 if self.hours:
276 rep += "hours={}, ".format(self.hours)
278 if self.minutes:
279 rep += "minutes={}, ".format(self.minutes)
281 if self.remaining_seconds:
282 rep += "seconds={}, ".format(self.remaining_seconds)
284 if self.microseconds:
285 rep += "microseconds={}, ".format(self.microseconds)
287 rep += ")"
289 return rep.replace(", )", ")")
291 def __add__(self, other):
292 if isinstance(other, timedelta):
293 return self.__class__(seconds=self.total_seconds() + other.total_seconds())
295 return NotImplemented
297 __radd__ = __add__
299 def __sub__(self, other):
300 if isinstance(other, timedelta):
301 return self.__class__(seconds=self.total_seconds() - other.total_seconds())
303 return NotImplemented
305 def __neg__(self):
306 return self.__class__(
307 years=-self._years,
308 months=-self._months,
309 weeks=-self._weeks,
310 days=-self._remaining_days,
311 seconds=-self._seconds,
312 microseconds=-self._microseconds,
313 )
315 def _to_microseconds(self):
316 return (self._days * (24 * 3600) + self._seconds) * 1000000 + self._microseconds
318 def __mul__(self, other):
319 if isinstance(other, int):
320 return self.__class__(
321 years=self._years * other,
322 months=self._months * other,
323 seconds=self.total_seconds() * other,
324 )
326 if isinstance(other, float):
327 usec = self._to_microseconds()
328 a, b = other.as_integer_ratio()
330 return self.__class__(0, 0, _divide_and_round(usec * a, b))
332 return NotImplemented
334 __rmul__ = __mul__
336 def __floordiv__(self, other):
337 if not isinstance(other, (int, timedelta)):
338 return NotImplemented
340 usec = self._to_microseconds()
341 if isinstance(other, timedelta):
342 return usec // other._to_microseconds()
344 # Removing years/months approximation
345 usec -= (self._years * 365 + self._months * 30) * SECONDS_PER_DAY * 1e6
347 if isinstance(other, int):
348 return self.__class__(
349 0,
350 0,
351 usec // other,
352 years=self._years // other,
353 months=self._months // other,
354 )
356 def __truediv__(self, other):
357 if not isinstance(other, (int, float, timedelta)):
358 return NotImplemented
360 usec = self._to_microseconds()
361 if isinstance(other, timedelta):
362 return usec / other._to_microseconds()
364 # Removing years/months approximation
365 usec -= (self._years * 365 + self._months * 30) * SECONDS_PER_DAY * 1e6
367 if isinstance(other, int):
368 return self.__class__(
369 0,
370 0,
371 _divide_and_round(usec, other),
372 years=_divide_and_round(self._years, other),
373 months=_divide_and_round(self._months, other),
374 )
376 if isinstance(other, float):
377 a, b = other.as_integer_ratio()
379 return self.__class__(
380 0,
381 0,
382 _divide_and_round(b * usec, a),
383 years=_divide_and_round(self._years * b, a),
384 months=_divide_and_round(self._months, other),
385 )
387 __div__ = __floordiv__
389 def __mod__(self, other):
390 if isinstance(other, timedelta):
391 r = self._to_microseconds() % other._to_microseconds()
393 return self.__class__(0, 0, r)
395 return NotImplemented
397 def __divmod__(self, other):
398 if isinstance(other, timedelta):
399 q, r = divmod(self._to_microseconds(), other._to_microseconds())
401 return q, self.__class__(0, 0, r)
403 return NotImplemented
406Duration.min = Duration(days=-999999999)
407Duration.max = Duration(
408 days=999999999, hours=23, minutes=59, seconds=59, microseconds=999999
409)
410Duration.resolution = Duration(microseconds=1)
413class AbsoluteDuration(Duration):
414 """
415 Duration that expresses a time difference in absolute values.
416 """
418 def __new__(
419 cls,
420 days=0,
421 seconds=0,
422 microseconds=0,
423 milliseconds=0,
424 minutes=0,
425 hours=0,
426 weeks=0,
427 years=0,
428 months=0,
429 ):
430 if not isinstance(years, int) or not isinstance(months, int):
431 raise ValueError("Float year and months are not supported")
433 self = timedelta.__new__(
434 cls, days, seconds, microseconds, milliseconds, minutes, hours, weeks
435 )
437 # We need to compute the total_seconds() value
438 # on a native timedelta object
439 delta = timedelta(
440 days, seconds, microseconds, milliseconds, minutes, hours, weeks
441 )
443 # Intuitive normalization
444 self._total = delta.total_seconds()
445 total = abs(self._total)
447 self._microseconds = round(total % 1 * 1e6)
448 self._seconds = int(total) % SECONDS_PER_DAY
450 days = int(total) // SECONDS_PER_DAY
451 self._days = abs(days + years * 365 + months * 30)
452 self._remaining_days = days % 7
453 self._weeks = days // 7
454 self._months = abs(months)
455 self._years = abs(years)
457 return self
459 def total_seconds(self):
460 return abs(self._total)
462 @property
463 def invert(self):
464 if self._invert is None:
465 self._invert = self._total < 0
467 return self._invert