Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pendulum/interval.py: 55%
216 statements
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-30 06:11 +0000
« prev ^ index » next coverage.py v7.3.1, created at 2023-09-30 06:11 +0000
1from __future__ import annotations
3import operator
5from datetime import date
6from datetime import datetime
7from datetime import timedelta
8from typing import TYPE_CHECKING
9from typing import Iterator
10from typing import Union
11from typing import cast
12from typing import overload
14import pendulum
16from pendulum.constants import MONTHS_PER_YEAR
17from pendulum.duration import Duration
18from pendulum.helpers import precise_diff
21if TYPE_CHECKING:
22 from typing_extensions import Self
23 from typing_extensions import SupportsIndex
25 from pendulum.helpers import PreciseDiff
26 from pendulum.locales.locale import Locale
29class Interval(Duration):
30 """
31 A period of time between two datetimes.
32 """
34 @overload
35 def __new__(
36 cls,
37 start: pendulum.DateTime | datetime,
38 end: pendulum.DateTime | datetime,
39 absolute: bool = False,
40 ) -> Self:
41 ...
43 @overload
44 def __new__(
45 cls,
46 start: pendulum.Date | date,
47 end: pendulum.Date | date,
48 absolute: bool = False,
49 ) -> Self:
50 ...
52 def __new__(
53 cls,
54 start: pendulum.DateTime | pendulum.Date | datetime | date,
55 end: pendulum.DateTime | pendulum.Date | datetime | date,
56 absolute: bool = False,
57 ) -> Self:
58 if (
59 isinstance(start, datetime)
60 and not isinstance(end, datetime)
61 or not isinstance(start, datetime)
62 and isinstance(end, datetime)
63 ):
64 raise ValueError("Both start and end of a Period must have the same type")
66 if (
67 isinstance(start, datetime)
68 and isinstance(end, datetime)
69 and (
70 start.tzinfo is None
71 and end.tzinfo is not None
72 or start.tzinfo is not None
73 and end.tzinfo is None
74 )
75 ):
76 raise TypeError("can't compare offset-naive and offset-aware datetimes")
78 if absolute and start > end:
79 end, start = start, end
81 _start = start
82 _end = end
83 if isinstance(start, pendulum.DateTime):
84 _start = datetime(
85 start.year,
86 start.month,
87 start.day,
88 start.hour,
89 start.minute,
90 start.second,
91 start.microsecond,
92 tzinfo=start.tzinfo,
93 fold=start.fold,
94 )
95 elif isinstance(start, pendulum.Date):
96 _start = date(start.year, start.month, start.day)
98 if isinstance(end, pendulum.DateTime):
99 _end = datetime(
100 end.year,
101 end.month,
102 end.day,
103 end.hour,
104 end.minute,
105 end.second,
106 end.microsecond,
107 tzinfo=end.tzinfo,
108 fold=end.fold,
109 )
110 elif isinstance(end, pendulum.Date):
111 _end = date(end.year, end.month, end.day)
113 # Fixing issues with datetime.__sub__()
114 # not handling offsets if the tzinfo is the same
115 if (
116 isinstance(_start, datetime)
117 and isinstance(_end, datetime)
118 and _start.tzinfo is _end.tzinfo
119 ):
120 if _start.tzinfo is not None:
121 offset = cast(timedelta, cast(datetime, start).utcoffset())
122 _start = (_start - offset).replace(tzinfo=None)
124 if isinstance(end, datetime) and _end.tzinfo is not None:
125 offset = cast(timedelta, end.utcoffset())
126 _end = (_end - offset).replace(tzinfo=None)
128 delta: timedelta = _end - _start # type: ignore[operator]
130 return super().__new__(cls, seconds=delta.total_seconds())
132 def __init__(
133 self,
134 start: pendulum.DateTime | pendulum.Date | datetime | date,
135 end: pendulum.DateTime | pendulum.Date | datetime | date,
136 absolute: bool = False,
137 ) -> None:
138 super().__init__()
140 _start: pendulum.DateTime | pendulum.Date | datetime | date
141 if not isinstance(start, pendulum.Date):
142 if isinstance(start, datetime):
143 start = pendulum.instance(start)
144 else:
145 start = pendulum.date(start.year, start.month, start.day)
147 _start = start
148 else:
149 if isinstance(start, pendulum.DateTime):
150 _start = datetime(
151 start.year,
152 start.month,
153 start.day,
154 start.hour,
155 start.minute,
156 start.second,
157 start.microsecond,
158 tzinfo=start.tzinfo,
159 )
160 else:
161 _start = date(start.year, start.month, start.day)
163 _end: pendulum.DateTime | pendulum.Date | datetime | date
164 if not isinstance(end, pendulum.Date):
165 if isinstance(end, datetime):
166 end = pendulum.instance(end)
167 else:
168 end = pendulum.date(end.year, end.month, end.day)
170 _end = end
171 else:
172 if isinstance(end, pendulum.DateTime):
173 _end = datetime(
174 end.year,
175 end.month,
176 end.day,
177 end.hour,
178 end.minute,
179 end.second,
180 end.microsecond,
181 tzinfo=end.tzinfo,
182 )
183 else:
184 _end = date(end.year, end.month, end.day)
186 self._invert = False
187 if start > end:
188 self._invert = True
190 if absolute:
191 end, start = start, end
192 _end, _start = _start, _end
194 self._absolute = absolute
195 self._start: pendulum.DateTime | pendulum.Date = start
196 self._end: pendulum.DateTime | pendulum.Date = end
197 self._delta: PreciseDiff = precise_diff(_start, _end)
199 @property
200 def years(self) -> int:
201 return self._delta.years
203 @property
204 def months(self) -> int:
205 return self._delta.months
207 @property
208 def weeks(self) -> int:
209 return abs(self._delta.days) // 7 * self._sign(self._delta.days)
211 @property
212 def days(self) -> int:
213 return self._days
215 @property
216 def remaining_days(self) -> int:
217 return abs(self._delta.days) % 7 * self._sign(self._days)
219 @property
220 def hours(self) -> int:
221 return self._delta.hours
223 @property
224 def minutes(self) -> int:
225 return self._delta.minutes
227 @property
228 def start(self) -> pendulum.DateTime | pendulum.Date | datetime | date:
229 return self._start
231 @property
232 def end(self) -> pendulum.DateTime | pendulum.Date | datetime | date:
233 return self._end
235 def in_years(self) -> int:
236 """
237 Gives the duration of the Period in full years.
238 """
239 return self.years
241 def in_months(self) -> int:
242 """
243 Gives the duration of the Period in full months.
244 """
245 return self.years * MONTHS_PER_YEAR + self.months
247 def in_weeks(self) -> int:
248 days = self.in_days()
249 sign = 1
251 if days < 0:
252 sign = -1
254 return sign * (abs(days) // 7)
256 def in_days(self) -> int:
257 return self._delta.total_days
259 def in_words(self, locale: str | None = None, separator: str = " ") -> str:
260 """
261 Get the current interval in words in the current locale.
263 Ex: 6 jours 23 heures 58 minutes
265 :param locale: The locale to use. Defaults to current locale.
266 :param separator: The separator to use between each unit
267 """
268 from pendulum.locales.locale import Locale
270 periods = [
271 ("year", self.years),
272 ("month", self.months),
273 ("week", self.weeks),
274 ("day", self.remaining_days),
275 ("hour", self.hours),
276 ("minute", self.minutes),
277 ("second", self.remaining_seconds),
278 ]
279 loaded_locale: Locale = Locale.load(locale or pendulum.get_locale())
280 parts = []
281 for period in periods:
282 unit, period_count = period
283 if abs(period_count) > 0:
284 translation = loaded_locale.translation(
285 f"units.{unit}.{loaded_locale.plural(abs(period_count))}"
286 )
287 parts.append(translation.format(period_count))
289 if not parts:
290 count: str | int = 0
291 if abs(self.microseconds) > 0:
292 unit = f"units.second.{loaded_locale.plural(1)}"
293 count = f"{abs(self.microseconds) / 1e6:.2f}"
294 else:
295 unit = f"units.microsecond.{loaded_locale.plural(0)}"
297 translation = loaded_locale.translation(unit)
298 parts.append(translation.format(count))
300 return separator.join(parts)
302 def range(
303 self, unit: str, amount: int = 1
304 ) -> Iterator[pendulum.DateTime | pendulum.Date]:
305 method = "add"
306 op = operator.le
307 if not self._absolute and self.invert:
308 method = "subtract"
309 op = operator.ge
311 start, end = self.start, self.end
313 i = amount
314 while op(start, end):
315 yield cast(Union[pendulum.DateTime, pendulum.Date], start)
317 start = getattr(self.start, method)(**{unit: i})
319 i += amount
321 def as_duration(self) -> Duration:
322 """
323 Return the Interval as a Duration.
324 """
325 return Duration(seconds=self.total_seconds())
327 def __iter__(self) -> Iterator[pendulum.DateTime | pendulum.Date]:
328 return self.range("days")
330 def __contains__(
331 self, item: datetime | date | pendulum.DateTime | pendulum.Date
332 ) -> bool:
333 return self.start <= item <= self.end
335 def __add__(self, other: timedelta) -> Duration: # type: ignore[override]
336 return self.as_duration().__add__(other)
338 __radd__ = __add__ # type: ignore[assignment]
340 def __sub__(self, other: timedelta) -> Duration: # type: ignore[override]
341 return self.as_duration().__sub__(other)
343 def __neg__(self) -> Self:
344 return self.__class__(self.end, self.start, self._absolute)
346 def __mul__(self, other: int | float) -> Duration: # type: ignore[override]
347 return self.as_duration().__mul__(other)
349 __rmul__ = __mul__ # type: ignore[assignment]
351 @overload # type: ignore[override]
352 def __floordiv__(self, other: timedelta) -> int:
353 ...
355 @overload
356 def __floordiv__(self, other: int) -> Duration:
357 ...
359 def __floordiv__(self, other: int | timedelta) -> int | Duration:
360 return self.as_duration().__floordiv__(other)
362 __div__ = __floordiv__ # type: ignore[assignment]
364 @overload # type: ignore[override]
365 def __truediv__(self, other: timedelta) -> float:
366 ...
368 @overload
369 def __truediv__(self, other: float) -> Duration:
370 ...
372 def __truediv__(self, other: float | timedelta) -> Duration | float:
373 return self.as_duration().__truediv__(other)
375 def __mod__(self, other: timedelta) -> Duration: # type: ignore[override]
376 return self.as_duration().__mod__(other)
378 def __divmod__(self, other: timedelta) -> tuple[int, Duration]:
379 return self.as_duration().__divmod__(other)
381 def __abs__(self) -> Self:
382 return self.__class__(self.start, self.end, absolute=True)
384 def __repr__(self) -> str:
385 return f"<Period [{self._start} -> {self._end}]>"
387 def __str__(self) -> str:
388 return self.__repr__()
390 def _cmp(self, other: timedelta) -> int:
391 # Only needed for PyPy
392 assert isinstance(other, timedelta)
394 if isinstance(other, Interval):
395 other = other.as_timedelta()
397 td = self.as_timedelta()
399 return 0 if td == other else 1 if td > other else -1
401 def _getstate(
402 self, protocol: SupportsIndex = 3
403 ) -> tuple[
404 pendulum.DateTime | pendulum.Date | datetime | date,
405 pendulum.DateTime | pendulum.Date | datetime | date,
406 bool,
407 ]:
408 start, end = self.start, self.end
410 if self._invert and self._absolute:
411 end, start = start, end
413 return start, end, self._absolute
415 def __reduce__(
416 self,
417 ) -> tuple[
418 type[Self],
419 tuple[
420 pendulum.DateTime | pendulum.Date | datetime | date,
421 pendulum.DateTime | pendulum.Date | datetime | date,
422 bool,
423 ],
424 ]:
425 return self.__reduce_ex__(2)
427 def __reduce_ex__(
428 self, protocol: SupportsIndex
429 ) -> tuple[
430 type[Self],
431 tuple[
432 pendulum.DateTime | pendulum.Date | datetime | date,
433 pendulum.DateTime | pendulum.Date | datetime | date,
434 bool,
435 ],
436 ]:
437 return self.__class__, self._getstate(protocol)
439 def __hash__(self) -> int:
440 return hash((self.start, self.end, self._absolute))
442 def __eq__(self, other: object) -> bool:
443 if isinstance(other, Interval):
444 return (self.start, self.end, self._absolute) == (
445 other.start,
446 other.end,
447 other._absolute,
448 )
449 else:
450 return self.as_duration() == other
452 def __ne__(self, other: object) -> bool:
453 return not self.__eq__(other)