Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pendulum/datetime.py: 41%
513 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 calendar
4import datetime
5import traceback
7from typing import TYPE_CHECKING
8from typing import Any
9from typing import Callable
10from typing import ClassVar
11from typing import Optional
12from typing import cast
13from typing import overload
15import pendulum
17from pendulum.constants import ATOM
18from pendulum.constants import COOKIE
19from pendulum.constants import MINUTES_PER_HOUR
20from pendulum.constants import MONTHS_PER_YEAR
21from pendulum.constants import RFC822
22from pendulum.constants import RFC850
23from pendulum.constants import RFC1036
24from pendulum.constants import RFC1123
25from pendulum.constants import RFC2822
26from pendulum.constants import RSS
27from pendulum.constants import SECONDS_PER_DAY
28from pendulum.constants import SECONDS_PER_MINUTE
29from pendulum.constants import W3C
30from pendulum.constants import YEARS_PER_CENTURY
31from pendulum.constants import YEARS_PER_DECADE
32from pendulum.date import Date
33from pendulum.day import WeekDay
34from pendulum.exceptions import PendulumException
35from pendulum.helpers import add_duration
36from pendulum.interval import Interval
37from pendulum.time import Time
38from pendulum.tz import UTC
39from pendulum.tz import local_timezone
40from pendulum.tz.timezone import FixedTimezone
41from pendulum.tz.timezone import Timezone
44if TYPE_CHECKING:
45 from typing_extensions import Literal
46 from typing_extensions import Self
47 from typing_extensions import SupportsIndex
50class DateTime(datetime.datetime, Date):
51 EPOCH: ClassVar[DateTime]
52 min: ClassVar[DateTime]
53 max: ClassVar[DateTime]
55 # Formats
57 _FORMATS: ClassVar[dict[str, str | Callable[[datetime.datetime], str]]] = {
58 "atom": ATOM,
59 "cookie": COOKIE,
60 "iso8601": lambda dt: dt.isoformat("T"),
61 "rfc822": RFC822,
62 "rfc850": RFC850,
63 "rfc1036": RFC1036,
64 "rfc1123": RFC1123,
65 "rfc2822": RFC2822,
66 "rfc3339": lambda dt: dt.isoformat("T"),
67 "rss": RSS,
68 "w3c": W3C,
69 }
71 _MODIFIERS_VALID_UNITS: ClassVar[list[str]] = [
72 "second",
73 "minute",
74 "hour",
75 "day",
76 "week",
77 "month",
78 "year",
79 "decade",
80 "century",
81 ]
83 _EPOCH: datetime.datetime = datetime.datetime(1970, 1, 1, tzinfo=UTC)
85 @classmethod
86 def create(
87 cls,
88 year: SupportsIndex,
89 month: SupportsIndex,
90 day: SupportsIndex,
91 hour: SupportsIndex = 0,
92 minute: SupportsIndex = 0,
93 second: SupportsIndex = 0,
94 microsecond: SupportsIndex = 0,
95 tz: str | float | Timezone | FixedTimezone | None | datetime.tzinfo = UTC,
96 fold: int = 1,
97 raise_on_unknown_times: bool = False,
98 ) -> Self:
99 """
100 Creates a new DateTime instance from a specific date and time.
101 """
102 if tz is not None:
103 tz = pendulum._safe_timezone(tz)
105 dt = datetime.datetime(
106 year, month, day, hour, minute, second, microsecond, fold=fold
107 )
109 if tz is not None:
110 dt = tz.convert(dt, raise_on_unknown_times=raise_on_unknown_times)
112 return cls(
113 dt.year,
114 dt.month,
115 dt.day,
116 dt.hour,
117 dt.minute,
118 dt.second,
119 dt.microsecond,
120 tzinfo=dt.tzinfo,
121 fold=dt.fold,
122 )
124 @classmethod
125 def instance(
126 cls,
127 dt: datetime.datetime,
128 tz: str | Timezone | FixedTimezone | datetime.tzinfo | None = UTC,
129 ) -> Self:
130 tz = dt.tzinfo or tz
132 if tz is not None:
133 tz = pendulum._safe_timezone(tz, dt=dt)
135 return cls.create(
136 dt.year,
137 dt.month,
138 dt.day,
139 dt.hour,
140 dt.minute,
141 dt.second,
142 dt.microsecond,
143 tz=tz,
144 fold=dt.fold,
145 )
147 @overload
148 @classmethod
149 def now(cls, tz: datetime.tzinfo | None = None) -> Self:
150 ...
152 @overload
153 @classmethod
154 def now(cls, tz: str | Timezone | FixedTimezone | None = None) -> Self:
155 ...
157 @classmethod
158 def now(
159 cls, tz: str | Timezone | FixedTimezone | datetime.tzinfo | None = None
160 ) -> Self:
161 """
162 Get a DateTime instance for the current date and time.
163 """
164 if tz is None or tz == "local":
165 dt = datetime.datetime.now(local_timezone())
166 elif tz is UTC or tz == "UTC":
167 dt = datetime.datetime.now(UTC)
168 else:
169 dt = datetime.datetime.now(UTC)
170 tz = pendulum._safe_timezone(tz)
171 dt = dt.astimezone(tz)
173 return cls(
174 dt.year,
175 dt.month,
176 dt.day,
177 dt.hour,
178 dt.minute,
179 dt.second,
180 dt.microsecond,
181 tzinfo=dt.tzinfo,
182 fold=dt.fold,
183 )
185 @classmethod
186 def utcnow(cls) -> Self:
187 """
188 Get a DateTime instance for the current date and time in UTC.
189 """
190 return cls.now(UTC)
192 @classmethod
193 def today(cls) -> Self:
194 return cls.now()
196 @classmethod
197 def strptime(cls, time: str, fmt: str) -> Self:
198 return cls.instance(datetime.datetime.strptime(time, fmt))
200 # Getters/Setters
202 def set(
203 self,
204 year: int | None = None,
205 month: int | None = None,
206 day: int | None = None,
207 hour: int | None = None,
208 minute: int | None = None,
209 second: int | None = None,
210 microsecond: int | None = None,
211 tz: str | float | Timezone | FixedTimezone | datetime.tzinfo | None = None,
212 ) -> Self:
213 if year is None:
214 year = self.year
215 if month is None:
216 month = self.month
217 if day is None:
218 day = self.day
219 if hour is None:
220 hour = self.hour
221 if minute is None:
222 minute = self.minute
223 if second is None:
224 second = self.second
225 if microsecond is None:
226 microsecond = self.microsecond
227 if tz is None:
228 tz = self.tz
230 return self.__class__.create(
231 year, month, day, hour, minute, second, microsecond, tz=tz, fold=self.fold
232 )
234 @property
235 def float_timestamp(self) -> float:
236 return self.timestamp()
238 @property
239 def int_timestamp(self) -> int:
240 # Workaround needed to avoid inaccuracy
241 # for far into the future datetimes
242 dt = datetime.datetime(
243 self.year,
244 self.month,
245 self.day,
246 self.hour,
247 self.minute,
248 self.second,
249 self.microsecond,
250 tzinfo=self.tzinfo,
251 fold=self.fold,
252 )
254 delta = dt - self._EPOCH
256 return delta.days * SECONDS_PER_DAY + delta.seconds
258 @property
259 def offset(self) -> int | None:
260 return self.get_offset()
262 @property
263 def offset_hours(self) -> float | None:
264 offset = self.get_offset()
266 if offset is None:
267 return None
269 return offset / SECONDS_PER_MINUTE / MINUTES_PER_HOUR
271 @property
272 def timezone(self) -> Timezone | FixedTimezone | None:
273 if not isinstance(self.tzinfo, (Timezone, FixedTimezone)):
274 return None
276 return self.tzinfo
278 @property
279 def tz(self) -> Timezone | FixedTimezone | None:
280 return self.timezone
282 @property
283 def timezone_name(self) -> str | None:
284 tz = self.timezone
286 if tz is None:
287 return None
289 return tz.name
291 @property
292 def age(self) -> int:
293 return self.date().diff(self.now(self.tz).date(), abs=False).in_years()
295 def is_local(self) -> bool:
296 return self.offset == self.in_timezone(pendulum.local_timezone()).offset
298 def is_utc(self) -> bool:
299 return self.offset == 0
301 def is_dst(self) -> bool:
302 return self.dst() != datetime.timedelta()
304 def get_offset(self) -> int | None:
305 utcoffset = self.utcoffset()
306 if utcoffset is None:
307 return None
309 return int(utcoffset.total_seconds())
311 def date(self) -> Date:
312 return Date(self.year, self.month, self.day)
314 def time(self) -> Time:
315 return Time(self.hour, self.minute, self.second, self.microsecond)
317 def naive(self) -> Self:
318 """
319 Return the DateTime without timezone information.
320 """
321 return self.__class__(
322 self.year,
323 self.month,
324 self.day,
325 self.hour,
326 self.minute,
327 self.second,
328 self.microsecond,
329 )
331 def on(self, year: int, month: int, day: int) -> Self:
332 """
333 Returns a new instance with the current date set to a different date.
334 """
335 return self.set(year=int(year), month=int(month), day=int(day))
337 def at(
338 self, hour: int, minute: int = 0, second: int = 0, microsecond: int = 0
339 ) -> Self:
340 """
341 Returns a new instance with the current time to a different time.
342 """
343 return self.set(
344 hour=hour, minute=minute, second=second, microsecond=microsecond
345 )
347 def in_timezone(self, tz: str | Timezone | FixedTimezone) -> Self:
348 """
349 Set the instance's timezone from a string or object.
350 """
351 tz = pendulum._safe_timezone(tz)
353 dt = self
354 if not self.timezone:
355 dt = dt.replace(fold=1)
357 return tz.convert(dt)
359 def in_tz(self, tz: str | Timezone | FixedTimezone) -> Self:
360 """
361 Set the instance's timezone from a string or object.
362 """
363 return self.in_timezone(tz)
365 # STRING FORMATTING
367 def to_time_string(self) -> str:
368 """
369 Format the instance as time.
370 """
371 return self.format("HH:mm:ss")
373 def to_datetime_string(self) -> str:
374 """
375 Format the instance as date and time.
376 """
377 return self.format("YYYY-MM-DD HH:mm:ss")
379 def to_day_datetime_string(self) -> str:
380 """
381 Format the instance as day, date and time (in english).
382 """
383 return self.format("ddd, MMM D, YYYY h:mm A", locale="en")
385 def to_atom_string(self) -> str:
386 """
387 Format the instance as ATOM.
388 """
389 return self._to_string("atom")
391 def to_cookie_string(self) -> str:
392 """
393 Format the instance as COOKIE.
394 """
395 return self._to_string("cookie", locale="en")
397 def to_iso8601_string(self) -> str:
398 """
399 Format the instance as ISO 8601.
400 """
401 string = self._to_string("iso8601")
403 if self.tz and self.tz.name == "UTC":
404 string = string.replace("+00:00", "Z")
406 return string
408 def to_rfc822_string(self) -> str:
409 """
410 Format the instance as RFC 822.
411 """
412 return self._to_string("rfc822")
414 def to_rfc850_string(self) -> str:
415 """
416 Format the instance as RFC 850.
417 """
418 return self._to_string("rfc850")
420 def to_rfc1036_string(self) -> str:
421 """
422 Format the instance as RFC 1036.
423 """
424 return self._to_string("rfc1036")
426 def to_rfc1123_string(self) -> str:
427 """
428 Format the instance as RFC 1123.
429 """
430 return self._to_string("rfc1123")
432 def to_rfc2822_string(self) -> str:
433 """
434 Format the instance as RFC 2822.
435 """
436 return self._to_string("rfc2822")
438 def to_rfc3339_string(self) -> str:
439 """
440 Format the instance as RFC 3339.
441 """
442 return self._to_string("rfc3339")
444 def to_rss_string(self) -> str:
445 """
446 Format the instance as RSS.
447 """
448 return self._to_string("rss")
450 def to_w3c_string(self) -> str:
451 """
452 Format the instance as W3C.
453 """
454 return self._to_string("w3c")
456 def _to_string(self, fmt: str, locale: str | None = None) -> str:
457 """
458 Format the instance to a common string format.
459 """
460 if fmt not in self._FORMATS:
461 raise ValueError(f"Format [{fmt}] is not supported")
463 fmt_value = self._FORMATS[fmt]
464 if callable(fmt_value):
465 return fmt_value(self)
467 return self.format(fmt_value, locale=locale)
469 def __str__(self) -> str:
470 return self.isoformat(" ")
472 def __repr__(self) -> str:
473 us = ""
474 if self.microsecond:
475 us = f", {self.microsecond}"
477 repr_ = "{klass}(" "{year}, {month}, {day}, " "{hour}, {minute}, {second}{us}"
479 if self.tzinfo is not None:
480 repr_ += ", tzinfo={tzinfo}"
482 repr_ += ")"
484 return repr_.format(
485 klass=self.__class__.__name__,
486 year=self.year,
487 month=self.month,
488 day=self.day,
489 hour=self.hour,
490 minute=self.minute,
491 second=self.second,
492 us=us,
493 tzinfo=repr(self.tzinfo),
494 )
496 # Comparisons
497 def closest(self, *dts: datetime.datetime) -> Self: # type: ignore[override]
498 """
499 Get the closest date to the instance.
500 """
501 pdts = [self.instance(x) for x in dts]
503 return min((abs(self - dt), dt) for dt in pdts)[1]
505 def farthest(self, *dts: datetime.datetime) -> Self: # type: ignore[override]
506 """
507 Get the farthest date from the instance.
508 """
509 pdts = [self.instance(x) for x in dts]
511 return max((abs(self - dt), dt) for dt in pdts)[1]
513 def is_future(self) -> bool:
514 """
515 Determines if the instance is in the future, ie. greater than now.
516 """
517 return self > self.now(self.timezone)
519 def is_past(self) -> bool:
520 """
521 Determines if the instance is in the past, ie. less than now.
522 """
523 return self < self.now(self.timezone)
525 def is_long_year(self) -> bool:
526 """
527 Determines if the instance is a long year
529 See link `https://en.wikipedia.org/wiki/ISO_8601#Week_dates`_
530 """
531 return (
532 DateTime.create(self.year, 12, 28, 0, 0, 0, tz=self.tz).isocalendar()[1]
533 == 53
534 )
536 def is_same_day(self, dt: datetime.datetime) -> bool: # type: ignore[override]
537 """
538 Checks if the passed in date is the same day
539 as the instance current day.
540 """
541 dt = self.instance(dt)
543 return self.to_date_string() == dt.to_date_string()
545 def is_anniversary( # type: ignore[override]
546 self, dt: datetime.datetime | None = None
547 ) -> bool:
548 """
549 Check if its the anniversary.
550 Compares the date/month values of the two dates.
551 """
552 if dt is None:
553 dt = self.now(self.tz)
555 instance = self.instance(dt)
557 return (self.month, self.day) == (instance.month, instance.day)
559 # ADDITIONS AND SUBSTRACTIONS
561 def add(
562 self,
563 years: int = 0,
564 months: int = 0,
565 weeks: int = 0,
566 days: int = 0,
567 hours: int = 0,
568 minutes: int = 0,
569 seconds: float = 0,
570 microseconds: int = 0,
571 ) -> Self:
572 """
573 Add a duration to the instance.
575 If we're adding units of variable length (i.e., years, months),
576 move forward from current time, otherwise move forward from utc, for accuracy
577 when moving across DST boundaries.
578 """
579 units_of_variable_length = any([years, months, weeks, days])
581 current_dt = datetime.datetime(
582 self.year,
583 self.month,
584 self.day,
585 self.hour,
586 self.minute,
587 self.second,
588 self.microsecond,
589 )
590 if not units_of_variable_length:
591 offset = self.utcoffset()
592 if offset:
593 current_dt = current_dt - offset
595 dt = add_duration(
596 current_dt,
597 years=years,
598 months=months,
599 weeks=weeks,
600 days=days,
601 hours=hours,
602 minutes=minutes,
603 seconds=seconds,
604 microseconds=microseconds,
605 )
607 if units_of_variable_length or self.tz is None:
608 return self.__class__.create(
609 dt.year,
610 dt.month,
611 dt.day,
612 dt.hour,
613 dt.minute,
614 dt.second,
615 dt.microsecond,
616 tz=self.tz,
617 )
619 dt = datetime.datetime(
620 dt.year,
621 dt.month,
622 dt.day,
623 dt.hour,
624 dt.minute,
625 dt.second,
626 dt.microsecond,
627 tzinfo=UTC,
628 )
630 dt = self.tz.convert(dt)
632 return self.__class__(
633 dt.year,
634 dt.month,
635 dt.day,
636 dt.hour,
637 dt.minute,
638 dt.second,
639 dt.microsecond,
640 tzinfo=self.tz,
641 fold=dt.fold,
642 )
644 def subtract(
645 self,
646 years: int = 0,
647 months: int = 0,
648 weeks: int = 0,
649 days: int = 0,
650 hours: int = 0,
651 minutes: int = 0,
652 seconds: float = 0,
653 microseconds: int = 0,
654 ) -> Self:
655 """
656 Remove duration from the instance.
657 """
658 return self.add(
659 years=-years,
660 months=-months,
661 weeks=-weeks,
662 days=-days,
663 hours=-hours,
664 minutes=-minutes,
665 seconds=-seconds,
666 microseconds=-microseconds,
667 )
669 # Adding a final underscore to the method name
670 # to avoid errors for PyPy which already defines
671 # a _add_timedelta method
672 def _add_timedelta_(self, delta: datetime.timedelta) -> Self:
673 """
674 Add timedelta duration to the instance.
675 """
676 if isinstance(delta, pendulum.Interval):
677 return self.add(
678 years=delta.years,
679 months=delta.months,
680 weeks=delta.weeks,
681 days=delta.remaining_days,
682 hours=delta.hours,
683 minutes=delta.minutes,
684 seconds=delta.remaining_seconds,
685 microseconds=delta.microseconds,
686 )
687 elif isinstance(delta, pendulum.Duration):
688 return self.add(
689 years=delta.years, months=delta.months, seconds=delta._total
690 )
692 return self.add(seconds=delta.total_seconds())
694 def _subtract_timedelta(self, delta: datetime.timedelta) -> Self:
695 """
696 Remove timedelta duration from the instance.
697 """
698 if isinstance(delta, pendulum.Duration):
699 return self.subtract(
700 years=delta.years, months=delta.months, seconds=delta._total
701 )
703 return self.subtract(seconds=delta.total_seconds())
705 # DIFFERENCES
707 def diff( # type: ignore[override]
708 self, dt: datetime.datetime | None = None, abs: bool = True
709 ) -> Interval:
710 """
711 Returns the difference between two DateTime objects represented as an Interval.
712 """
713 if dt is None:
714 dt = self.now(self.tz)
716 return Interval(self, dt, absolute=abs)
718 def diff_for_humans( # type: ignore[override]
719 self,
720 other: DateTime | None = None,
721 absolute: bool = False,
722 locale: str | None = None,
723 ) -> str:
724 """
725 Get the difference in a human readable format in the current locale.
727 When comparing a value in the past to default now:
728 1 day ago
729 5 months ago
731 When comparing a value in the future to default now:
732 1 day from now
733 5 months from now
735 When comparing a value in the past to another value:
736 1 day before
737 5 months before
739 When comparing a value in the future to another value:
740 1 day after
741 5 months after
742 """
743 is_now = other is None
745 if is_now:
746 other = self.now()
748 diff = self.diff(other)
750 return pendulum.format_diff(diff, is_now, absolute, locale)
752 # Modifiers
753 def start_of(self, unit: str) -> Self:
754 """
755 Returns a copy of the instance with the time reset
756 with the following rules:
758 * second: microsecond set to 0
759 * minute: second and microsecond set to 0
760 * hour: minute, second and microsecond set to 0
761 * day: time to 00:00:00
762 * week: date to first day of the week and time to 00:00:00
763 * month: date to first day of the month and time to 00:00:00
764 * year: date to first day of the year and time to 00:00:00
765 * decade: date to first day of the decade and time to 00:00:00
766 * century: date to first day of century and time to 00:00:00
767 """
768 if unit not in self._MODIFIERS_VALID_UNITS:
769 raise ValueError(f'Invalid unit "{unit}" for start_of()')
771 return cast("Self", getattr(self, f"_start_of_{unit}")())
773 def end_of(self, unit: str) -> Self:
774 """
775 Returns a copy of the instance with the time reset
776 with the following rules:
778 * second: microsecond set to 999999
779 * minute: second set to 59 and microsecond set to 999999
780 * hour: minute and second set to 59 and microsecond set to 999999
781 * day: time to 23:59:59.999999
782 * week: date to last day of the week and time to 23:59:59.999999
783 * month: date to last day of the month and time to 23:59:59.999999
784 * year: date to last day of the year and time to 23:59:59.999999
785 * decade: date to last day of the decade and time to 23:59:59.999999
786 * century: date to last day of century and time to 23:59:59.999999
787 """
788 if unit not in self._MODIFIERS_VALID_UNITS:
789 raise ValueError(f'Invalid unit "{unit}" for end_of()')
791 return cast("Self", getattr(self, f"_end_of_{unit}")())
793 def _start_of_second(self) -> Self:
794 """
795 Reset microseconds to 0.
796 """
797 return self.set(microsecond=0)
799 def _end_of_second(self) -> Self:
800 """
801 Set microseconds to 999999.
802 """
803 return self.set(microsecond=999999)
805 def _start_of_minute(self) -> Self:
806 """
807 Reset seconds and microseconds to 0.
808 """
809 return self.set(second=0, microsecond=0)
811 def _end_of_minute(self) -> Self:
812 """
813 Set seconds to 59 and microseconds to 999999.
814 """
815 return self.set(second=59, microsecond=999999)
817 def _start_of_hour(self) -> Self:
818 """
819 Reset minutes, seconds and microseconds to 0.
820 """
821 return self.set(minute=0, second=0, microsecond=0)
823 def _end_of_hour(self) -> Self:
824 """
825 Set minutes and seconds to 59 and microseconds to 999999.
826 """
827 return self.set(minute=59, second=59, microsecond=999999)
829 def _start_of_day(self) -> Self:
830 """
831 Reset the time to 00:00:00.
832 """
833 return self.at(0, 0, 0, 0)
835 def _end_of_day(self) -> Self:
836 """
837 Reset the time to 23:59:59.999999.
838 """
839 return self.at(23, 59, 59, 999999)
841 def _start_of_month(self) -> Self:
842 """
843 Reset the date to the first day of the month and the time to 00:00:00.
844 """
845 return self.set(self.year, self.month, 1, 0, 0, 0, 0)
847 def _end_of_month(self) -> Self:
848 """
849 Reset the date to the last day of the month
850 and the time to 23:59:59.999999.
851 """
852 return self.set(self.year, self.month, self.days_in_month, 23, 59, 59, 999999)
854 def _start_of_year(self) -> Self:
855 """
856 Reset the date to the first day of the year and the time to 00:00:00.
857 """
858 return self.set(self.year, 1, 1, 0, 0, 0, 0)
860 def _end_of_year(self) -> Self:
861 """
862 Reset the date to the last day of the year
863 and the time to 23:59:59.999999.
864 """
865 return self.set(self.year, 12, 31, 23, 59, 59, 999999)
867 def _start_of_decade(self) -> Self:
868 """
869 Reset the date to the first day of the decade
870 and the time to 00:00:00.
871 """
872 year = self.year - self.year % YEARS_PER_DECADE
873 return self.set(year, 1, 1, 0, 0, 0, 0)
875 def _end_of_decade(self) -> Self:
876 """
877 Reset the date to the last day of the decade
878 and the time to 23:59:59.999999.
879 """
880 year = self.year - self.year % YEARS_PER_DECADE + YEARS_PER_DECADE - 1
882 return self.set(year, 12, 31, 23, 59, 59, 999999)
884 def _start_of_century(self) -> Self:
885 """
886 Reset the date to the first day of the century
887 and the time to 00:00:00.
888 """
889 year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + 1
891 return self.set(year, 1, 1, 0, 0, 0, 0)
893 def _end_of_century(self) -> Self:
894 """
895 Reset the date to the last day of the century
896 and the time to 23:59:59.999999.
897 """
898 year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + YEARS_PER_CENTURY
900 return self.set(year, 12, 31, 23, 59, 59, 999999)
902 def _start_of_week(self) -> Self:
903 """
904 Reset the date to the first day of the week
905 and the time to 00:00:00.
906 """
907 dt = self
909 if self.day_of_week != pendulum._WEEK_STARTS_AT:
910 dt = self.previous(pendulum._WEEK_STARTS_AT)
912 return dt.start_of("day")
914 def _end_of_week(self) -> Self:
915 """
916 Reset the date to the last day of the week
917 and the time to 23:59:59.
918 """
919 dt = self
921 if self.day_of_week != pendulum._WEEK_ENDS_AT:
922 dt = self.next(pendulum._WEEK_ENDS_AT)
924 return dt.end_of("day")
926 def next(self, day_of_week: WeekDay | None = None, keep_time: bool = False) -> Self:
927 """
928 Modify to the next occurrence of a given day of the week.
929 If no day_of_week is provided, modify to the next occurrence
930 of the current day of the week. Use the supplied consts
931 to indicate the desired day_of_week, ex. DateTime.MONDAY.
932 """
933 if day_of_week is None:
934 day_of_week = self.day_of_week
936 if day_of_week < WeekDay.MONDAY or day_of_week > WeekDay.SUNDAY:
937 raise ValueError("Invalid day of week")
939 dt = self if keep_time else self.start_of("day")
941 dt = dt.add(days=1)
942 while dt.day_of_week != day_of_week:
943 dt = dt.add(days=1)
945 return dt
947 def previous(
948 self, day_of_week: WeekDay | None = None, keep_time: bool = False
949 ) -> Self:
950 """
951 Modify to the previous occurrence of a given day of the week.
952 If no day_of_week is provided, modify to the previous occurrence
953 of the current day of the week. Use the supplied consts
954 to indicate the desired day_of_week, ex. DateTime.MONDAY.
955 """
956 if day_of_week is None:
957 day_of_week = self.day_of_week
959 if day_of_week < WeekDay.MONDAY or day_of_week > WeekDay.SUNDAY:
960 raise ValueError("Invalid day of week")
962 dt = self if keep_time else self.start_of("day")
964 dt = dt.subtract(days=1)
965 while dt.day_of_week != day_of_week:
966 dt = dt.subtract(days=1)
968 return dt
970 def first_of(self, unit: str, day_of_week: WeekDay | None = None) -> Self:
971 """
972 Returns an instance set to the first occurrence
973 of a given day of the week in the current unit.
974 If no day_of_week is provided, modify to the first day of the unit.
975 Use the supplied consts to indicate the desired day_of_week,
976 ex. DateTime.MONDAY.
978 Supported units are month, quarter and year.
979 """
980 if unit not in ["month", "quarter", "year"]:
981 raise ValueError(f'Invalid unit "{unit}" for first_of()')
983 return cast("Self", getattr(self, f"_first_of_{unit}")(day_of_week))
985 def last_of(self, unit: str, day_of_week: WeekDay | None = None) -> Self:
986 """
987 Returns an instance set to the last occurrence
988 of a given day of the week in the current unit.
989 If no day_of_week is provided, modify to the last day of the unit.
990 Use the supplied consts to indicate the desired day_of_week,
991 ex. DateTime.MONDAY.
993 Supported units are month, quarter and year.
994 """
995 if unit not in ["month", "quarter", "year"]:
996 raise ValueError(f'Invalid unit "{unit}" for first_of()')
998 return cast("Self", getattr(self, f"_last_of_{unit}")(day_of_week))
1000 def nth_of(self, unit: str, nth: int, day_of_week: WeekDay) -> Self:
1001 """
1002 Returns a new instance set to the given occurrence
1003 of a given day of the week in the current unit.
1004 If the calculated occurrence is outside the scope of the current unit,
1005 then raise an error. Use the supplied consts
1006 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1008 Supported units are month, quarter and year.
1009 """
1010 if unit not in ["month", "quarter", "year"]:
1011 raise ValueError(f'Invalid unit "{unit}" for first_of()')
1013 dt = cast(Optional["Self"], getattr(self, f"_nth_of_{unit}")(nth, day_of_week))
1014 if not dt:
1015 raise PendulumException(
1016 f"Unable to find occurrence {nth}"
1017 f" of {WeekDay(day_of_week).name.capitalize()} in {unit}"
1018 )
1020 return dt
1022 def _first_of_month(self, day_of_week: WeekDay | None = None) -> Self:
1023 """
1024 Modify to the first occurrence of a given day of the week
1025 in the current month. If no day_of_week is provided,
1026 modify to the first day of the month. Use the supplied consts
1027 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1028 """
1029 dt = self.start_of("day")
1031 if day_of_week is None:
1032 return dt.set(day=1)
1034 month = calendar.monthcalendar(dt.year, dt.month)
1036 calendar_day = day_of_week
1038 if month[0][calendar_day] > 0:
1039 day_of_month = month[0][calendar_day]
1040 else:
1041 day_of_month = month[1][calendar_day]
1043 return dt.set(day=day_of_month)
1045 def _last_of_month(self, day_of_week: WeekDay | None = None) -> Self:
1046 """
1047 Modify to the last occurrence of a given day of the week
1048 in the current month. If no day_of_week is provided,
1049 modify to the last day of the month. Use the supplied consts
1050 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1051 """
1052 dt = self.start_of("day")
1054 if day_of_week is None:
1055 return dt.set(day=self.days_in_month)
1057 month = calendar.monthcalendar(dt.year, dt.month)
1059 calendar_day = day_of_week
1061 if month[-1][calendar_day] > 0:
1062 day_of_month = month[-1][calendar_day]
1063 else:
1064 day_of_month = month[-2][calendar_day]
1066 return dt.set(day=day_of_month)
1068 def _nth_of_month(
1069 self, nth: int, day_of_week: WeekDay | None = None
1070 ) -> Self | None:
1071 """
1072 Modify to the given occurrence of a given day of the week
1073 in the current month. If the calculated occurrence is outside,
1074 the scope of the current month, then return False and no
1075 modifications are made. Use the supplied consts
1076 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1077 """
1078 if nth == 1:
1079 return self.first_of("month", day_of_week)
1081 dt = self.first_of("month")
1082 check = dt.format("%Y-%M")
1083 for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
1084 dt = dt.next(day_of_week)
1086 if dt.format("%Y-%M") == check:
1087 return self.set(day=dt.day).start_of("day")
1089 return None
1091 def _first_of_quarter(self, day_of_week: WeekDay | None = None) -> Self:
1092 """
1093 Modify to the first occurrence of a given day of the week
1094 in the current quarter. If no day_of_week is provided,
1095 modify to the first day of the quarter. Use the supplied consts
1096 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1097 """
1098 return self.on(self.year, self.quarter * 3 - 2, 1).first_of(
1099 "month", day_of_week
1100 )
1102 def _last_of_quarter(self, day_of_week: WeekDay | None = None) -> Self:
1103 """
1104 Modify to the last occurrence of a given day of the week
1105 in the current quarter. If no day_of_week is provided,
1106 modify to the last day of the quarter. Use the supplied consts
1107 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1108 """
1109 return self.on(self.year, self.quarter * 3, 1).last_of("month", day_of_week)
1111 def _nth_of_quarter(
1112 self, nth: int, day_of_week: WeekDay | None = None
1113 ) -> Self | None:
1114 """
1115 Modify to the given occurrence of a given day of the week
1116 in the current quarter. If the calculated occurrence is outside,
1117 the scope of the current quarter, then return False and no
1118 modifications are made. Use the supplied consts
1119 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1120 """
1121 if nth == 1:
1122 return self.first_of("quarter", day_of_week)
1124 dt = self.set(day=1, month=self.quarter * 3)
1125 last_month = dt.month
1126 year = dt.year
1127 dt = dt.first_of("quarter")
1128 for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
1129 dt = dt.next(day_of_week)
1131 if last_month < dt.month or year != dt.year:
1132 return None
1134 return self.on(self.year, dt.month, dt.day).start_of("day")
1136 def _first_of_year(self, day_of_week: WeekDay | None = None) -> Self:
1137 """
1138 Modify to the first occurrence of a given day of the week
1139 in the current year. If no day_of_week is provided,
1140 modify to the first day of the year. Use the supplied consts
1141 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1142 """
1143 return self.set(month=1).first_of("month", day_of_week)
1145 def _last_of_year(self, day_of_week: WeekDay | None = None) -> Self:
1146 """
1147 Modify to the last occurrence of a given day of the week
1148 in the current year. If no day_of_week is provided,
1149 modify to the last day of the year. Use the supplied consts
1150 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1151 """
1152 return self.set(month=MONTHS_PER_YEAR).last_of("month", day_of_week)
1154 def _nth_of_year(self, nth: int, day_of_week: WeekDay | None = None) -> Self | None:
1155 """
1156 Modify to the given occurrence of a given day of the week
1157 in the current year. If the calculated occurrence is outside,
1158 the scope of the current year, then return False and no
1159 modifications are made. Use the supplied consts
1160 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1161 """
1162 if nth == 1:
1163 return self.first_of("year", day_of_week)
1165 dt = self.first_of("year")
1166 year = dt.year
1167 for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
1168 dt = dt.next(day_of_week)
1170 if year != dt.year:
1171 return None
1173 return self.on(self.year, dt.month, dt.day).start_of("day")
1175 def average( # type: ignore[override]
1176 self, dt: datetime.datetime | None = None
1177 ) -> Self:
1178 """
1179 Modify the current instance to the average
1180 of a given instance (default now) and the current instance.
1181 """
1182 if dt is None:
1183 dt = self.now(self.tz)
1185 diff = self.diff(dt, False)
1186 return self.add(
1187 microseconds=(diff.in_seconds() * 1000000 + diff.microseconds) // 2
1188 )
1190 @overload # type: ignore[override]
1191 def __sub__(self, other: datetime.timedelta) -> Self:
1192 ...
1194 @overload
1195 def __sub__(self, other: DateTime) -> Interval:
1196 ...
1198 def __sub__(self, other: datetime.datetime | datetime.timedelta) -> Self | Interval:
1199 if isinstance(other, datetime.timedelta):
1200 return self._subtract_timedelta(other)
1202 if not isinstance(other, datetime.datetime):
1203 return NotImplemented
1205 if not isinstance(other, self.__class__):
1206 if other.tzinfo is None:
1207 other = pendulum.naive(
1208 other.year,
1209 other.month,
1210 other.day,
1211 other.hour,
1212 other.minute,
1213 other.second,
1214 other.microsecond,
1215 )
1216 else:
1217 other = self.instance(other)
1219 return other.diff(self, False)
1221 def __rsub__(self, other: datetime.datetime) -> Interval:
1222 if not isinstance(other, datetime.datetime):
1223 return NotImplemented
1225 if not isinstance(other, self.__class__):
1226 if other.tzinfo is None:
1227 other = pendulum.naive(
1228 other.year,
1229 other.month,
1230 other.day,
1231 other.hour,
1232 other.minute,
1233 other.second,
1234 other.microsecond,
1235 )
1236 else:
1237 other = self.instance(other)
1239 return self.diff(other, False)
1241 def __add__(self, other: datetime.timedelta) -> Self:
1242 if not isinstance(other, datetime.timedelta):
1243 return NotImplemented
1245 caller = traceback.extract_stack(limit=2)[0].name
1246 if caller == "astimezone":
1247 return super().__add__(other)
1249 return self._add_timedelta_(other)
1251 def __radd__(self, other: datetime.timedelta) -> Self:
1252 return self.__add__(other)
1254 # Native methods override
1256 @classmethod
1257 def fromtimestamp(cls, t: float, tz: datetime.tzinfo | None = None) -> Self:
1258 tzinfo = pendulum._safe_timezone(tz)
1260 return cls.instance(datetime.datetime.fromtimestamp(t, tz=tzinfo), tz=tzinfo)
1262 @classmethod
1263 def utcfromtimestamp(cls, t: float) -> Self:
1264 return cls.instance(datetime.datetime.utcfromtimestamp(t), tz=None)
1266 @classmethod
1267 def fromordinal(cls, n: int) -> Self:
1268 return cls.instance(datetime.datetime.fromordinal(n), tz=None)
1270 @classmethod
1271 def combine(
1272 cls,
1273 date: datetime.date,
1274 time: datetime.time,
1275 tzinfo: datetime.tzinfo | None = None,
1276 ) -> Self:
1277 return cls.instance(datetime.datetime.combine(date, time), tz=tzinfo)
1279 def astimezone(self, tz: datetime.tzinfo | None = None) -> Self:
1280 dt = super().astimezone(tz)
1282 return self.__class__(
1283 dt.year,
1284 dt.month,
1285 dt.day,
1286 dt.hour,
1287 dt.minute,
1288 dt.second,
1289 dt.microsecond,
1290 fold=dt.fold,
1291 tzinfo=dt.tzinfo,
1292 )
1294 def replace(
1295 self,
1296 year: SupportsIndex | None = None,
1297 month: SupportsIndex | None = None,
1298 day: SupportsIndex | None = None,
1299 hour: SupportsIndex | None = None,
1300 minute: SupportsIndex | None = None,
1301 second: SupportsIndex | None = None,
1302 microsecond: SupportsIndex | None = None,
1303 tzinfo: bool | datetime.tzinfo | Literal[True] | None = True,
1304 fold: int | None = None,
1305 ) -> Self:
1306 if year is None:
1307 year = self.year
1308 if month is None:
1309 month = self.month
1310 if day is None:
1311 day = self.day
1312 if hour is None:
1313 hour = self.hour
1314 if minute is None:
1315 minute = self.minute
1316 if second is None:
1317 second = self.second
1318 if microsecond is None:
1319 microsecond = self.microsecond
1320 if tzinfo is True:
1321 tzinfo = self.tzinfo
1322 if fold is None:
1323 fold = self.fold
1325 if tzinfo is not None:
1326 tzinfo = pendulum._safe_timezone(tzinfo)
1328 return self.__class__.create(
1329 year,
1330 month,
1331 day,
1332 hour,
1333 minute,
1334 second,
1335 microsecond,
1336 tz=tzinfo,
1337 fold=fold,
1338 )
1340 def __getnewargs__(self) -> tuple[Self]:
1341 return (self,)
1343 def _getstate(
1344 self, protocol: SupportsIndex = 3
1345 ) -> tuple[int, int, int, int, int, int, int, datetime.tzinfo | None]:
1346 return (
1347 self.year,
1348 self.month,
1349 self.day,
1350 self.hour,
1351 self.minute,
1352 self.second,
1353 self.microsecond,
1354 self.tzinfo,
1355 )
1357 def __reduce__(
1358 self,
1359 ) -> tuple[
1360 type[Self], tuple[int, int, int, int, int, int, int, datetime.tzinfo | None]
1361 ]:
1362 return self.__reduce_ex__(2)
1364 def __reduce_ex__(
1365 self, protocol: SupportsIndex
1366 ) -> tuple[
1367 type[Self], tuple[int, int, int, int, int, int, int, datetime.tzinfo | None]
1368 ]:
1369 return self.__class__, self._getstate(protocol)
1371 def _cmp(self, other: datetime.datetime, **kwargs: Any) -> int:
1372 # Fix for pypy which compares using this method
1373 # which would lead to infinite recursion if we didn't override
1374 dt = datetime.datetime(
1375 self.year,
1376 self.month,
1377 self.day,
1378 self.hour,
1379 self.minute,
1380 self.second,
1381 self.microsecond,
1382 tzinfo=self.tz,
1383 fold=self.fold,
1384 )
1386 return 0 if dt == other else 1 if dt > other else -1
1389DateTime.min = DateTime(1, 1, 1, 0, 0, tzinfo=UTC)
1390DateTime.max = DateTime(9999, 12, 31, 23, 59, 59, 999999, tzinfo=UTC)
1391DateTime.EPOCH = DateTime(1970, 1, 1, tzinfo=UTC)