Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pendulum/date.py: 35%
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
1# The following is only needed because of Python 3.7
2# mypy: no-warn-unused-ignores
3from __future__ import annotations
5import calendar
6import math
8from datetime import date
9from datetime import datetime
10from datetime import timedelta
11from typing import TYPE_CHECKING
12from typing import ClassVar
13from typing import NoReturn
14from typing import cast
15from typing import overload
17import pendulum
19from pendulum.constants import MONTHS_PER_YEAR
20from pendulum.constants import YEARS_PER_CENTURY
21from pendulum.constants import YEARS_PER_DECADE
22from pendulum.day import WeekDay
23from pendulum.exceptions import PendulumException
24from pendulum.helpers import add_duration
25from pendulum.interval import Interval
26from pendulum.mixins.default import FormattableMixin
29if TYPE_CHECKING:
30 from typing_extensions import Self
31 from typing_extensions import SupportsIndex
34class Date(FormattableMixin, date):
35 _MODIFIERS_VALID_UNITS: ClassVar[list[str]] = [
36 "day",
37 "week",
38 "month",
39 "year",
40 "decade",
41 "century",
42 ]
44 # Getters/Setters
46 def set(
47 self, year: int | None = None, month: int | None = None, day: int | None = None
48 ) -> Self:
49 return self.replace(year=year, month=month, day=day)
51 @property
52 def day_of_week(self) -> WeekDay:
53 """
54 Returns the day of the week (0-6).
55 """
56 return WeekDay(self.weekday())
58 @property
59 def day_of_year(self) -> int:
60 """
61 Returns the day of the year (1-366).
62 """
63 k = 1 if self.is_leap_year() else 2
65 return (275 * self.month) // 9 - k * ((self.month + 9) // 12) + self.day - 30
67 @property
68 def week_of_year(self) -> int:
69 return self.isocalendar()[1]
71 @property
72 def days_in_month(self) -> int:
73 return calendar.monthrange(self.year, self.month)[1]
75 @property
76 def week_of_month(self) -> int:
77 return math.ceil((self.day + self.first_of("month").isoweekday() - 1) / 7)
79 @property
80 def age(self) -> int:
81 return self.diff(abs=False).in_years()
83 @property
84 def quarter(self) -> int:
85 return math.ceil(self.month / 3)
87 # String Formatting
89 def to_date_string(self) -> str:
90 """
91 Format the instance as date.
93 :rtype: str
94 """
95 return self.strftime("%Y-%m-%d")
97 def to_formatted_date_string(self) -> str:
98 """
99 Format the instance as a readable date.
101 :rtype: str
102 """
103 return self.strftime("%b %d, %Y")
105 def __repr__(self) -> str:
106 return f"{self.__class__.__name__}({self.year}, {self.month}, {self.day})"
108 # COMPARISONS
110 def closest(self, dt1: date, dt2: date) -> Self:
111 """
112 Get the closest date from the instance.
113 """
114 dt1 = self.__class__(dt1.year, dt1.month, dt1.day)
115 dt2 = self.__class__(dt2.year, dt2.month, dt2.day)
117 if self.diff(dt1).in_seconds() < self.diff(dt2).in_seconds():
118 return dt1
120 return dt2
122 def farthest(self, dt1: date, dt2: date) -> Self:
123 """
124 Get the farthest date from the instance.
125 """
126 dt1 = self.__class__(dt1.year, dt1.month, dt1.day)
127 dt2 = self.__class__(dt2.year, dt2.month, dt2.day)
129 if self.diff(dt1).in_seconds() > self.diff(dt2).in_seconds():
130 return dt1
132 return dt2
134 def is_future(self) -> bool:
135 """
136 Determines if the instance is in the future, ie. greater than now.
137 """
138 return self > self.today()
140 def is_past(self) -> bool:
141 """
142 Determines if the instance is in the past, ie. less than now.
143 """
144 return self < self.today()
146 def is_leap_year(self) -> bool:
147 """
148 Determines if the instance is a leap year.
149 """
150 return calendar.isleap(self.year)
152 def is_long_year(self) -> bool:
153 """
154 Determines if the instance is a long year
156 See link `<https://en.wikipedia.org/wiki/ISO_8601#Week_dates>`_
157 """
158 return Date(self.year, 12, 28).isocalendar()[1] == 53
160 def is_same_day(self, dt: date) -> bool:
161 """
162 Checks if the passed in date is the same day as the instance current day.
163 """
164 return self == dt
166 def is_anniversary(self, dt: date | None = None) -> bool:
167 """
168 Check if it's the anniversary.
170 Compares the date/month values of the two dates.
171 """
172 if dt is None:
173 dt = self.__class__.today()
175 instance = self.__class__(dt.year, dt.month, dt.day)
177 return (self.month, self.day) == (instance.month, instance.day)
179 # the additional method for checking if today is the anniversary day
180 # the alias is provided to start using a new name and keep the backward
181 # compatibility the old name can be completely replaced with the new in
182 # one of the future versions
183 is_birthday = is_anniversary
185 # ADDITIONS AND SUBTRACTIONS
187 def add(
188 self, years: int = 0, months: int = 0, weeks: int = 0, days: int = 0
189 ) -> Self:
190 """
191 Add duration to the instance.
193 :param years: The number of years
194 :param months: The number of months
195 :param weeks: The number of weeks
196 :param days: The number of days
197 """
198 dt = add_duration(
199 date(self.year, self.month, self.day),
200 years=years,
201 months=months,
202 weeks=weeks,
203 days=days,
204 )
206 return self.__class__(dt.year, dt.month, dt.day)
208 def subtract(
209 self, years: int = 0, months: int = 0, weeks: int = 0, days: int = 0
210 ) -> Self:
211 """
212 Remove duration from the instance.
214 :param years: The number of years
215 :param months: The number of months
216 :param weeks: The number of weeks
217 :param days: The number of days
218 """
219 return self.add(years=-years, months=-months, weeks=-weeks, days=-days)
221 def _add_timedelta(self, delta: timedelta) -> Self:
222 """
223 Add timedelta duration to the instance.
225 :param delta: The timedelta instance
226 """
227 if isinstance(delta, pendulum.Duration):
228 return self.add(
229 years=delta.years,
230 months=delta.months,
231 weeks=delta.weeks,
232 days=delta.remaining_days,
233 )
235 return self.add(days=delta.days)
237 def _subtract_timedelta(self, delta: timedelta) -> Self:
238 """
239 Remove timedelta duration from the instance.
241 :param delta: The timedelta instance
242 """
243 if isinstance(delta, pendulum.Duration):
244 return self.subtract(
245 years=delta.years,
246 months=delta.months,
247 weeks=delta.weeks,
248 days=delta.remaining_days,
249 )
251 return self.subtract(days=delta.days)
253 def __add__(self, other: timedelta) -> Self:
254 if not isinstance(other, timedelta):
255 return NotImplemented
257 return self._add_timedelta(other)
259 @overload # type: ignore[override] # this is only needed because of Python 3.7
260 def __sub__(self, __delta: timedelta) -> Self:
261 ...
263 @overload
264 def __sub__(self, __dt: datetime) -> NoReturn:
265 ...
267 @overload
268 def __sub__(self, __dt: Self) -> Interval:
269 ...
271 def __sub__(self, other: timedelta | date) -> Self | Interval:
272 if isinstance(other, timedelta):
273 return self._subtract_timedelta(other)
275 if not isinstance(other, date):
276 return NotImplemented
278 dt = self.__class__(other.year, other.month, other.day)
280 return dt.diff(self, False)
282 # DIFFERENCES
284 def diff(self, dt: date | None = None, abs: bool = True) -> Interval:
285 """
286 Returns the difference between two Date objects as an Interval.
288 :param dt: The date to compare to (defaults to today)
289 :param abs: Whether to return an absolute interval or not
290 """
291 if dt is None:
292 dt = self.today()
294 return Interval(self, Date(dt.year, dt.month, dt.day), absolute=abs)
296 def diff_for_humans(
297 self,
298 other: date | None = None,
299 absolute: bool = False,
300 locale: str | None = None,
301 ) -> str:
302 """
303 Get the difference in a human readable format in the current locale.
305 When comparing a value in the past to default now:
306 1 day ago
307 5 months ago
309 When comparing a value in the future to default now:
310 1 day from now
311 5 months from now
313 When comparing a value in the past to another value:
314 1 day before
315 5 months before
317 When comparing a value in the future to another value:
318 1 day after
319 5 months after
321 :param other: The date to compare to (defaults to today)
322 :param absolute: removes time difference modifiers ago, after, etc
323 :param locale: The locale to use for localization
324 """
325 is_now = other is None
327 if is_now:
328 other = self.today()
330 diff = self.diff(other)
332 return pendulum.format_diff(diff, is_now, absolute, locale)
334 # MODIFIERS
336 def start_of(self, unit: str) -> Self:
337 """
338 Returns a copy of the instance with the time reset
339 with the following rules:
341 * day: time to 00:00:00
342 * week: date to first day of the week and time to 00:00:00
343 * month: date to first day of the month and time to 00:00:00
344 * year: date to first day of the year and time to 00:00:00
345 * decade: date to first day of the decade and time to 00:00:00
346 * century: date to first day of century and time to 00:00:00
348 :param unit: The unit to reset to
349 """
350 if unit not in self._MODIFIERS_VALID_UNITS:
351 raise ValueError(f'Invalid unit "{unit}" for start_of()')
353 return cast("Self", getattr(self, f"_start_of_{unit}")())
355 def end_of(self, unit: str) -> Self:
356 """
357 Returns a copy of the instance with the time reset
358 with the following rules:
360 * week: date to last day of the week
361 * month: date to last day of the month
362 * year: date to last day of the year
363 * decade: date to last day of the decade
364 * century: date to last day of century
366 :param unit: The unit to reset to
367 """
368 if unit not in self._MODIFIERS_VALID_UNITS:
369 raise ValueError(f'Invalid unit "{unit}" for end_of()')
371 return cast("Self", getattr(self, f"_end_of_{unit}")())
373 def _start_of_day(self) -> Self:
374 """
375 Compatibility method.
376 """
377 return self
379 def _end_of_day(self) -> Self:
380 """
381 Compatibility method
382 """
383 return self
385 def _start_of_month(self) -> Self:
386 """
387 Reset the date to the first day of the month.
388 """
389 return self.set(self.year, self.month, 1)
391 def _end_of_month(self) -> Self:
392 """
393 Reset the date to the last day of the month.
394 """
395 return self.set(self.year, self.month, self.days_in_month)
397 def _start_of_year(self) -> Self:
398 """
399 Reset the date to the first day of the year.
400 """
401 return self.set(self.year, 1, 1)
403 def _end_of_year(self) -> Self:
404 """
405 Reset the date to the last day of the year.
406 """
407 return self.set(self.year, 12, 31)
409 def _start_of_decade(self) -> Self:
410 """
411 Reset the date to the first day of the decade.
412 """
413 year = self.year - self.year % YEARS_PER_DECADE
415 return self.set(year, 1, 1)
417 def _end_of_decade(self) -> Self:
418 """
419 Reset the date to the last day of the decade.
420 """
421 year = self.year - self.year % YEARS_PER_DECADE + YEARS_PER_DECADE - 1
423 return self.set(year, 12, 31)
425 def _start_of_century(self) -> Self:
426 """
427 Reset the date to the first day of the century.
428 """
429 year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + 1
431 return self.set(year, 1, 1)
433 def _end_of_century(self) -> Self:
434 """
435 Reset the date to the last day of the century.
436 """
437 year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + YEARS_PER_CENTURY
439 return self.set(year, 12, 31)
441 def _start_of_week(self) -> Self:
442 """
443 Reset the date to the first day of the week.
444 """
445 dt = self
447 if self.day_of_week != pendulum._WEEK_STARTS_AT:
448 dt = self.previous(pendulum._WEEK_STARTS_AT)
450 return dt.start_of("day")
452 def _end_of_week(self) -> Self:
453 """
454 Reset the date to the last day of the week.
455 """
456 dt = self
458 if self.day_of_week != pendulum._WEEK_ENDS_AT:
459 dt = self.next(pendulum._WEEK_ENDS_AT)
461 return dt.end_of("day")
463 def next(self, day_of_week: WeekDay | None = None) -> Self:
464 """
465 Modify to the next occurrence of a given day of the week.
466 If no day_of_week is provided, modify to the next occurrence
467 of the current day of the week. Use the supplied consts
468 to indicate the desired day_of_week, ex. pendulum.MONDAY.
470 :param day_of_week: The next day of week to reset to.
471 """
472 if day_of_week is None:
473 day_of_week = self.day_of_week
475 if day_of_week < WeekDay.MONDAY or day_of_week > WeekDay.SUNDAY:
476 raise ValueError("Invalid day of week")
478 dt = self.add(days=1)
479 while dt.day_of_week != day_of_week:
480 dt = dt.add(days=1)
482 return dt
484 def previous(self, day_of_week: WeekDay | None = None) -> Self:
485 """
486 Modify to the previous occurrence of a given day of the week.
487 If no day_of_week is provided, modify to the previous occurrence
488 of the current day of the week. Use the supplied consts
489 to indicate the desired day_of_week, ex. pendulum.MONDAY.
491 :param day_of_week: The previous day of week to reset to.
492 """
493 if day_of_week is None:
494 day_of_week = self.day_of_week
496 if day_of_week < WeekDay.MONDAY or day_of_week > WeekDay.SUNDAY:
497 raise ValueError("Invalid day of week")
499 dt = self.subtract(days=1)
500 while dt.day_of_week != day_of_week:
501 dt = dt.subtract(days=1)
503 return dt
505 def first_of(self, unit: str, day_of_week: WeekDay | None = None) -> Self:
506 """
507 Returns an instance set to the first occurrence
508 of a given day of the week in the current unit.
509 If no day_of_week is provided, modify to the first day of the unit.
510 Use the supplied consts to indicate the desired day_of_week,
511 ex. pendulum.MONDAY.
513 Supported units are month, quarter and year.
515 :param unit: The unit to use
516 :param day_of_week: The day of week to reset to.
517 """
518 if unit not in ["month", "quarter", "year"]:
519 raise ValueError(f'Invalid unit "{unit}" for first_of()')
521 return cast("Self", getattr(self, f"_first_of_{unit}")(day_of_week))
523 def last_of(self, unit: str, day_of_week: WeekDay | None = None) -> Self:
524 """
525 Returns an instance set to the last occurrence
526 of a given day of the week in the current unit.
527 If no day_of_week is provided, modify to the last day of the unit.
528 Use the supplied consts to indicate the desired day_of_week,
529 ex. pendulum.MONDAY.
531 Supported units are month, quarter and year.
533 :param unit: The unit to use
534 :param day_of_week: The day of week to reset to.
535 """
536 if unit not in ["month", "quarter", "year"]:
537 raise ValueError(f'Invalid unit "{unit}" for first_of()')
539 return cast("Self", getattr(self, f"_last_of_{unit}")(day_of_week))
541 def nth_of(self, unit: str, nth: int, day_of_week: WeekDay) -> Self:
542 """
543 Returns a new instance set to the given occurrence
544 of a given day of the week in the current unit.
545 If the calculated occurrence is outside the scope of the current unit,
546 then raise an error. Use the supplied consts
547 to indicate the desired day_of_week, ex. pendulum.MONDAY.
549 Supported units are month, quarter and year.
551 :param unit: The unit to use
552 :param nth: The occurrence to use
553 :param day_of_week: The day of week to set to.
554 """
555 if unit not in ["month", "quarter", "year"]:
556 raise ValueError(f'Invalid unit "{unit}" for first_of()')
558 dt = cast("Self", getattr(self, f"_nth_of_{unit}")(nth, day_of_week))
559 if not dt:
560 raise PendulumException(
561 f"Unable to find occurrence {nth}"
562 f" of {WeekDay(day_of_week).name.capitalize()} in {unit}"
563 )
565 return dt
567 def _first_of_month(self, day_of_week: WeekDay) -> Self:
568 """
569 Modify to the first occurrence of a given day of the week
570 in the current month. If no day_of_week is provided,
571 modify to the first day of the month. Use the supplied consts
572 to indicate the desired day_of_week, ex. pendulum.MONDAY.
574 :param day_of_week: The day of week to set to.
575 """
576 dt = self
578 if day_of_week is None:
579 return dt.set(day=1)
581 month = calendar.monthcalendar(dt.year, dt.month)
583 calendar_day = day_of_week
585 if month[0][calendar_day] > 0:
586 day_of_month = month[0][calendar_day]
587 else:
588 day_of_month = month[1][calendar_day]
590 return dt.set(day=day_of_month)
592 def _last_of_month(self, day_of_week: WeekDay | None = None) -> Self:
593 """
594 Modify to the last occurrence of a given day of the week
595 in the current month. If no day_of_week is provided,
596 modify to the last day of the month. Use the supplied consts
597 to indicate the desired day_of_week, ex. pendulum.MONDAY.
599 :param day_of_week: The day of week to set to.
600 """
601 dt = self
603 if day_of_week is None:
604 return dt.set(day=self.days_in_month)
606 month = calendar.monthcalendar(dt.year, dt.month)
608 calendar_day = day_of_week
610 if month[-1][calendar_day] > 0:
611 day_of_month = month[-1][calendar_day]
612 else:
613 day_of_month = month[-2][calendar_day]
615 return dt.set(day=day_of_month)
617 def _nth_of_month(self, nth: int, day_of_week: WeekDay) -> Self | None:
618 """
619 Modify to the given occurrence of a given day of the week
620 in the current month. If the calculated occurrence is outside,
621 the scope of the current month, then return False and no
622 modifications are made. Use the supplied consts
623 to indicate the desired day_of_week, ex. pendulum.MONDAY.
624 """
625 if nth == 1:
626 return self.first_of("month", day_of_week)
628 dt = self.first_of("month")
629 check = dt.format("YYYY-MM")
630 for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
631 dt = dt.next(day_of_week)
633 if dt.format("YYYY-MM") == check:
634 return self.set(day=dt.day)
636 return None
638 def _first_of_quarter(self, day_of_week: WeekDay | None = None) -> Self:
639 """
640 Modify to the first occurrence of a given day of the week
641 in the current quarter. If no day_of_week is provided,
642 modify to the first day of the quarter. Use the supplied consts
643 to indicate the desired day_of_week, ex. pendulum.MONDAY.
644 """
645 return self.set(self.year, self.quarter * 3 - 2, 1).first_of(
646 "month", day_of_week
647 )
649 def _last_of_quarter(self, day_of_week: WeekDay | None = None) -> Self:
650 """
651 Modify to the last occurrence of a given day of the week
652 in the current quarter. If no day_of_week is provided,
653 modify to the last day of the quarter. Use the supplied consts
654 to indicate the desired day_of_week, ex. pendulum.MONDAY.
655 """
656 return self.set(self.year, self.quarter * 3, 1).last_of("month", day_of_week)
658 def _nth_of_quarter(self, nth: int, day_of_week: WeekDay) -> Self | None:
659 """
660 Modify to the given occurrence of a given day of the week
661 in the current quarter. If the calculated occurrence is outside,
662 the scope of the current quarter, then return False and no
663 modifications are made. Use the supplied consts
664 to indicate the desired day_of_week, ex. pendulum.MONDAY.
665 """
666 if nth == 1:
667 return self.first_of("quarter", day_of_week)
669 dt = self.replace(self.year, self.quarter * 3, 1)
670 last_month = dt.month
671 year = dt.year
672 dt = dt.first_of("quarter")
673 for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
674 dt = dt.next(day_of_week)
676 if last_month < dt.month or year != dt.year:
677 return None
679 return self.set(self.year, dt.month, dt.day)
681 def _first_of_year(self, day_of_week: WeekDay | None = None) -> Self:
682 """
683 Modify to the first occurrence of a given day of the week
684 in the current year. If no day_of_week is provided,
685 modify to the first day of the year. Use the supplied consts
686 to indicate the desired day_of_week, ex. pendulum.MONDAY.
687 """
688 return self.set(month=1).first_of("month", day_of_week)
690 def _last_of_year(self, day_of_week: WeekDay | None = None) -> Self:
691 """
692 Modify to the last occurrence of a given day of the week
693 in the current year. If no day_of_week is provided,
694 modify to the last day of the year. Use the supplied consts
695 to indicate the desired day_of_week, ex. pendulum.MONDAY.
696 """
697 return self.set(month=MONTHS_PER_YEAR).last_of("month", day_of_week)
699 def _nth_of_year(self, nth: int, day_of_week: WeekDay) -> Self | None:
700 """
701 Modify to the given occurrence of a given day of the week
702 in the current year. If the calculated occurrence is outside,
703 the scope of the current year, then return False and no
704 modifications are made. Use the supplied consts
705 to indicate the desired day_of_week, ex. pendulum.MONDAY.
706 """
707 if nth == 1:
708 return self.first_of("year", day_of_week)
710 dt = self.first_of("year")
711 year = dt.year
712 for _ in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
713 dt = dt.next(day_of_week)
715 if year != dt.year:
716 return None
718 return self.set(self.year, dt.month, dt.day)
720 def average(self, dt: date | None = None) -> Self:
721 """
722 Modify the current instance to the average
723 of a given instance (default now) and the current instance.
724 """
725 if dt is None:
726 dt = Date.today()
728 return self.add(days=int(self.diff(dt, False).in_days() / 2))
730 # Native methods override
732 @classmethod
733 def today(cls) -> Self:
734 dt = date.today()
736 return cls(dt.year, dt.month, dt.day)
738 @classmethod
739 def fromtimestamp(cls, t: float) -> Self:
740 dt = super().fromtimestamp(t)
742 return cls(dt.year, dt.month, dt.day)
744 @classmethod
745 def fromordinal(cls, n: int) -> Self:
746 dt = super().fromordinal(n)
748 return cls(dt.year, dt.month, dt.day)
750 def replace(
751 self,
752 year: SupportsIndex | None = None,
753 month: SupportsIndex | None = None,
754 day: SupportsIndex | None = None,
755 ) -> Self:
756 year = year if year is not None else self.year
757 month = month if month is not None else self.month
758 day = day if day is not None else self.day
760 return self.__class__(year, month, day)