Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pendulum/datetime.py: 38%
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:
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(**delta._signature) # type: ignore[attr-defined]
690 return self.add(seconds=delta.total_seconds())
692 def _subtract_timedelta(self, delta: datetime.timedelta) -> Self:
693 """
694 Remove timedelta duration from the instance.
695 """
696 if isinstance(delta, pendulum.Duration):
697 return self.subtract(
698 years=delta.years, months=delta.months, seconds=delta._total
699 )
701 return self.subtract(seconds=delta.total_seconds())
703 # DIFFERENCES
705 def diff( # type: ignore[override]
706 self, dt: datetime.datetime | None = None, abs: bool = True
707 ) -> Interval:
708 """
709 Returns the difference between two DateTime objects represented as an Interval.
710 """
711 if dt is None:
712 dt = self.now(self.tz)
714 return Interval(self, dt, absolute=abs)
716 def diff_for_humans( # type: ignore[override]
717 self,
718 other: DateTime | None = None,
719 absolute: bool = False,
720 locale: str | None = None,
721 ) -> str:
722 """
723 Get the difference in a human readable format in the current locale.
725 When comparing a value in the past to default now:
726 1 day ago
727 5 months ago
729 When comparing a value in the future to default now:
730 1 day from now
731 5 months from now
733 When comparing a value in the past to another value:
734 1 day before
735 5 months before
737 When comparing a value in the future to another value:
738 1 day after
739 5 months after
740 """
741 is_now = other is None
743 if is_now:
744 other = self.now()
746 diff = self.diff(other)
748 return pendulum.format_diff(diff, is_now, absolute, locale)
750 # Modifiers
751 def start_of(self, unit: str) -> Self:
752 """
753 Returns a copy of the instance with the time reset
754 with the following rules:
756 * second: microsecond set to 0
757 * minute: second and microsecond set to 0
758 * hour: minute, second and microsecond set to 0
759 * day: time to 00:00:00
760 * week: date to first day of the week and time to 00:00:00
761 * month: date to first day of the month and time to 00:00:00
762 * year: date to first day of the year and time to 00:00:00
763 * decade: date to first day of the decade and time to 00:00:00
764 * century: date to first day of century and time to 00:00:00
765 """
766 if unit not in self._MODIFIERS_VALID_UNITS:
767 raise ValueError(f'Invalid unit "{unit}" for start_of()')
769 return cast("Self", getattr(self, f"_start_of_{unit}")())
771 def end_of(self, unit: str) -> Self:
772 """
773 Returns a copy of the instance with the time reset
774 with the following rules:
776 * second: microsecond set to 999999
777 * minute: second set to 59 and microsecond set to 999999
778 * hour: minute and second set to 59 and microsecond set to 999999
779 * day: time to 23:59:59.999999
780 * week: date to last day of the week and time to 23:59:59.999999
781 * month: date to last day of the month and time to 23:59:59.999999
782 * year: date to last day of the year and time to 23:59:59.999999
783 * decade: date to last day of the decade and time to 23:59:59.999999
784 * century: date to last day of century and time to 23:59:59.999999
785 """
786 if unit not in self._MODIFIERS_VALID_UNITS:
787 raise ValueError(f'Invalid unit "{unit}" for end_of()')
789 return cast("Self", getattr(self, f"_end_of_{unit}")())
791 def _start_of_second(self) -> Self:
792 """
793 Reset microseconds to 0.
794 """
795 return self.set(microsecond=0)
797 def _end_of_second(self) -> Self:
798 """
799 Set microseconds to 999999.
800 """
801 return self.set(microsecond=999999)
803 def _start_of_minute(self) -> Self:
804 """
805 Reset seconds and microseconds to 0.
806 """
807 return self.set(second=0, microsecond=0)
809 def _end_of_minute(self) -> Self:
810 """
811 Set seconds to 59 and microseconds to 999999.
812 """
813 return self.set(second=59, microsecond=999999)
815 def _start_of_hour(self) -> Self:
816 """
817 Reset minutes, seconds and microseconds to 0.
818 """
819 return self.set(minute=0, second=0, microsecond=0)
821 def _end_of_hour(self) -> Self:
822 """
823 Set minutes and seconds to 59 and microseconds to 999999.
824 """
825 return self.set(minute=59, second=59, microsecond=999999)
827 def _start_of_day(self) -> Self:
828 """
829 Reset the time to 00:00:00.
830 """
831 return self.at(0, 0, 0, 0)
833 def _end_of_day(self) -> Self:
834 """
835 Reset the time to 23:59:59.999999.
836 """
837 return self.at(23, 59, 59, 999999)
839 def _start_of_month(self) -> Self:
840 """
841 Reset the date to the first day of the month and the time to 00:00:00.
842 """
843 return self.set(self.year, self.month, 1, 0, 0, 0, 0)
845 def _end_of_month(self) -> Self:
846 """
847 Reset the date to the last day of the month
848 and the time to 23:59:59.999999.
849 """
850 return self.set(self.year, self.month, self.days_in_month, 23, 59, 59, 999999)
852 def _start_of_year(self) -> Self:
853 """
854 Reset the date to the first day of the year and the time to 00:00:00.
855 """
856 return self.set(self.year, 1, 1, 0, 0, 0, 0)
858 def _end_of_year(self) -> Self:
859 """
860 Reset the date to the last day of the year
861 and the time to 23:59:59.999999.
862 """
863 return self.set(self.year, 12, 31, 23, 59, 59, 999999)
865 def _start_of_decade(self) -> Self:
866 """
867 Reset the date to the first day of the decade
868 and the time to 00:00:00.
869 """
870 year = self.year - self.year % YEARS_PER_DECADE
871 return self.set(year, 1, 1, 0, 0, 0, 0)
873 def _end_of_decade(self) -> Self:
874 """
875 Reset the date to the last day of the decade
876 and the time to 23:59:59.999999.
877 """
878 year = self.year - self.year % YEARS_PER_DECADE + YEARS_PER_DECADE - 1
880 return self.set(year, 12, 31, 23, 59, 59, 999999)
882 def _start_of_century(self) -> Self:
883 """
884 Reset the date to the first day of the century
885 and the time to 00:00:00.
886 """
887 year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + 1
889 return self.set(year, 1, 1, 0, 0, 0, 0)
891 def _end_of_century(self) -> Self:
892 """
893 Reset the date to the last day of the century
894 and the time to 23:59:59.999999.
895 """
896 year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + YEARS_PER_CENTURY
898 return self.set(year, 12, 31, 23, 59, 59, 999999)
900 def _start_of_week(self) -> Self:
901 """
902 Reset the date to the first day of the week
903 and the time to 00:00:00.
904 """
905 dt = self
907 if self.day_of_week != pendulum._WEEK_STARTS_AT:
908 dt = self.previous(pendulum._WEEK_STARTS_AT)
910 return dt.start_of("day")
912 def _end_of_week(self) -> Self:
913 """
914 Reset the date to the last day of the week
915 and the time to 23:59:59.
916 """
917 dt = self
919 if self.day_of_week != pendulum._WEEK_ENDS_AT:
920 dt = self.next(pendulum._WEEK_ENDS_AT)
922 return dt.end_of("day")
924 def next(self, day_of_week: WeekDay | None = None, keep_time: bool = False) -> Self:
925 """
926 Modify to the next occurrence of a given day of the week.
927 If no day_of_week is provided, modify to the next occurrence
928 of the current day of the week. Use the supplied consts
929 to indicate the desired day_of_week, ex. DateTime.MONDAY.
930 """
931 if day_of_week is None:
932 day_of_week = self.day_of_week
934 if day_of_week < WeekDay.MONDAY or day_of_week > WeekDay.SUNDAY:
935 raise ValueError("Invalid day of week")
937 dt = self if keep_time else self.start_of("day")
939 dt = dt.add(days=1)
940 while dt.day_of_week != day_of_week:
941 dt = dt.add(days=1)
943 return dt
945 def previous(
946 self, day_of_week: WeekDay | None = None, keep_time: bool = False
947 ) -> Self:
948 """
949 Modify to the previous occurrence of a given day of the week.
950 If no day_of_week is provided, modify to the previous occurrence
951 of the current day of the week. Use the supplied consts
952 to indicate the desired day_of_week, ex. DateTime.MONDAY.
953 """
954 if day_of_week is None:
955 day_of_week = self.day_of_week
957 if day_of_week < WeekDay.MONDAY or day_of_week > WeekDay.SUNDAY:
958 raise ValueError("Invalid day of week")
960 dt = self if keep_time else self.start_of("day")
962 dt = dt.subtract(days=1)
963 while dt.day_of_week != day_of_week:
964 dt = dt.subtract(days=1)
966 return dt
968 def first_of(self, unit: str, day_of_week: WeekDay | None = None) -> Self:
969 """
970 Returns an instance set to the first occurrence
971 of a given day of the week in the current unit.
972 If no day_of_week is provided, modify to the first day of the unit.
973 Use the supplied consts to indicate the desired day_of_week,
974 ex. DateTime.MONDAY.
976 Supported units are month, quarter and year.
977 """
978 if unit not in ["month", "quarter", "year"]:
979 raise ValueError(f'Invalid unit "{unit}" for first_of()')
981 return cast("Self", getattr(self, f"_first_of_{unit}")(day_of_week))
983 def last_of(self, unit: str, day_of_week: WeekDay | None = None) -> Self:
984 """
985 Returns an instance set to the last occurrence
986 of a given day of the week in the current unit.
987 If no day_of_week is provided, modify to the last day of the unit.
988 Use the supplied consts to indicate the desired day_of_week,
989 ex. DateTime.MONDAY.
991 Supported units are month, quarter and year.
992 """
993 if unit not in ["month", "quarter", "year"]:
994 raise ValueError(f'Invalid unit "{unit}" for first_of()')
996 return cast("Self", getattr(self, f"_last_of_{unit}")(day_of_week))
998 def nth_of(self, unit: str, nth: int, day_of_week: WeekDay) -> Self:
999 """
1000 Returns a new instance set to the given occurrence
1001 of a given day of the week in the current unit.
1002 If the calculated occurrence is outside the scope of the current unit,
1003 then raise an error. Use the supplied consts
1004 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1006 Supported units are month, quarter and year.
1007 """
1008 if unit not in ["month", "quarter", "year"]:
1009 raise ValueError(f'Invalid unit "{unit}" for first_of()')
1011 dt = cast(Optional["Self"], getattr(self, f"_nth_of_{unit}")(nth, day_of_week))
1012 if not dt:
1013 raise PendulumException(
1014 f"Unable to find occurrence {nth}"
1015 f" of {WeekDay(day_of_week).name.capitalize()} in {unit}"
1016 )
1018 return dt
1020 def _first_of_month(self, day_of_week: WeekDay | None = None) -> Self:
1021 """
1022 Modify to the first occurrence of a given day of the week
1023 in the current month. If no day_of_week is provided,
1024 modify to the first day of the month. Use the supplied consts
1025 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1026 """
1027 dt = self.start_of("day")
1029 if day_of_week is None:
1030 return dt.set(day=1)
1032 month = calendar.monthcalendar(dt.year, dt.month)
1034 calendar_day = day_of_week
1036 if month[0][calendar_day] > 0:
1037 day_of_month = month[0][calendar_day]
1038 else:
1039 day_of_month = month[1][calendar_day]
1041 return dt.set(day=day_of_month)
1043 def _last_of_month(self, day_of_week: WeekDay | None = None) -> Self:
1044 """
1045 Modify to the last occurrence of a given day of the week
1046 in the current month. If no day_of_week is provided,
1047 modify to the last day of the month. Use the supplied consts
1048 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1049 """
1050 dt = self.start_of("day")
1052 if day_of_week is None:
1053 return dt.set(day=self.days_in_month)
1055 month = calendar.monthcalendar(dt.year, dt.month)
1057 calendar_day = day_of_week
1059 if month[-1][calendar_day] > 0:
1060 day_of_month = month[-1][calendar_day]
1061 else:
1062 day_of_month = month[-2][calendar_day]
1064 return dt.set(day=day_of_month)
1066 def _nth_of_month(
1067 self, nth: int, day_of_week: WeekDay | None = None
1068 ) -> Self | None:
1069 """
1070 Modify to the given occurrence of a given day of the week
1071 in the current month. If the calculated occurrence is outside,
1072 the scope of the current month, then return False and no
1073 modifications are made. Use the supplied consts
1074 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1075 """
1076 if nth == 1:
1077 return self.first_of("month", day_of_week)
1079 dt = self.first_of("month")
1080 check = dt.format("%Y-%M")
1081 for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
1082 dt = dt.next(day_of_week)
1084 if dt.format("%Y-%M") == check:
1085 return self.set(day=dt.day).start_of("day")
1087 return None
1089 def _first_of_quarter(self, day_of_week: WeekDay | None = None) -> Self:
1090 """
1091 Modify to the first occurrence of a given day of the week
1092 in the current quarter. If no day_of_week is provided,
1093 modify to the first day of the quarter. Use the supplied consts
1094 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1095 """
1096 return self.on(self.year, self.quarter * 3 - 2, 1).first_of(
1097 "month", day_of_week
1098 )
1100 def _last_of_quarter(self, day_of_week: WeekDay | None = None) -> Self:
1101 """
1102 Modify to the last occurrence of a given day of the week
1103 in the current quarter. If no day_of_week is provided,
1104 modify to the last day of the quarter. Use the supplied consts
1105 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1106 """
1107 return self.on(self.year, self.quarter * 3, 1).last_of("month", day_of_week)
1109 def _nth_of_quarter(
1110 self, nth: int, day_of_week: WeekDay | None = None
1111 ) -> Self | None:
1112 """
1113 Modify to the given occurrence of a given day of the week
1114 in the current quarter. If the calculated occurrence is outside,
1115 the scope of the current quarter, then return False and no
1116 modifications are made. Use the supplied consts
1117 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1118 """
1119 if nth == 1:
1120 return self.first_of("quarter", day_of_week)
1122 dt = self.set(day=1, month=self.quarter * 3)
1123 last_month = dt.month
1124 year = dt.year
1125 dt = dt.first_of("quarter")
1126 for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
1127 dt = dt.next(day_of_week)
1129 if last_month < dt.month or year != dt.year:
1130 return None
1132 return self.on(self.year, dt.month, dt.day).start_of("day")
1134 def _first_of_year(self, day_of_week: WeekDay | None = None) -> Self:
1135 """
1136 Modify to the first occurrence of a given day of the week
1137 in the current year. If no day_of_week is provided,
1138 modify to the first day of the year. Use the supplied consts
1139 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1140 """
1141 return self.set(month=1).first_of("month", day_of_week)
1143 def _last_of_year(self, day_of_week: WeekDay | None = None) -> Self:
1144 """
1145 Modify to the last occurrence of a given day of the week
1146 in the current year. If no day_of_week is provided,
1147 modify to the last day of the year. Use the supplied consts
1148 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1149 """
1150 return self.set(month=MONTHS_PER_YEAR).last_of("month", day_of_week)
1152 def _nth_of_year(self, nth: int, day_of_week: WeekDay | None = None) -> Self | None:
1153 """
1154 Modify to the given occurrence of a given day of the week
1155 in the current year. If the calculated occurrence is outside,
1156 the scope of the current year, then return False and no
1157 modifications are made. Use the supplied consts
1158 to indicate the desired day_of_week, ex. DateTime.MONDAY.
1159 """
1160 if nth == 1:
1161 return self.first_of("year", day_of_week)
1163 dt = self.first_of("year")
1164 year = dt.year
1165 for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
1166 dt = dt.next(day_of_week)
1168 if year != dt.year:
1169 return None
1171 return self.on(self.year, dt.month, dt.day).start_of("day")
1173 def average( # type: ignore[override]
1174 self, dt: datetime.datetime | None = None
1175 ) -> Self:
1176 """
1177 Modify the current instance to the average
1178 of a given instance (default now) and the current instance.
1179 """
1180 if dt is None:
1181 dt = self.now(self.tz)
1183 diff = self.diff(dt, False)
1184 return self.add(
1185 microseconds=(diff.in_seconds() * 1000000 + diff.microseconds) // 2
1186 )
1188 @overload # type: ignore[override]
1189 def __sub__(self, other: datetime.timedelta) -> Self:
1190 ...
1192 @overload
1193 def __sub__(self, other: DateTime) -> Interval:
1194 ...
1196 def __sub__(self, other: datetime.datetime | datetime.timedelta) -> Self | Interval:
1197 if isinstance(other, datetime.timedelta):
1198 return self._subtract_timedelta(other)
1200 if not isinstance(other, datetime.datetime):
1201 return NotImplemented
1203 if not isinstance(other, self.__class__):
1204 if other.tzinfo is None:
1205 other = pendulum.naive(
1206 other.year,
1207 other.month,
1208 other.day,
1209 other.hour,
1210 other.minute,
1211 other.second,
1212 other.microsecond,
1213 )
1214 else:
1215 other = self.instance(other)
1217 return other.diff(self, False)
1219 def __rsub__(self, other: datetime.datetime) -> Interval:
1220 if not isinstance(other, datetime.datetime):
1221 return NotImplemented
1223 if not isinstance(other, self.__class__):
1224 if other.tzinfo is None:
1225 other = pendulum.naive(
1226 other.year,
1227 other.month,
1228 other.day,
1229 other.hour,
1230 other.minute,
1231 other.second,
1232 other.microsecond,
1233 )
1234 else:
1235 other = self.instance(other)
1237 return self.diff(other, False)
1239 def __add__(self, other: datetime.timedelta) -> Self:
1240 if not isinstance(other, datetime.timedelta):
1241 return NotImplemented
1243 caller = traceback.extract_stack(limit=2)[0].name
1244 if caller == "astimezone":
1245 return super().__add__(other)
1247 return self._add_timedelta_(other)
1249 def __radd__(self, other: datetime.timedelta) -> Self:
1250 return self.__add__(other)
1252 # Native methods override
1254 @classmethod
1255 def fromtimestamp(cls, t: float, tz: datetime.tzinfo | None = None) -> Self:
1256 tzinfo = pendulum._safe_timezone(tz)
1258 return cls.instance(datetime.datetime.fromtimestamp(t, tz=tzinfo), tz=tzinfo)
1260 @classmethod
1261 def utcfromtimestamp(cls, t: float) -> Self:
1262 return cls.instance(datetime.datetime.utcfromtimestamp(t), tz=None)
1264 @classmethod
1265 def fromordinal(cls, n: int) -> Self:
1266 return cls.instance(datetime.datetime.fromordinal(n), tz=None)
1268 @classmethod
1269 def combine(
1270 cls,
1271 date: datetime.date,
1272 time: datetime.time,
1273 tzinfo: datetime.tzinfo | None = None,
1274 ) -> Self:
1275 return cls.instance(datetime.datetime.combine(date, time), tz=tzinfo)
1277 def astimezone(self, tz: datetime.tzinfo | None = None) -> Self:
1278 dt = super().astimezone(tz)
1280 return self.__class__(
1281 dt.year,
1282 dt.month,
1283 dt.day,
1284 dt.hour,
1285 dt.minute,
1286 dt.second,
1287 dt.microsecond,
1288 fold=dt.fold,
1289 tzinfo=dt.tzinfo,
1290 )
1292 def replace(
1293 self,
1294 year: SupportsIndex | None = None,
1295 month: SupportsIndex | None = None,
1296 day: SupportsIndex | None = None,
1297 hour: SupportsIndex | None = None,
1298 minute: SupportsIndex | None = None,
1299 second: SupportsIndex | None = None,
1300 microsecond: SupportsIndex | None = None,
1301 tzinfo: bool | datetime.tzinfo | Literal[True] | None = True,
1302 fold: int | None = None,
1303 ) -> Self:
1304 if year is None:
1305 year = self.year
1306 if month is None:
1307 month = self.month
1308 if day is None:
1309 day = self.day
1310 if hour is None:
1311 hour = self.hour
1312 if minute is None:
1313 minute = self.minute
1314 if second is None:
1315 second = self.second
1316 if microsecond is None:
1317 microsecond = self.microsecond
1318 if tzinfo is True:
1319 tzinfo = self.tzinfo
1320 if fold is None:
1321 fold = self.fold
1323 if tzinfo is not None:
1324 tzinfo = pendulum._safe_timezone(tzinfo)
1326 return self.__class__.create(
1327 year,
1328 month,
1329 day,
1330 hour,
1331 minute,
1332 second,
1333 microsecond,
1334 tz=tzinfo,
1335 fold=fold,
1336 )
1338 def __getnewargs__(self) -> tuple[Self]:
1339 return (self,)
1341 def _getstate(
1342 self, protocol: SupportsIndex = 3
1343 ) -> tuple[int, int, int, int, int, int, int, datetime.tzinfo | None]:
1344 return (
1345 self.year,
1346 self.month,
1347 self.day,
1348 self.hour,
1349 self.minute,
1350 self.second,
1351 self.microsecond,
1352 self.tzinfo,
1353 )
1355 def __reduce__(
1356 self,
1357 ) -> tuple[
1358 type[Self],
1359 tuple[int, int, int, int, int, int, int, datetime.tzinfo | None],
1360 ]:
1361 return self.__reduce_ex__(2)
1363 def __reduce_ex__(
1364 self, protocol: SupportsIndex
1365 ) -> tuple[
1366 type[Self],
1367 tuple[int, int, int, int, int, int, int, datetime.tzinfo | None],
1368 ]:
1369 return self.__class__, self._getstate(protocol)
1371 def __deepcopy__(self, _: dict[int, Self]) -> Self:
1372 return self.__class__(
1373 self.year,
1374 self.month,
1375 self.day,
1376 self.hour,
1377 self.minute,
1378 self.second,
1379 self.microsecond,
1380 tzinfo=self.tz,
1381 fold=self.fold,
1382 )
1384 def _cmp(self, other: datetime.datetime, **kwargs: Any) -> int:
1385 # Fix for pypy which compares using this method
1386 # which would lead to infinite recursion if we didn't override
1387 dt = datetime.datetime(
1388 self.year,
1389 self.month,
1390 self.day,
1391 self.hour,
1392 self.minute,
1393 self.second,
1394 self.microsecond,
1395 tzinfo=self.tz,
1396 fold=self.fold,
1397 )
1399 return 0 if dt == other else 1 if dt > other else -1
1402DateTime.min = DateTime(1, 1, 1, 0, 0, tzinfo=UTC)
1403DateTime.max = DateTime(9999, 12, 31, 23, 59, 59, 999999, tzinfo=UTC)
1404DateTime.EPOCH = DateTime(1970, 1, 1, tzinfo=UTC)