Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.11/site-packages/pendulum/datetime.py: 37%
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 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: ...
151 @overload
152 @classmethod
153 def now(cls, tz: str | Timezone | FixedTimezone | None = None) -> Self: ...
155 @classmethod
156 def now(
157 cls, tz: str | Timezone | FixedTimezone | datetime.tzinfo | None = None
158 ) -> Self:
159 """
160 Get a DateTime instance for the current date and time.
161 """
162 if tz is None or tz == "local":
163 dt = datetime.datetime.now(local_timezone())
164 elif tz is UTC or tz == "UTC":
165 dt = datetime.datetime.now(UTC)
166 else:
167 dt = datetime.datetime.now(UTC)
168 tz = pendulum._safe_timezone(tz)
169 dt = dt.astimezone(tz)
171 return cls(
172 dt.year,
173 dt.month,
174 dt.day,
175 dt.hour,
176 dt.minute,
177 dt.second,
178 dt.microsecond,
179 tzinfo=dt.tzinfo,
180 fold=dt.fold,
181 )
183 @classmethod
184 def utcnow(cls) -> Self:
185 """
186 Get a DateTime instance for the current date and time in UTC.
187 """
188 return cls.now(UTC)
190 @classmethod
191 def today(cls) -> Self:
192 return cls.now()
194 @classmethod
195 def strptime(cls, time: str, fmt: str) -> Self:
196 return cls.instance(datetime.datetime.strptime(time, fmt))
198 # Getters/Setters
200 def set(
201 self,
202 year: int | None = None,
203 month: int | None = None,
204 day: int | None = None,
205 hour: int | None = None,
206 minute: int | None = None,
207 second: int | None = None,
208 microsecond: int | None = None,
209 tz: str | float | Timezone | FixedTimezone | datetime.tzinfo | None = None,
210 ) -> Self:
211 if year is None:
212 year = self.year
213 if month is None:
214 month = self.month
215 if day is None:
216 day = self.day
217 if hour is None:
218 hour = self.hour
219 if minute is None:
220 minute = self.minute
221 if second is None:
222 second = self.second
223 if microsecond is None:
224 microsecond = self.microsecond
225 if tz is None:
226 tz = self.tz
228 return self.__class__.create(
229 year, month, day, hour, minute, second, microsecond, tz=tz, fold=self.fold
230 )
232 @property
233 def float_timestamp(self) -> float:
234 return self.timestamp()
236 @property
237 def int_timestamp(self) -> int:
238 # Workaround needed to avoid inaccuracy
239 # for far into the future datetimes
240 dt = datetime.datetime(
241 self.year,
242 self.month,
243 self.day,
244 self.hour,
245 self.minute,
246 self.second,
247 self.microsecond,
248 tzinfo=self.tzinfo,
249 fold=self.fold,
250 )
252 delta = dt - self._EPOCH
254 return delta.days * SECONDS_PER_DAY + delta.seconds
256 @property
257 def offset(self) -> int | None:
258 return self.get_offset()
260 @property
261 def offset_hours(self) -> float | None:
262 offset = self.get_offset()
264 if offset is None:
265 return None
267 return offset / SECONDS_PER_MINUTE / MINUTES_PER_HOUR
269 @property
270 def timezone(self) -> Timezone | FixedTimezone | None:
271 if not isinstance(self.tzinfo, (Timezone, FixedTimezone)):
272 return None
274 return self.tzinfo
276 @property
277 def tz(self) -> Timezone | FixedTimezone | None:
278 return self.timezone
280 @property
281 def timezone_name(self) -> str | None:
282 tz = self.timezone
284 if tz is None:
285 return None
287 return tz.name
289 @property
290 def age(self) -> int:
291 return self.date().diff(self.now(self.tz).date(), abs=False).in_years()
293 def is_local(self) -> bool:
294 return self.offset == self.in_timezone(pendulum.local_timezone()).offset
296 def is_utc(self) -> bool:
297 return self.offset == 0
299 def is_dst(self) -> bool:
300 return self.dst() != datetime.timedelta()
302 def get_offset(self) -> int | None:
303 utcoffset = self.utcoffset()
304 if utcoffset is None:
305 return None
307 return int(utcoffset.total_seconds())
309 def date(self) -> Date:
310 return Date(self.year, self.month, self.day)
312 def time(self) -> Time:
313 return Time(self.hour, self.minute, self.second, self.microsecond)
315 def naive(self) -> Self:
316 """
317 Return the DateTime without timezone information.
318 """
319 return self.__class__(
320 self.year,
321 self.month,
322 self.day,
323 self.hour,
324 self.minute,
325 self.second,
326 self.microsecond,
327 )
329 def on(self, year: int, month: int, day: int) -> Self:
330 """
331 Returns a new instance with the current date set to a different date.
332 """
333 return self.set(year=int(year), month=int(month), day=int(day))
335 def at(
336 self, hour: int, minute: int = 0, second: int = 0, microsecond: int = 0
337 ) -> Self:
338 """
339 Returns a new instance with the current time to a different time.
340 """
341 return self.set(
342 hour=hour, minute=minute, second=second, microsecond=microsecond
343 )
345 def in_timezone(self, tz: str | Timezone | FixedTimezone) -> Self:
346 """
347 Set the instance's timezone from a string or object.
348 """
349 tz = pendulum._safe_timezone(tz)
351 dt = self
352 if not self.timezone:
353 dt = dt.replace(fold=1)
355 return tz.convert(dt)
357 def in_tz(self, tz: str | Timezone | FixedTimezone) -> Self:
358 """
359 Set the instance's timezone from a string or object.
360 """
361 return self.in_timezone(tz)
363 # STRING FORMATTING
365 def to_time_string(self) -> str:
366 """
367 Format the instance as time.
368 """
369 return self.format("HH:mm:ss")
371 def to_datetime_string(self) -> str:
372 """
373 Format the instance as date and time.
374 """
375 return self.format("YYYY-MM-DD HH:mm:ss")
377 def to_day_datetime_string(self) -> str:
378 """
379 Format the instance as day, date and time (in english).
380 """
381 return self.format("ddd, MMM D, YYYY h:mm A", locale="en")
383 def to_atom_string(self) -> str:
384 """
385 Format the instance as ATOM.
386 """
387 return self._to_string("atom")
389 def to_cookie_string(self) -> str:
390 """
391 Format the instance as COOKIE.
392 """
393 return self._to_string("cookie", locale="en")
395 def to_iso8601_string(self) -> str:
396 """
397 Format the instance as ISO 8601.
398 """
399 string = self._to_string("iso8601")
401 if self.tz and self.tz.name == "UTC":
402 string = string.replace("+00:00", "Z")
404 return string
406 def to_rfc822_string(self) -> str:
407 """
408 Format the instance as RFC 822.
409 """
410 return self._to_string("rfc822")
412 def to_rfc850_string(self) -> str:
413 """
414 Format the instance as RFC 850.
415 """
416 return self._to_string("rfc850")
418 def to_rfc1036_string(self) -> str:
419 """
420 Format the instance as RFC 1036.
421 """
422 return self._to_string("rfc1036")
424 def to_rfc1123_string(self) -> str:
425 """
426 Format the instance as RFC 1123.
427 """
428 return self._to_string("rfc1123")
430 def to_rfc2822_string(self) -> str:
431 """
432 Format the instance as RFC 2822.
433 """
434 return self._to_string("rfc2822")
436 def to_rfc3339_string(self) -> str:
437 """
438 Format the instance as RFC 3339.
439 """
440 return self._to_string("rfc3339")
442 def to_rss_string(self) -> str:
443 """
444 Format the instance as RSS.
445 """
446 return self._to_string("rss")
448 def to_w3c_string(self) -> str:
449 """
450 Format the instance as W3C.
451 """
452 return self._to_string("w3c")
454 def _to_string(self, fmt: str, locale: str | None = None) -> str:
455 """
456 Format the instance to a common string format.
457 """
458 if fmt not in self._FORMATS:
459 raise ValueError(f"Format [{fmt}] is not supported")
461 fmt_value = self._FORMATS[fmt]
462 if callable(fmt_value):
463 return fmt_value(self)
465 return self.format(fmt_value, locale=locale)
467 def __str__(self) -> str:
468 return self.isoformat(" ")
470 def __repr__(self) -> str:
471 us = ""
472 if self.microsecond:
473 us = f", {self.microsecond}"
475 repr_ = "{klass}({year}, {month}, {day}, {hour}, {minute}, {second}{us}"
477 if self.tzinfo is not None:
478 repr_ += ", tzinfo={tzinfo}"
480 repr_ += ")"
482 return repr_.format(
483 klass=self.__class__.__name__,
484 year=self.year,
485 month=self.month,
486 day=self.day,
487 hour=self.hour,
488 minute=self.minute,
489 second=self.second,
490 us=us,
491 tzinfo=repr(self.tzinfo),
492 )
494 # Comparisons
495 def closest(self, *dts: datetime.datetime) -> Self: # type: ignore[override]
496 """
497 Get the closest date to the instance.
498 """
499 pdts = [self.instance(x) for x in dts]
501 return min((abs(self - dt), dt) for dt in pdts)[1]
503 def farthest(self, *dts: datetime.datetime) -> Self: # type: ignore[override]
504 """
505 Get the farthest date from the instance.
506 """
507 pdts = [self.instance(x) for x in dts]
509 return max((abs(self - dt), dt) for dt in pdts)[1]
511 def is_future(self) -> bool:
512 """
513 Determines if the instance is in the future, ie. greater than now.
514 """
515 return self > self.now(self.timezone)
517 def is_past(self) -> bool:
518 """
519 Determines if the instance is in the past, ie. less than now.
520 """
521 return self < self.now(self.timezone)
523 def is_long_year(self) -> bool:
524 """
525 Determines if the instance is a long year
527 See link `https://en.wikipedia.org/wiki/ISO_8601#Week_dates`_
528 """
529 return (
530 DateTime.create(self.year, 12, 28, 0, 0, 0, tz=self.tz).isocalendar()[1]
531 == 53
532 )
534 def is_same_day(self, dt: datetime.datetime) -> bool: # type: ignore[override]
535 """
536 Checks if the passed in date is the same day
537 as the instance current day.
538 """
539 dt = self.instance(dt)
541 return self.to_date_string() == dt.to_date_string()
543 def is_anniversary( # type: ignore[override]
544 self, dt: datetime.datetime | None = None
545 ) -> bool:
546 """
547 Check if its the anniversary.
548 Compares the date/month values of the two dates.
549 """
550 if dt is None:
551 dt = self.now(self.tz)
553 instance = self.instance(dt)
555 return (self.month, self.day) == (instance.month, instance.day)
557 # ADDITIONS AND SUBSTRACTIONS
559 def add(
560 self,
561 years: int = 0,
562 months: int = 0,
563 weeks: int = 0,
564 days: int = 0,
565 hours: int = 0,
566 minutes: int = 0,
567 seconds: float = 0,
568 microseconds: int = 0,
569 ) -> Self:
570 """
571 Add a duration to the instance.
573 If we're adding units of variable length (i.e., years, months),
574 move forward from current time, otherwise move forward from utc, for accuracy
575 when moving across DST boundaries.
576 """
577 units_of_variable_length = any([years, months, weeks, days])
579 current_dt = datetime.datetime(
580 self.year,
581 self.month,
582 self.day,
583 self.hour,
584 self.minute,
585 self.second,
586 self.microsecond,
587 )
588 if not units_of_variable_length:
589 offset = self.utcoffset()
590 if offset:
591 current_dt = current_dt - offset
593 dt = add_duration(
594 current_dt,
595 years=years,
596 months=months,
597 weeks=weeks,
598 days=days,
599 hours=hours,
600 minutes=minutes,
601 seconds=seconds,
602 microseconds=microseconds,
603 )
605 if units_of_variable_length or self.tz is None:
606 return self.__class__.create(
607 dt.year,
608 dt.month,
609 dt.day,
610 dt.hour,
611 dt.minute,
612 dt.second,
613 dt.microsecond,
614 tz=self.tz,
615 )
617 dt = datetime.datetime(
618 dt.year,
619 dt.month,
620 dt.day,
621 dt.hour,
622 dt.minute,
623 dt.second,
624 dt.microsecond,
625 tzinfo=UTC,
626 )
628 dt = self.tz.convert(dt)
630 return self.__class__(
631 dt.year,
632 dt.month,
633 dt.day,
634 dt.hour,
635 dt.minute,
636 dt.second,
637 dt.microsecond,
638 tzinfo=self.tz,
639 fold=dt.fold,
640 )
642 def subtract(
643 self,
644 years: int = 0,
645 months: int = 0,
646 weeks: int = 0,
647 days: int = 0,
648 hours: int = 0,
649 minutes: int = 0,
650 seconds: float = 0,
651 microseconds: int = 0,
652 ) -> Self:
653 """
654 Remove duration from the instance.
655 """
656 return self.add(
657 years=-years,
658 months=-months,
659 weeks=-weeks,
660 days=-days,
661 hours=-hours,
662 minutes=-minutes,
663 seconds=-seconds,
664 microseconds=-microseconds,
665 )
667 # Adding a final underscore to the method name
668 # to avoid errors for PyPy which already defines
669 # a _add_timedelta method
670 def _add_timedelta_(self, delta: datetime.timedelta) -> Self:
671 """
672 Add timedelta duration to the instance.
673 """
674 if isinstance(delta, pendulum.Interval):
675 return self.add(
676 years=delta.years,
677 months=delta.months,
678 weeks=delta.weeks,
679 days=delta.remaining_days,
680 hours=delta.hours,
681 minutes=delta.minutes,
682 seconds=delta.remaining_seconds,
683 microseconds=delta.microseconds,
684 )
685 elif isinstance(delta, pendulum.Duration):
686 return self.add(**delta._signature) # type: ignore[attr-defined]
688 return self.add(seconds=delta.total_seconds())
690 def _subtract_timedelta(self, delta: datetime.timedelta) -> Self:
691 """
692 Remove timedelta duration from the instance.
693 """
694 if isinstance(delta, pendulum.Duration):
695 return self.subtract(
696 years=delta.years, months=delta.months, seconds=delta._total
697 )
699 return self.subtract(seconds=delta.total_seconds())
701 # DIFFERENCES
703 def diff( # type: ignore[override]
704 self, dt: datetime.datetime | None = None, abs: bool = True
705 ) -> Interval[datetime.datetime]:
706 """
707 Returns the difference between two DateTime objects represented as an Interval.
708 """
709 if dt is None:
710 dt = self.now(self.tz)
712 return Interval(self, dt, absolute=abs)
714 def diff_for_humans( # type: ignore[override]
715 self,
716 other: DateTime | None = None,
717 absolute: bool = False,
718 locale: str | None = None,
719 ) -> str:
720 """
721 Get the difference in a human readable format in the current locale.
723 When comparing a value in the past to default now:
724 1 day ago
725 5 months ago
727 When comparing a value in the future to default now:
728 1 day from now
729 5 months from now
731 When comparing a value in the past to another value:
732 1 day before
733 5 months before
735 When comparing a value in the future to another value:
736 1 day after
737 5 months after
738 """
739 is_now = other is None
741 if is_now:
742 other = self.now()
744 diff = self.diff(other)
746 return pendulum.format_diff(diff, is_now, absolute, locale)
748 # Modifiers
749 def start_of(self, unit: str) -> Self:
750 """
751 Returns a copy of the instance with the time reset
752 with the following rules:
754 * second: microsecond set to 0
755 * minute: second and microsecond set to 0
756 * hour: minute, second and microsecond set to 0
757 * day: time to 00:00:00
758 * week: date to first day of the week and time to 00:00:00
759 * month: date to first day of the month and time to 00:00:00
760 * year: date to first day of the year and time to 00:00:00
761 * decade: date to first day of the decade and time to 00:00:00
762 * century: date to first day of century and time to 00:00:00
763 """
764 if unit not in self._MODIFIERS_VALID_UNITS:
765 raise ValueError(f'Invalid unit "{unit}" for start_of()')
767 return cast("Self", getattr(self, f"_start_of_{unit}")())
769 def end_of(self, unit: str) -> Self:
770 """
771 Returns a copy of the instance with the time reset
772 with the following rules:
774 * second: microsecond set to 999999
775 * minute: second set to 59 and microsecond set to 999999
776 * hour: minute and second set to 59 and microsecond set to 999999
777 * day: time to 23:59:59.999999
778 * week: date to last day of the week and time to 23:59:59.999999
779 * month: date to last day of the month and time to 23:59:59.999999
780 * year: date to last day of the year and time to 23:59:59.999999
781 * decade: date to last day of the decade and time to 23:59:59.999999
782 * century: date to last day of century and time to 23:59:59.999999
783 """
784 if unit not in self._MODIFIERS_VALID_UNITS:
785 raise ValueError(f'Invalid unit "{unit}" for end_of()')
787 return cast("Self", getattr(self, f"_end_of_{unit}")())
789 def _start_of_second(self) -> Self:
790 """
791 Reset microseconds to 0.
792 """
793 return self.set(microsecond=0)
795 def _end_of_second(self) -> Self:
796 """
797 Set microseconds to 999999.
798 """
799 return self.set(microsecond=999999)
801 def _start_of_minute(self) -> Self:
802 """
803 Reset seconds and microseconds to 0.
804 """
805 return self.set(second=0, microsecond=0)
807 def _end_of_minute(self) -> Self:
808 """
809 Set seconds to 59 and microseconds to 999999.
810 """
811 return self.set(second=59, microsecond=999999)
813 def _start_of_hour(self) -> Self:
814 """
815 Reset minutes, seconds and microseconds to 0.
816 """
817 return self.set(minute=0, second=0, microsecond=0)
819 def _end_of_hour(self) -> Self:
820 """
821 Set minutes and seconds to 59 and microseconds to 999999.
822 """
823 return self.set(minute=59, second=59, microsecond=999999)
825 def _start_of_day(self) -> Self:
826 """
827 Reset the time to 00:00:00.
828 """
829 return self.at(0, 0, 0, 0)
831 def _end_of_day(self) -> Self:
832 """
833 Reset the time to 23:59:59.999999.
834 """
835 return self.at(23, 59, 59, 999999)
837 def _start_of_month(self) -> Self:
838 """
839 Reset the date to the first day of the month and the time to 00:00:00.
840 """
841 return self.set(self.year, self.month, 1, 0, 0, 0, 0)
843 def _end_of_month(self) -> Self:
844 """
845 Reset the date to the last day of the month
846 and the time to 23:59:59.999999.
847 """
848 return self.set(self.year, self.month, self.days_in_month, 23, 59, 59, 999999)
850 def _start_of_year(self) -> Self:
851 """
852 Reset the date to the first day of the year and the time to 00:00:00.
853 """
854 return self.set(self.year, 1, 1, 0, 0, 0, 0)
856 def _end_of_year(self) -> Self:
857 """
858 Reset the date to the last day of the year
859 and the time to 23:59:59.999999.
860 """
861 return self.set(self.year, 12, 31, 23, 59, 59, 999999)
863 def _start_of_decade(self) -> Self:
864 """
865 Reset the date to the first day of the decade
866 and the time to 00:00:00.
867 """
868 year = self.year - self.year % YEARS_PER_DECADE
869 return self.set(year, 1, 1, 0, 0, 0, 0)
871 def _end_of_decade(self) -> Self:
872 """
873 Reset the date to the last day of the decade
874 and the time to 23:59:59.999999.
875 """
876 year = self.year - self.year % YEARS_PER_DECADE + YEARS_PER_DECADE - 1
878 return self.set(year, 12, 31, 23, 59, 59, 999999)
880 def _start_of_century(self) -> Self:
881 """
882 Reset the date to the first day of the century
883 and the time to 00:00:00.
884 """
885 year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + 1
887 return self.set(year, 1, 1, 0, 0, 0, 0)
889 def _end_of_century(self) -> Self:
890 """
891 Reset the date to the last day of the century
892 and the time to 23:59:59.999999.
893 """
894 year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + YEARS_PER_CENTURY
896 return self.set(year, 12, 31, 23, 59, 59, 999999)
898 def _start_of_week(self) -> Self:
899 """
900 Reset the date to the first day of the week
901 and the time to 00:00:00.
902 """
903 dt = self
905 if self.day_of_week != pendulum._WEEK_STARTS_AT:
906 dt = self.previous(pendulum._WEEK_STARTS_AT)
908 return dt.start_of("day")
910 def _end_of_week(self) -> Self:
911 """
912 Reset the date to the last day of the week
913 and the time to 23:59:59.
914 """
915 dt = self
917 if self.day_of_week != pendulum._WEEK_ENDS_AT:
918 dt = self.next(pendulum._WEEK_ENDS_AT)
920 return dt.end_of("day")
922 def next(self, day_of_week: WeekDay | None = None, keep_time: bool = False) -> Self:
923 """
924 Modify to the next occurrence of a given day of the week.
925 If no day_of_week is provided, modify to the next occurrence
926 of the current day of the week. Use the supplied consts
927 to indicate the desired day_of_week, ex. DateTime.MONDAY.
928 """
929 if day_of_week is None:
930 day_of_week = self.day_of_week
932 if day_of_week < WeekDay.MONDAY or day_of_week > WeekDay.SUNDAY:
933 raise ValueError("Invalid day of week")
935 dt = self if keep_time else self.start_of("day")
937 dt = dt.add(days=1)
938 while dt.day_of_week != day_of_week:
939 dt = dt.add(days=1)
941 return dt
943 def previous(
944 self, day_of_week: WeekDay | None = None, keep_time: bool = False
945 ) -> Self:
946 """
947 Modify to the previous occurrence of a given day of the week.
948 If no day_of_week is provided, modify to the previous occurrence
949 of the current day of the week. Use the supplied consts
950 to indicate the desired day_of_week, ex. DateTime.MONDAY.
951 """
952 if day_of_week is None:
953 day_of_week = self.day_of_week
955 if day_of_week < WeekDay.MONDAY or day_of_week > WeekDay.SUNDAY:
956 raise ValueError("Invalid day of week")
958 dt = self if keep_time else self.start_of("day")
960 dt = dt.subtract(days=1)
961 while dt.day_of_week != day_of_week:
962 dt = dt.subtract(days=1)
964 return dt
966 def first_of(self, unit: str, day_of_week: WeekDay | None = None) -> Self:
967 """
968 Returns an instance set to the first occurrence
969 of a given day of the week in the current unit.
970 If no day_of_week is provided, modify to the first day of the unit.
971 Use the supplied consts to indicate the desired day_of_week,
972 ex. DateTime.MONDAY.
974 Supported units are month, quarter and year.
975 """
976 if unit not in ["month", "quarter", "year"]:
977 raise ValueError(f'Invalid unit "{unit}" for first_of()')
979 return cast("Self", getattr(self, f"_first_of_{unit}")(day_of_week))
981 def last_of(self, unit: str, day_of_week: WeekDay | None = None) -> Self:
982 """
983 Returns an instance set to the last occurrence
984 of a given day of the week in the current unit.
985 If no day_of_week is provided, modify to the last day of the unit.
986 Use the supplied consts to indicate the desired day_of_week,
987 ex. DateTime.MONDAY.
989 Supported units are month, quarter and year.
990 """
991 if unit not in ["month", "quarter", "year"]:
992 raise ValueError(f'Invalid unit "{unit}" for first_of()')
994 return cast("Self", getattr(self, f"_last_of_{unit}")(day_of_week))
996 def nth_of(self, unit: str, nth: int, day_of_week: WeekDay) -> Self:
997 """
998 Returns a new instance set to the given occurrence
999 of a given day of the week in the current unit.
1000 If the calculated occurrence is outside the scope of the current unit,
1001 then raise an error. Use the supplied consts
1002 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1004 Supported units are month, quarter and year.
1005 """
1006 if unit not in ["month", "quarter", "year"]:
1007 raise ValueError(f'Invalid unit "{unit}" for first_of()')
1009 dt = cast("Optional[Self]", getattr(self, f"_nth_of_{unit}")(nth, day_of_week))
1010 if not dt:
1011 raise PendulumException(
1012 f"Unable to find occurrence {nth}"
1013 f" of {WeekDay(day_of_week).name.capitalize()} in {unit}"
1014 )
1016 return dt
1018 def _first_of_month(self, day_of_week: WeekDay | None = None) -> Self:
1019 """
1020 Modify to the first occurrence of a given day of the week
1021 in the current month. If no day_of_week is provided,
1022 modify to the first day of the month. Use the supplied consts
1023 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1024 """
1025 dt = self.start_of("day")
1027 if day_of_week is None:
1028 return dt.set(day=1)
1030 month = calendar.monthcalendar(dt.year, dt.month)
1032 calendar_day = day_of_week
1034 if month[0][calendar_day] > 0:
1035 day_of_month = month[0][calendar_day]
1036 else:
1037 day_of_month = month[1][calendar_day]
1039 return dt.set(day=day_of_month)
1041 def _last_of_month(self, day_of_week: WeekDay | None = None) -> Self:
1042 """
1043 Modify to the last occurrence of a given day of the week
1044 in the current month. If no day_of_week is provided,
1045 modify to the last day of the month. Use the supplied consts
1046 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1047 """
1048 dt = self.start_of("day")
1050 if day_of_week is None:
1051 return dt.set(day=self.days_in_month)
1053 month = calendar.monthcalendar(dt.year, dt.month)
1055 calendar_day = day_of_week
1057 if month[-1][calendar_day] > 0:
1058 day_of_month = month[-1][calendar_day]
1059 else:
1060 day_of_month = month[-2][calendar_day]
1062 return dt.set(day=day_of_month)
1064 def _nth_of_month(
1065 self, nth: int, day_of_week: WeekDay | None = None
1066 ) -> Self | None:
1067 """
1068 Modify to the given occurrence of a given day of the week
1069 in the current month. If the calculated occurrence is outside,
1070 the scope of the current month, then return False and no
1071 modifications are made. Use the supplied consts
1072 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1073 """
1074 if nth == 1:
1075 return self.first_of("month", day_of_week)
1077 dt = self.first_of("month")
1078 check = dt.format("%Y-%M")
1079 for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
1080 dt = dt.next(day_of_week)
1082 if dt.format("%Y-%M") == check:
1083 return self.set(day=dt.day).start_of("day")
1085 return None
1087 def _first_of_quarter(self, day_of_week: WeekDay | None = None) -> Self:
1088 """
1089 Modify to the first occurrence of a given day of the week
1090 in the current quarter. If no day_of_week is provided,
1091 modify to the first day of the quarter. Use the supplied consts
1092 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1093 """
1094 return self.on(self.year, self.quarter * 3 - 2, 1).first_of(
1095 "month", day_of_week
1096 )
1098 def _last_of_quarter(self, day_of_week: WeekDay | None = None) -> Self:
1099 """
1100 Modify to the last occurrence of a given day of the week
1101 in the current quarter. If no day_of_week is provided,
1102 modify to the last day of the quarter. Use the supplied consts
1103 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1104 """
1105 return self.on(self.year, self.quarter * 3, 1).last_of("month", day_of_week)
1107 def _nth_of_quarter(
1108 self, nth: int, day_of_week: WeekDay | None = None
1109 ) -> Self | None:
1110 """
1111 Modify to the given occurrence of a given day of the week
1112 in the current quarter. If the calculated occurrence is outside,
1113 the scope of the current quarter, then return False and no
1114 modifications are made. Use the supplied consts
1115 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1116 """
1117 if nth == 1:
1118 return self.first_of("quarter", day_of_week)
1120 dt = self.set(day=1, month=self.quarter * 3)
1121 last_month = dt.month
1122 year = dt.year
1123 dt = dt.first_of("quarter")
1124 for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
1125 dt = dt.next(day_of_week)
1127 if last_month < dt.month or year != dt.year:
1128 return None
1130 return self.on(self.year, dt.month, dt.day).start_of("day")
1132 def _first_of_year(self, day_of_week: WeekDay | None = None) -> Self:
1133 """
1134 Modify to the first occurrence of a given day of the week
1135 in the current year. If no day_of_week is provided,
1136 modify to the first day of the year. Use the supplied consts
1137 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1138 """
1139 return self.set(month=1).first_of("month", day_of_week)
1141 def _last_of_year(self, day_of_week: WeekDay | None = None) -> Self:
1142 """
1143 Modify to the last occurrence of a given day of the week
1144 in the current year. If no day_of_week is provided,
1145 modify to the last day of the year. Use the supplied consts
1146 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1147 """
1148 return self.set(month=MONTHS_PER_YEAR).last_of("month", day_of_week)
1150 def _nth_of_year(self, nth: int, day_of_week: WeekDay | None = None) -> Self | None:
1151 """
1152 Modify to the given occurrence of a given day of the week
1153 in the current year. If the calculated occurrence is outside,
1154 the scope of the current year, then return False and no
1155 modifications are made. Use the supplied consts
1156 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1157 """
1158 if nth == 1:
1159 return self.first_of("year", day_of_week)
1161 dt = self.first_of("year")
1162 year = dt.year
1163 for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
1164 dt = dt.next(day_of_week)
1166 if year != dt.year:
1167 return None
1169 return self.on(self.year, dt.month, dt.day).start_of("day")
1171 def average( # type: ignore[override]
1172 self, dt: datetime.datetime | None = None
1173 ) -> Self:
1174 """
1175 Modify the current instance to the average
1176 of a given instance (default now) and the current instance.
1177 """
1178 if dt is None:
1179 dt = self.now(self.tz)
1181 diff = self.diff(dt, False)
1182 return self.add(
1183 microseconds=(diff.in_seconds() * 1000000 + diff.microseconds) // 2
1184 )
1186 @overload # type: ignore[override]
1187 def __sub__(self, other: datetime.timedelta) -> Self: ...
1189 @overload
1190 def __sub__(self, other: DateTime) -> Interval[datetime.datetime]: ...
1192 def __sub__(
1193 self, other: datetime.datetime | datetime.timedelta
1194 ) -> Self | Interval[datetime.datetime]:
1195 if isinstance(other, datetime.timedelta):
1196 return self._subtract_timedelta(other)
1198 if not isinstance(other, datetime.datetime):
1199 return NotImplemented
1201 if not isinstance(other, self.__class__):
1202 if other.tzinfo is None:
1203 other = pendulum.naive(
1204 other.year,
1205 other.month,
1206 other.day,
1207 other.hour,
1208 other.minute,
1209 other.second,
1210 other.microsecond,
1211 )
1212 else:
1213 other = self.instance(other)
1215 return other.diff(self, False)
1217 def __rsub__(self, other: datetime.datetime) -> Interval[datetime.datetime]:
1218 if not isinstance(other, datetime.datetime):
1219 return NotImplemented
1221 if not isinstance(other, self.__class__):
1222 if other.tzinfo is None:
1223 other = pendulum.naive(
1224 other.year,
1225 other.month,
1226 other.day,
1227 other.hour,
1228 other.minute,
1229 other.second,
1230 other.microsecond,
1231 )
1232 else:
1233 other = self.instance(other)
1235 return self.diff(other, False)
1237 def __add__(self, other: datetime.timedelta) -> Self:
1238 if not isinstance(other, datetime.timedelta):
1239 return NotImplemented
1241 caller = traceback.extract_stack(limit=2)[0].name
1242 if caller == "astimezone":
1243 return super().__add__(other)
1245 return self._add_timedelta_(other)
1247 def __radd__(self, other: datetime.timedelta) -> Self:
1248 return self.__add__(other)
1250 # Native methods override
1252 @classmethod
1253 def fromtimestamp(cls, t: float, tz: datetime.tzinfo | None = None) -> Self:
1254 tzinfo = pendulum._safe_timezone(tz)
1256 return cls.instance(datetime.datetime.fromtimestamp(t, tz=tzinfo), tz=tzinfo)
1258 @classmethod
1259 def utcfromtimestamp(cls, t: float) -> Self:
1260 return cls.instance(datetime.datetime.utcfromtimestamp(t), tz=None)
1262 @classmethod
1263 def fromordinal(cls, n: int) -> Self:
1264 return cls.instance(datetime.datetime.fromordinal(n), tz=None)
1266 @classmethod
1267 def combine(
1268 cls,
1269 date: datetime.date,
1270 time: datetime.time,
1271 tzinfo: datetime.tzinfo | None = None,
1272 ) -> Self:
1273 return cls.instance(datetime.datetime.combine(date, time), tz=tzinfo)
1275 def astimezone(self, tz: datetime.tzinfo | None = None) -> Self:
1276 dt = super().astimezone(tz)
1278 return self.__class__(
1279 dt.year,
1280 dt.month,
1281 dt.day,
1282 dt.hour,
1283 dt.minute,
1284 dt.second,
1285 dt.microsecond,
1286 fold=dt.fold,
1287 tzinfo=dt.tzinfo,
1288 )
1290 def replace(
1291 self,
1292 year: SupportsIndex | None = None,
1293 month: SupportsIndex | None = None,
1294 day: SupportsIndex | None = None,
1295 hour: SupportsIndex | None = None,
1296 minute: SupportsIndex | None = None,
1297 second: SupportsIndex | None = None,
1298 microsecond: SupportsIndex | None = None,
1299 tzinfo: bool | datetime.tzinfo | Literal[True] | None = True,
1300 fold: int | None = None,
1301 ) -> Self:
1302 if year is None:
1303 year = self.year
1304 if month is None:
1305 month = self.month
1306 if day is None:
1307 day = self.day
1308 if hour is None:
1309 hour = self.hour
1310 if minute is None:
1311 minute = self.minute
1312 if second is None:
1313 second = self.second
1314 if microsecond is None:
1315 microsecond = self.microsecond
1316 if tzinfo is True:
1317 tzinfo = self.tzinfo
1318 if fold is None:
1319 fold = self.fold
1321 if tzinfo is not None:
1322 tzinfo = pendulum._safe_timezone(tzinfo)
1324 return self.__class__.create(
1325 year,
1326 month,
1327 day,
1328 hour,
1329 minute,
1330 second,
1331 microsecond,
1332 tz=tzinfo,
1333 fold=fold,
1334 )
1336 def __getnewargs__(self) -> tuple[Self]:
1337 return (self,)
1339 def _getstate(
1340 self, protocol: SupportsIndex = 3
1341 ) -> tuple[int, int, int, int, int, int, int, datetime.tzinfo | None]:
1342 return (
1343 self.year,
1344 self.month,
1345 self.day,
1346 self.hour,
1347 self.minute,
1348 self.second,
1349 self.microsecond,
1350 self.tzinfo,
1351 )
1353 def __reduce__(
1354 self,
1355 ) -> tuple[
1356 type[Self],
1357 tuple[int, int, int, int, int, int, int, datetime.tzinfo | None],
1358 ]:
1359 return self.__reduce_ex__(2)
1361 def __reduce_ex__(
1362 self, protocol: SupportsIndex
1363 ) -> tuple[
1364 type[Self],
1365 tuple[int, int, int, int, int, int, int, datetime.tzinfo | None],
1366 ]:
1367 return self.__class__, self._getstate(protocol)
1369 def __deepcopy__(self, _: dict[int, Self]) -> Self:
1370 return self.__class__(
1371 self.year,
1372 self.month,
1373 self.day,
1374 self.hour,
1375 self.minute,
1376 self.second,
1377 self.microsecond,
1378 tzinfo=self.tz,
1379 fold=self.fold,
1380 )
1382 def _cmp(self, other: datetime.datetime, **kwargs: Any) -> int:
1383 # Fix for pypy which compares using this method
1384 # which would lead to infinite recursion if we didn't override
1385 dt = datetime.datetime(
1386 self.year,
1387 self.month,
1388 self.day,
1389 self.hour,
1390 self.minute,
1391 self.second,
1392 self.microsecond,
1393 tzinfo=self.tz,
1394 fold=self.fold,
1395 )
1397 return 0 if dt == other else 1 if dt > other else -1
1400DateTime.min = DateTime(1, 1, 1, 0, 0, tzinfo=UTC)
1401DateTime.max = DateTime(9999, 12, 31, 23, 59, 59, 999999, tzinfo=UTC)
1402DateTime.EPOCH = DateTime(1970, 1, 1, tzinfo=UTC)