Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pendulum/interval.py: 36%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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 An interval 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(
65 "Both start and end of an Interval must have the same type"
66 )
68 if (
69 isinstance(start, datetime)
70 and isinstance(end, datetime)
71 and (
72 start.tzinfo is None
73 and end.tzinfo is not None
74 or start.tzinfo is not None
75 and end.tzinfo is None
76 )
77 ):
78 raise TypeError("can't compare offset-naive and offset-aware datetimes")
80 if absolute and start > end:
81 end, start = start, end
83 _start = start
84 _end = end
85 if isinstance(start, pendulum.DateTime):
86 _start = datetime(
87 start.year,
88 start.month,
89 start.day,
90 start.hour,
91 start.minute,
92 start.second,
93 start.microsecond,
94 tzinfo=start.tzinfo,
95 fold=start.fold,
96 )
97 elif isinstance(start, pendulum.Date):
98 _start = date(start.year, start.month, start.day)
100 if isinstance(end, pendulum.DateTime):
101 _end = datetime(
102 end.year,
103 end.month,
104 end.day,
105 end.hour,
106 end.minute,
107 end.second,
108 end.microsecond,
109 tzinfo=end.tzinfo,
110 fold=end.fold,
111 )
112 elif isinstance(end, pendulum.Date):
113 _end = date(end.year, end.month, end.day)
115 # Fixing issues with datetime.__sub__()
116 # not handling offsets if the tzinfo is the same
117 if (
118 isinstance(_start, datetime)
119 and isinstance(_end, datetime)
120 and _start.tzinfo is _end.tzinfo
121 ):
122 if _start.tzinfo is not None:
123 offset = cast(timedelta, cast(datetime, start).utcoffset())
124 _start = (_start - offset).replace(tzinfo=None)
126 if isinstance(end, datetime) and _end.tzinfo is not None:
127 offset = cast(timedelta, end.utcoffset())
128 _end = (_end - offset).replace(tzinfo=None)
130 delta: timedelta = _end - _start # type: ignore[operator]
132 return super().__new__(cls, seconds=delta.total_seconds())
134 def __init__(
135 self,
136 start: pendulum.DateTime | pendulum.Date | datetime | date,
137 end: pendulum.DateTime | pendulum.Date | datetime | date,
138 absolute: bool = False,
139 ) -> None:
140 super().__init__()
142 _start: pendulum.DateTime | pendulum.Date | datetime | date
143 if not isinstance(start, pendulum.Date):
144 if isinstance(start, datetime):
145 start = pendulum.instance(start)
146 else:
147 start = pendulum.date(start.year, start.month, start.day)
149 _start = start
150 else:
151 if isinstance(start, pendulum.DateTime):
152 _start = datetime(
153 start.year,
154 start.month,
155 start.day,
156 start.hour,
157 start.minute,
158 start.second,
159 start.microsecond,
160 tzinfo=start.tzinfo,
161 )
162 else:
163 _start = date(start.year, start.month, start.day)
165 _end: pendulum.DateTime | pendulum.Date | datetime | date
166 if not isinstance(end, pendulum.Date):
167 if isinstance(end, datetime):
168 end = pendulum.instance(end)
169 else:
170 end = pendulum.date(end.year, end.month, end.day)
172 _end = end
173 else:
174 if isinstance(end, pendulum.DateTime):
175 _end = datetime(
176 end.year,
177 end.month,
178 end.day,
179 end.hour,
180 end.minute,
181 end.second,
182 end.microsecond,
183 tzinfo=end.tzinfo,
184 )
185 else:
186 _end = date(end.year, end.month, end.day)
188 self._invert = False
189 if start > end:
190 self._invert = True
192 if absolute:
193 end, start = start, end
194 _end, _start = _start, _end
196 self._absolute = absolute
197 self._start: pendulum.DateTime | pendulum.Date = start
198 self._end: pendulum.DateTime | pendulum.Date = end
199 self._delta: PreciseDiff = precise_diff(_start, _end)
201 @property
202 def years(self) -> int:
203 return self._delta.years
205 @property
206 def months(self) -> int:
207 return self._delta.months
209 @property
210 def weeks(self) -> int:
211 return abs(self._delta.days) // 7 * self._sign(self._delta.days)
213 @property
214 def days(self) -> int:
215 return self._days
217 @property
218 def remaining_days(self) -> int:
219 return abs(self._delta.days) % 7 * self._sign(self._days)
221 @property
222 def hours(self) -> int:
223 return self._delta.hours
225 @property
226 def minutes(self) -> int:
227 return self._delta.minutes
229 @property
230 def start(self) -> pendulum.DateTime | pendulum.Date | datetime | date:
231 return self._start
233 @property
234 def end(self) -> pendulum.DateTime | pendulum.Date | datetime | date:
235 return self._end
237 def in_years(self) -> int:
238 """
239 Gives the duration of the Interval in full years.
240 """
241 return self.years
243 def in_months(self) -> int:
244 """
245 Gives the duration of the Interval in full months.
246 """
247 return self.years * MONTHS_PER_YEAR + self.months
249 def in_weeks(self) -> int:
250 days = self.in_days()
251 sign = 1
253 if days < 0:
254 sign = -1
256 return sign * (abs(days) // 7)
258 def in_days(self) -> int:
259 return self._delta.total_days
261 def in_words(self, locale: str | None = None, separator: str = " ") -> str:
262 """
263 Get the current interval in words in the current locale.
265 Ex: 6 jours 23 heures 58 minutes
267 :param locale: The locale to use. Defaults to current locale.
268 :param separator: The separator to use between each unit
269 """
270 from pendulum.locales.locale import Locale
272 intervals = [
273 ("year", self.years),
274 ("month", self.months),
275 ("week", self.weeks),
276 ("day", self.remaining_days),
277 ("hour", self.hours),
278 ("minute", self.minutes),
279 ("second", self.remaining_seconds),
280 ]
281 loaded_locale: Locale = Locale.load(locale or pendulum.get_locale())
282 parts = []
283 for interval in intervals:
284 unit, interval_count = interval
285 if abs(interval_count) > 0:
286 translation = loaded_locale.translation(
287 f"units.{unit}.{loaded_locale.plural(abs(interval_count))}"
288 )
289 parts.append(translation.format(interval_count))
291 if not parts:
292 count: str | int = 0
293 if abs(self.microseconds) > 0:
294 unit = f"units.second.{loaded_locale.plural(1)}"
295 count = f"{abs(self.microseconds) / 1e6:.2f}"
296 else:
297 unit = f"units.microsecond.{loaded_locale.plural(0)}"
299 translation = loaded_locale.translation(unit)
300 parts.append(translation.format(count))
302 return separator.join(parts)
304 def range(
305 self, unit: str, amount: int = 1
306 ) -> Iterator[pendulum.DateTime | pendulum.Date]:
307 method = "add"
308 op = operator.le
309 if not self._absolute and self.invert:
310 method = "subtract"
311 op = operator.ge
313 start, end = self.start, self.end
315 i = amount
316 while op(start, end):
317 yield cast(Union[pendulum.DateTime, pendulum.Date], start)
319 start = getattr(self.start, method)(**{unit: i})
321 i += amount
323 def as_duration(self) -> Duration:
324 """
325 Return the Interval as a Duration.
326 """
327 return Duration(seconds=self.total_seconds())
329 def __iter__(self) -> Iterator[pendulum.DateTime | pendulum.Date]:
330 return self.range("days")
332 def __contains__(
333 self, item: datetime | date | pendulum.DateTime | pendulum.Date
334 ) -> bool:
335 return self.start <= item <= self.end
337 def __add__(self, other: timedelta) -> Duration: # type: ignore[override]
338 return self.as_duration().__add__(other)
340 __radd__ = __add__ # type: ignore[assignment]
342 def __sub__(self, other: timedelta) -> Duration: # type: ignore[override]
343 return self.as_duration().__sub__(other)
345 def __neg__(self) -> Self:
346 return self.__class__(self.end, self.start, self._absolute)
348 def __mul__(self, other: int | float) -> Duration: # type: ignore[override]
349 return self.as_duration().__mul__(other)
351 __rmul__ = __mul__ # type: ignore[assignment]
353 @overload # type: ignore[override]
354 def __floordiv__(self, other: timedelta) -> int:
355 ...
357 @overload
358 def __floordiv__(self, other: int) -> Duration:
359 ...
361 def __floordiv__(self, other: int | timedelta) -> int | Duration:
362 return self.as_duration().__floordiv__(other)
364 __div__ = __floordiv__ # type: ignore[assignment]
366 @overload # type: ignore[override]
367 def __truediv__(self, other: timedelta) -> float:
368 ...
370 @overload
371 def __truediv__(self, other: float) -> Duration:
372 ...
374 def __truediv__(self, other: float | timedelta) -> Duration | float:
375 return self.as_duration().__truediv__(other)
377 def __mod__(self, other: timedelta) -> Duration: # type: ignore[override]
378 return self.as_duration().__mod__(other)
380 def __divmod__(self, other: timedelta) -> tuple[int, Duration]:
381 return self.as_duration().__divmod__(other)
383 def __abs__(self) -> Self:
384 return self.__class__(self.start, self.end, absolute=True)
386 def __repr__(self) -> str:
387 return f"<Interval [{self._start} -> {self._end}]>"
389 def __str__(self) -> str:
390 return self.__repr__()
392 def _cmp(self, other: timedelta) -> int:
393 # Only needed for PyPy
394 assert isinstance(other, timedelta)
396 if isinstance(other, Interval):
397 other = other.as_timedelta()
399 td = self.as_timedelta()
401 return 0 if td == other else 1 if td > other else -1
403 def _getstate(
404 self, protocol: SupportsIndex = 3
405 ) -> tuple[
406 pendulum.DateTime | pendulum.Date | datetime | date,
407 pendulum.DateTime | pendulum.Date | datetime | date,
408 bool,
409 ]:
410 start, end = self.start, self.end
412 if self._invert and self._absolute:
413 end, start = start, end
415 return start, end, self._absolute
417 def __reduce__(
418 self,
419 ) -> tuple[
420 type[Self],
421 tuple[
422 pendulum.DateTime | pendulum.Date | datetime | date,
423 pendulum.DateTime | pendulum.Date | datetime | date,
424 bool,
425 ],
426 ]:
427 return self.__reduce_ex__(2)
429 def __reduce_ex__(
430 self, protocol: SupportsIndex
431 ) -> tuple[
432 type[Self],
433 tuple[
434 pendulum.DateTime | pendulum.Date | datetime | date,
435 pendulum.DateTime | pendulum.Date | datetime | date,
436 bool,
437 ],
438 ]:
439 return self.__class__, self._getstate(protocol)
441 def __hash__(self) -> int:
442 return hash((self.start, self.end, self._absolute))
444 def __eq__(self, other: object) -> bool:
445 if isinstance(other, Interval):
446 return (self.start, self.end, self._absolute) == (
447 other.start,
448 other.end,
449 other._absolute,
450 )
451 else:
452 return self.as_duration() == other
454 def __ne__(self, other: object) -> bool:
455 return not self.__eq__(other)