Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pendulum/duration.py: 37%
249 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
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,
70 days + years * 365 + months * 30,
71 seconds,
72 microseconds,
73 milliseconds,
74 minutes,
75 hours,
76 weeks,
77 )
79 # Intuitive normalization
80 total = self.total_seconds() - (years * 365 + months * 30) * SECONDS_PER_DAY
81 self._total = total
83 m = 1
84 if total < 0:
85 m = -1
87 self._microseconds = round(total % m * 1e6)
88 self._seconds = abs(int(total)) % SECONDS_PER_DAY * m
90 _days = abs(int(total)) // SECONDS_PER_DAY * m
91 self._days = _days
92 self._remaining_days = abs(_days) % 7 * m
93 self._weeks = abs(_days) // 7 * m
94 self._months = months
95 self._years = years
97 return self
99 def total_minutes(self):
100 return self.total_seconds() / SECONDS_PER_MINUTE
102 def total_hours(self):
103 return self.total_seconds() / SECONDS_PER_HOUR
105 def total_days(self):
106 return self.total_seconds() / SECONDS_PER_DAY
108 def total_weeks(self):
109 return self.total_days() / 7
111 if PYPY:
113 def total_seconds(self):
114 days = 0
116 if hasattr(self, "_years"):
117 days += self._years * 365
119 if hasattr(self, "_months"):
120 days += self._months * 30
122 if hasattr(self, "_remaining_days"):
123 days += self._weeks * 7 + self._remaining_days
124 else:
125 days += self._days
127 return (
128 (days * SECONDS_PER_DAY + self._seconds) * US_PER_SECOND
129 + self._microseconds
130 ) / US_PER_SECOND
132 @property
133 def years(self):
134 return self._years
136 @property
137 def months(self):
138 return self._months
140 @property
141 def weeks(self):
142 return self._weeks
144 if PYPY:
146 @property
147 def days(self):
148 return self._years * 365 + self._months * 30 + self._days
150 @property
151 def remaining_days(self):
152 return self._remaining_days
154 @property
155 def hours(self):
156 if self._h is None:
157 seconds = self._seconds
158 self._h = 0
159 if abs(seconds) >= 3600:
160 self._h = (abs(seconds) // 3600 % 24) * self._sign(seconds)
162 return self._h
164 @property
165 def minutes(self):
166 if self._i is None:
167 seconds = self._seconds
168 self._i = 0
169 if abs(seconds) >= 60:
170 self._i = (abs(seconds) // 60 % 60) * self._sign(seconds)
172 return self._i
174 @property
175 def seconds(self):
176 return self._seconds
178 @property
179 def remaining_seconds(self):
180 if self._s is None:
181 self._s = self._seconds
182 self._s = abs(self._s) % 60 * self._sign(self._s)
184 return self._s
186 @property
187 def microseconds(self):
188 return self._microseconds
190 @property
191 def invert(self):
192 if self._invert is None:
193 self._invert = self.total_seconds() < 0
195 return self._invert
197 def in_weeks(self):
198 return int(self.total_weeks())
200 def in_days(self):
201 return int(self.total_days())
203 def in_hours(self):
204 return int(self.total_hours())
206 def in_minutes(self):
207 return int(self.total_minutes())
209 def in_seconds(self):
210 return int(self.total_seconds())
212 def in_words(self, locale=None, separator=" "):
213 """
214 Get the current interval in words in the current locale.
216 Ex: 6 jours 23 heures 58 minutes
218 :param locale: The locale to use. Defaults to current locale.
219 :type locale: str
221 :param separator: The separator to use between each unit
222 :type separator: str
224 :rtype: str
225 """
226 periods = [
227 ("year", self.years),
228 ("month", self.months),
229 ("week", self.weeks),
230 ("day", self.remaining_days),
231 ("hour", self.hours),
232 ("minute", self.minutes),
233 ("second", self.remaining_seconds),
234 ]
236 if locale is None:
237 locale = pendulum.get_locale()
239 locale = pendulum.locale(locale)
240 parts = []
241 for period in periods:
242 unit, count = period
243 if abs(count) > 0:
244 translation = locale.translation(
245 "units.{}.{}".format(unit, locale.plural(abs(count)))
246 )
247 parts.append(translation.format(count))
249 if not parts:
250 if abs(self.microseconds) > 0:
251 unit = "units.second.{}".format(locale.plural(1))
252 count = "{:.2f}".format(abs(self.microseconds) / 1e6)
253 else:
254 unit = "units.microsecond.{}".format(locale.plural(0))
255 count = 0
256 translation = locale.translation(unit)
257 parts.append(translation.format(count))
259 return decode(separator.join(parts))
261 def _sign(self, value):
262 if value < 0:
263 return -1
265 return 1
267 def as_timedelta(self):
268 """
269 Return the interval as a native timedelta.
271 :rtype: timedelta
272 """
273 return timedelta(seconds=self.total_seconds())
275 def __str__(self):
276 return self.in_words()
278 def __repr__(self):
279 rep = "{}(".format(self.__class__.__name__)
281 if self._years:
282 rep += "years={}, ".format(self._years)
284 if self._months:
285 rep += "months={}, ".format(self._months)
287 if self._weeks:
288 rep += "weeks={}, ".format(self._weeks)
290 if self._days:
291 rep += "days={}, ".format(self._remaining_days)
293 if self.hours:
294 rep += "hours={}, ".format(self.hours)
296 if self.minutes:
297 rep += "minutes={}, ".format(self.minutes)
299 if self.remaining_seconds:
300 rep += "seconds={}, ".format(self.remaining_seconds)
302 if self.microseconds:
303 rep += "microseconds={}, ".format(self.microseconds)
305 rep += ")"
307 return rep.replace(", )", ")")
309 def __add__(self, other):
310 if isinstance(other, timedelta):
311 return self.__class__(seconds=self.total_seconds() + other.total_seconds())
313 return NotImplemented
315 __radd__ = __add__
317 def __sub__(self, other):
318 if isinstance(other, timedelta):
319 return self.__class__(seconds=self.total_seconds() - other.total_seconds())
321 return NotImplemented
323 def __neg__(self):
324 return self.__class__(
325 years=-self._years,
326 months=-self._months,
327 weeks=-self._weeks,
328 days=-self._remaining_days,
329 seconds=-self._seconds,
330 microseconds=-self._microseconds,
331 )
333 def _to_microseconds(self):
334 return (self._days * (24 * 3600) + self._seconds) * 1000000 + self._microseconds
336 def __mul__(self, other):
337 if isinstance(other, int):
338 return self.__class__(
339 years=self._years * other,
340 months=self._months * other,
341 seconds=self._total * other,
342 )
344 if isinstance(other, float):
345 usec = self._to_microseconds()
346 a, b = other.as_integer_ratio()
348 return self.__class__(0, 0, _divide_and_round(usec * a, b))
350 return NotImplemented
352 __rmul__ = __mul__
354 def __floordiv__(self, other):
355 if not isinstance(other, (int, timedelta)):
356 return NotImplemented
358 usec = self._to_microseconds()
359 if isinstance(other, timedelta):
360 return usec // other._to_microseconds()
362 if isinstance(other, int):
363 return self.__class__(
364 0,
365 0,
366 usec // other,
367 years=self._years // other,
368 months=self._months // other,
369 )
371 def __truediv__(self, other):
372 if not isinstance(other, (int, float, timedelta)):
373 return NotImplemented
375 usec = self._to_microseconds()
376 if isinstance(other, timedelta):
377 return usec / other._to_microseconds()
379 if isinstance(other, int):
380 return self.__class__(
381 0,
382 0,
383 _divide_and_round(usec, other),
384 years=_divide_and_round(self._years, other),
385 months=_divide_and_round(self._months, other),
386 )
388 if isinstance(other, float):
389 a, b = other.as_integer_ratio()
391 return self.__class__(
392 0,
393 0,
394 _divide_and_round(b * usec, a),
395 years=_divide_and_round(self._years * b, a),
396 months=_divide_and_round(self._months, other),
397 )
399 __div__ = __floordiv__
401 def __mod__(self, other):
402 if isinstance(other, timedelta):
403 r = self._to_microseconds() % other._to_microseconds()
405 return self.__class__(0, 0, r)
407 return NotImplemented
409 def __divmod__(self, other):
410 if isinstance(other, timedelta):
411 q, r = divmod(self._to_microseconds(), other._to_microseconds())
413 return q, self.__class__(0, 0, r)
415 return NotImplemented
418Duration.min = Duration(days=-999999999)
419Duration.max = Duration(
420 days=999999999, hours=23, minutes=59, seconds=59, microseconds=999999
421)
422Duration.resolution = Duration(microseconds=1)
425class AbsoluteDuration(Duration):
426 """
427 Duration that expresses a time difference in absolute values.
428 """
430 def __new__(
431 cls,
432 days=0,
433 seconds=0,
434 microseconds=0,
435 milliseconds=0,
436 minutes=0,
437 hours=0,
438 weeks=0,
439 years=0,
440 months=0,
441 ):
442 if not isinstance(years, int) or not isinstance(months, int):
443 raise ValueError("Float year and months are not supported")
445 self = timedelta.__new__(
446 cls, days, seconds, microseconds, milliseconds, minutes, hours, weeks
447 )
449 # We need to compute the total_seconds() value
450 # on a native timedelta object
451 delta = timedelta(
452 days, seconds, microseconds, milliseconds, minutes, hours, weeks
453 )
455 # Intuitive normalization
456 self._total = delta.total_seconds()
457 total = abs(self._total)
459 self._microseconds = round(total % 1 * 1e6)
460 self._seconds = int(total) % SECONDS_PER_DAY
462 days = int(total) // SECONDS_PER_DAY
463 self._days = abs(days + years * 365 + months * 30)
464 self._remaining_days = days % 7
465 self._weeks = days // 7
466 self._months = abs(months)
467 self._years = abs(years)
469 return self
471 def total_seconds(self):
472 return abs(self._total)
474 @property
475 def invert(self):
476 if self._invert is None:
477 self._invert = self._total < 0
479 return self._invert