Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.8/site-packages/pendulum/date.py: 35%
272 statements
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
« prev ^ index » next coverage.py v7.2.7, created at 2023-06-07 06:35 +0000
1from __future__ import absolute_import
2from __future__ import division
4import calendar
5import math
7from datetime import date
8from datetime import timedelta
10import pendulum
12from .constants import FRIDAY
13from .constants import MONDAY
14from .constants import MONTHS_PER_YEAR
15from .constants import SATURDAY
16from .constants import SUNDAY
17from .constants import THURSDAY
18from .constants import TUESDAY
19from .constants import WEDNESDAY
20from .constants import YEARS_PER_CENTURY
21from .constants import YEARS_PER_DECADE
22from .exceptions import PendulumException
23from .helpers import add_duration
24from .mixins.default import FormattableMixin
25from .period import Period
28class Date(FormattableMixin, date):
30 # Names of days of the week
31 _days = {
32 SUNDAY: "Sunday",
33 MONDAY: "Monday",
34 TUESDAY: "Tuesday",
35 WEDNESDAY: "Wednesday",
36 THURSDAY: "Thursday",
37 FRIDAY: "Friday",
38 SATURDAY: "Saturday",
39 }
41 _MODIFIERS_VALID_UNITS = ["day", "week", "month", "year", "decade", "century"]
43 # Getters/Setters
45 def set(self, year=None, month=None, day=None):
46 return self.replace(year=year, month=month, day=day)
48 @property
49 def day_of_week(self):
50 """
51 Returns the day of the week (0-6).
53 :rtype: int
54 """
55 return self.isoweekday() % 7
57 @property
58 def day_of_year(self):
59 """
60 Returns the day of the year (1-366).
62 :rtype: int
63 """
64 k = 1 if self.is_leap_year() else 2
66 return (275 * self.month) // 9 - k * ((self.month + 9) // 12) + self.day - 30
68 @property
69 def week_of_year(self):
70 return self.isocalendar()[1]
72 @property
73 def days_in_month(self):
74 return calendar.monthrange(self.year, self.month)[1]
76 @property
77 def week_of_month(self):
78 first_day_of_month = self.replace(day=1)
80 return self.week_of_year - first_day_of_month.week_of_year + 1
82 @property
83 def age(self):
84 return self.diff(abs=False).in_years()
86 @property
87 def quarter(self):
88 return int(math.ceil(self.month / 3))
90 # String Formatting
92 def to_date_string(self):
93 """
94 Format the instance as date.
96 :rtype: str
97 """
98 return self.strftime("%Y-%m-%d")
100 def to_formatted_date_string(self):
101 """
102 Format the instance as a readable date.
104 :rtype: str
105 """
106 return self.strftime("%b %d, %Y")
108 def __repr__(self):
109 return (
110 "{klass}("
111 "{year}, {month}, {day}"
112 ")".format(
113 klass=self.__class__.__name__,
114 year=self.year,
115 month=self.month,
116 day=self.day,
117 )
118 )
120 # COMPARISONS
122 def closest(self, dt1, dt2):
123 """
124 Get the closest date from the instance.
126 :type dt1: Date or date
127 :type dt2: Date or date
129 :rtype: Date
130 """
131 dt1 = self.__class__(dt1.year, dt1.month, dt1.day)
132 dt2 = self.__class__(dt2.year, dt2.month, dt2.day)
134 if self.diff(dt1).in_seconds() < self.diff(dt2).in_seconds():
135 return dt1
137 return dt2
139 def farthest(self, dt1, dt2):
140 """
141 Get the farthest date from the instance.
143 :type dt1: Date or date
144 :type dt2: Date or date
146 :rtype: Date
147 """
148 dt1 = self.__class__(dt1.year, dt1.month, dt1.day)
149 dt2 = self.__class__(dt2.year, dt2.month, dt2.day)
151 if self.diff(dt1).in_seconds() > self.diff(dt2).in_seconds():
152 return dt1
154 return dt2
156 def is_future(self):
157 """
158 Determines if the instance is in the future, ie. greater than now.
160 :rtype: bool
161 """
162 return self > self.today()
164 def is_past(self):
165 """
166 Determines if the instance is in the past, ie. less than now.
168 :rtype: bool
169 """
170 return self < self.today()
172 def is_leap_year(self):
173 """
174 Determines if the instance is a leap year.
176 :rtype: bool
177 """
178 return calendar.isleap(self.year)
180 def is_long_year(self):
181 """
182 Determines if the instance is a long year
184 See link `<https://en.wikipedia.org/wiki/ISO_8601#Week_dates>`_
186 :rtype: bool
187 """
188 return Date(self.year, 12, 28).isocalendar()[1] == 53
190 def is_same_day(self, dt):
191 """
192 Checks if the passed in date is the same day as the instance current day.
194 :type dt: Date or date
196 :rtype: bool
197 """
198 return self == dt
200 def is_anniversary(self, dt=None):
201 """
202 Check if its the anniversary.
204 Compares the date/month values of the two dates.
206 :rtype: bool
207 """
208 if dt is None:
209 dt = Date.today()
211 instance = self.__class__(dt.year, dt.month, dt.day)
213 return (self.month, self.day) == (instance.month, instance.day)
215 # the additional method for checking if today is the anniversary day
216 # the alias is provided to start using a new name and keep the backward compatibility
217 # the old name can be completely replaced with the new in one of the future versions
218 is_birthday = is_anniversary
220 # ADDITIONS AND SUBSTRACTIONS
222 def add(self, years=0, months=0, weeks=0, days=0):
223 """
224 Add duration to the instance.
226 :param years: The number of years
227 :type years: int
229 :param months: The number of months
230 :type months: int
232 :param weeks: The number of weeks
233 :type weeks: int
235 :param days: The number of days
236 :type days: int
238 :rtype: Date
239 """
240 dt = add_duration(
241 date(self.year, self.month, self.day),
242 years=years,
243 months=months,
244 weeks=weeks,
245 days=days,
246 )
248 return self.__class__(dt.year, dt.month, dt.day)
250 def subtract(self, years=0, months=0, weeks=0, days=0):
251 """
252 Remove duration from the instance.
254 :param years: The number of years
255 :type years: int
257 :param months: The number of months
258 :type months: int
260 :param weeks: The number of weeks
261 :type weeks: int
263 :param days: The number of days
264 :type days: int
266 :rtype: Date
267 """
268 return self.add(years=-years, months=-months, weeks=-weeks, days=-days)
270 def _add_timedelta(self, delta):
271 """
272 Add timedelta duration to the instance.
274 :param delta: The timedelta instance
275 :type delta: pendulum.Duration or datetime.timedelta
277 :rtype: Date
278 """
279 if isinstance(delta, pendulum.Duration):
280 return self.add(
281 years=delta.years,
282 months=delta.months,
283 weeks=delta.weeks,
284 days=delta.remaining_days,
285 )
287 return self.add(days=delta.days)
289 def _subtract_timedelta(self, delta):
290 """
291 Remove timedelta duration from the instance.
293 :param delta: The timedelta instance
294 :type delta: pendulum.Duration or datetime.timedelta
296 :rtype: Date
297 """
298 if isinstance(delta, pendulum.Duration):
299 return self.subtract(
300 years=delta.years,
301 months=delta.months,
302 weeks=delta.weeks,
303 days=delta.remaining_days,
304 )
306 return self.subtract(days=delta.days)
308 def __add__(self, other):
309 if not isinstance(other, timedelta):
310 return NotImplemented
312 return self._add_timedelta(other)
314 def __sub__(self, other):
315 if isinstance(other, timedelta):
316 return self._subtract_timedelta(other)
318 if not isinstance(other, date):
319 return NotImplemented
321 dt = self.__class__(other.year, other.month, other.day)
323 return dt.diff(self, False)
325 # DIFFERENCES
327 def diff(self, dt=None, abs=True):
328 """
329 Returns the difference between two Date objects as a Period.
331 :type dt: Date or None
333 :param abs: Whether to return an absolute interval or not
334 :type abs: bool
336 :rtype: Period
337 """
338 if dt is None:
339 dt = self.today()
341 return Period(self, Date(dt.year, dt.month, dt.day), absolute=abs)
343 def diff_for_humans(self, other=None, absolute=False, locale=None):
344 """
345 Get the difference in a human readable format in the current locale.
347 When comparing a value in the past to default now:
348 1 day ago
349 5 months ago
351 When comparing a value in the future to default now:
352 1 day from now
353 5 months from now
355 When comparing a value in the past to another value:
356 1 day before
357 5 months before
359 When comparing a value in the future to another value:
360 1 day after
361 5 months after
363 :type other: Date
365 :param absolute: removes time difference modifiers ago, after, etc
366 :type absolute: bool
368 :param locale: The locale to use for localization
369 :type locale: str
371 :rtype: str
372 """
373 is_now = other is None
375 if is_now:
376 other = self.today()
378 diff = self.diff(other)
380 return pendulum.format_diff(diff, is_now, absolute, locale)
382 # MODIFIERS
384 def start_of(self, unit):
385 """
386 Returns a copy of the instance with the time reset
387 with the following rules:
389 * day: time to 00:00:00
390 * week: date to first day of the week and time to 00:00:00
391 * month: date to first day of the month and time to 00:00:00
392 * year: date to first day of the year and time to 00:00:00
393 * decade: date to first day of the decade and time to 00:00:00
394 * century: date to first day of century and time to 00:00:00
396 :param unit: The unit to reset to
397 :type unit: str
399 :rtype: Date
400 """
401 if unit not in self._MODIFIERS_VALID_UNITS:
402 raise ValueError('Invalid unit "{}" for start_of()'.format(unit))
404 return getattr(self, "_start_of_{}".format(unit))()
406 def end_of(self, unit):
407 """
408 Returns a copy of the instance with the time reset
409 with the following rules:
411 * week: date to last day of the week
412 * month: date to last day of the month
413 * year: date to last day of the year
414 * decade: date to last day of the decade
415 * century: date to last day of century
417 :param unit: The unit to reset to
418 :type unit: str
420 :rtype: Date
421 """
422 if unit not in self._MODIFIERS_VALID_UNITS:
423 raise ValueError('Invalid unit "%s" for end_of()' % unit)
425 return getattr(self, "_end_of_%s" % unit)()
427 def _start_of_day(self):
428 """
429 Compatibility method.
431 :rtype: Date
432 """
433 return self
435 def _end_of_day(self):
436 """
437 Compatibility method
439 :rtype: Date
440 """
441 return self
443 def _start_of_month(self):
444 """
445 Reset the date to the first day of the month.
447 :rtype: Date
448 """
449 return self.set(self.year, self.month, 1)
451 def _end_of_month(self):
452 """
453 Reset the date to the last day of the month.
455 :rtype: Date
456 """
457 return self.set(self.year, self.month, self.days_in_month)
459 def _start_of_year(self):
460 """
461 Reset the date to the first day of the year.
463 :rtype: Date
464 """
465 return self.set(self.year, 1, 1)
467 def _end_of_year(self):
468 """
469 Reset the date to the last day of the year.
471 :rtype: Date
472 """
473 return self.set(self.year, 12, 31)
475 def _start_of_decade(self):
476 """
477 Reset the date to the first day of the decade.
479 :rtype: Date
480 """
481 year = self.year - self.year % YEARS_PER_DECADE
483 return self.set(year, 1, 1)
485 def _end_of_decade(self):
486 """
487 Reset the date to the last day of the decade.
489 :rtype: Date
490 """
491 year = self.year - self.year % YEARS_PER_DECADE + YEARS_PER_DECADE - 1
493 return self.set(year, 12, 31)
495 def _start_of_century(self):
496 """
497 Reset the date to the first day of the century.
499 :rtype: Date
500 """
501 year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + 1
503 return self.set(year, 1, 1)
505 def _end_of_century(self):
506 """
507 Reset the date to the last day of the century.
509 :rtype: Date
510 """
511 year = self.year - 1 - (self.year - 1) % YEARS_PER_CENTURY + YEARS_PER_CENTURY
513 return self.set(year, 12, 31)
515 def _start_of_week(self):
516 """
517 Reset the date to the first day of the week.
519 :rtype: Date
520 """
521 dt = self
523 if self.day_of_week != pendulum._WEEK_STARTS_AT:
524 dt = self.previous(pendulum._WEEK_STARTS_AT)
526 return dt.start_of("day")
528 def _end_of_week(self):
529 """
530 Reset the date to the last day of the week.
532 :rtype: Date
533 """
534 dt = self
536 if self.day_of_week != pendulum._WEEK_ENDS_AT:
537 dt = self.next(pendulum._WEEK_ENDS_AT)
539 return dt.end_of("day")
541 def next(self, day_of_week=None):
542 """
543 Modify to the next occurrence of a given day of the week.
544 If no day_of_week is provided, modify to the next occurrence
545 of the current day of the week. Use the supplied consts
546 to indicate the desired day_of_week, ex. pendulum.MONDAY.
548 :param day_of_week: The next day of week to reset to.
549 :type day_of_week: int or None
551 :rtype: Date
552 """
553 if day_of_week is None:
554 day_of_week = self.day_of_week
556 if day_of_week < SUNDAY or day_of_week > SATURDAY:
557 raise ValueError("Invalid day of week")
559 dt = self.add(days=1)
560 while dt.day_of_week != day_of_week:
561 dt = dt.add(days=1)
563 return dt
565 def previous(self, day_of_week=None):
566 """
567 Modify to the previous occurrence of a given day of the week.
568 If no day_of_week is provided, modify to the previous occurrence
569 of the current day of the week. Use the supplied consts
570 to indicate the desired day_of_week, ex. pendulum.MONDAY.
572 :param day_of_week: The previous day of week to reset to.
573 :type day_of_week: int or None
575 :rtype: Date
576 """
577 if day_of_week is None:
578 day_of_week = self.day_of_week
580 if day_of_week < SUNDAY or day_of_week > SATURDAY:
581 raise ValueError("Invalid day of week")
583 dt = self.subtract(days=1)
584 while dt.day_of_week != day_of_week:
585 dt = dt.subtract(days=1)
587 return dt
589 def first_of(self, unit, day_of_week=None):
590 """
591 Returns an instance set to the first occurrence
592 of a given day of the week in the current unit.
593 If no day_of_week is provided, modify to the first day of the unit.
594 Use the supplied consts to indicate the desired day_of_week, ex. pendulum.MONDAY.
596 Supported units are month, quarter and year.
598 :param unit: The unit to use
599 :type unit: str
601 :type day_of_week: int or None
603 :rtype: Date
604 """
605 if unit not in ["month", "quarter", "year"]:
606 raise ValueError('Invalid unit "{}" for first_of()'.format(unit))
608 return getattr(self, "_first_of_{}".format(unit))(day_of_week)
610 def last_of(self, unit, day_of_week=None):
611 """
612 Returns an instance set to the last occurrence
613 of a given day of the week in the current unit.
614 If no day_of_week is provided, modify to the last day of the unit.
615 Use the supplied consts to indicate the desired day_of_week, ex. pendulum.MONDAY.
617 Supported units are month, quarter and year.
619 :param unit: The unit to use
620 :type unit: str
622 :type day_of_week: int or None
624 :rtype: Date
625 """
626 if unit not in ["month", "quarter", "year"]:
627 raise ValueError('Invalid unit "{}" for first_of()'.format(unit))
629 return getattr(self, "_last_of_{}".format(unit))(day_of_week)
631 def nth_of(self, unit, nth, day_of_week):
632 """
633 Returns a new instance set to the given occurrence
634 of a given day of the week in the current unit.
635 If the calculated occurrence is outside the scope of the current unit,
636 then raise an error. Use the supplied consts
637 to indicate the desired day_of_week, ex. pendulum.MONDAY.
639 Supported units are month, quarter and year.
641 :param unit: The unit to use
642 :type unit: str
644 :type nth: int
646 :type day_of_week: int or None
648 :rtype: Date
649 """
650 if unit not in ["month", "quarter", "year"]:
651 raise ValueError('Invalid unit "{}" for first_of()'.format(unit))
653 dt = getattr(self, "_nth_of_{}".format(unit))(nth, day_of_week)
654 if dt is False:
655 raise PendulumException(
656 "Unable to find occurence {} of {} in {}".format(
657 nth, self._days[day_of_week], unit
658 )
659 )
661 return dt
663 def _first_of_month(self, day_of_week):
664 """
665 Modify to the first occurrence of a given day of the week
666 in the current month. If no day_of_week is provided,
667 modify to the first day of the month. Use the supplied consts
668 to indicate the desired day_of_week, ex. pendulum.MONDAY.
670 :type day_of_week: int
672 :rtype: Date
673 """
674 dt = self
676 if day_of_week is None:
677 return dt.set(day=1)
679 month = calendar.monthcalendar(dt.year, dt.month)
681 calendar_day = (day_of_week - 1) % 7
683 if month[0][calendar_day] > 0:
684 day_of_month = month[0][calendar_day]
685 else:
686 day_of_month = month[1][calendar_day]
688 return dt.set(day=day_of_month)
690 def _last_of_month(self, day_of_week=None):
691 """
692 Modify to the last occurrence of a given day of the week
693 in the current month. If no day_of_week is provided,
694 modify to the last day of the month. Use the supplied consts
695 to indicate the desired day_of_week, ex. pendulum.MONDAY.
697 :type day_of_week: int or None
699 :rtype: Date
700 """
701 dt = self
703 if day_of_week is None:
704 return dt.set(day=self.days_in_month)
706 month = calendar.monthcalendar(dt.year, dt.month)
708 calendar_day = (day_of_week - 1) % 7
710 if month[-1][calendar_day] > 0:
711 day_of_month = month[-1][calendar_day]
712 else:
713 day_of_month = month[-2][calendar_day]
715 return dt.set(day=day_of_month)
717 def _nth_of_month(self, nth, day_of_week):
718 """
719 Modify to the given occurrence of a given day of the week
720 in the current month. If the calculated occurrence is outside,
721 the scope of the current month, then return False and no
722 modifications are made. Use the supplied consts
723 to indicate the desired day_of_week, ex. pendulum.MONDAY.
725 :type nth: int
727 :type day_of_week: int or None
729 :rtype: Date
730 """
731 if nth == 1:
732 return self.first_of("month", day_of_week)
734 dt = self.first_of("month")
735 check = dt.format("YYYY-MM")
736 for i in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
737 dt = dt.next(day_of_week)
739 if dt.format("YYYY-MM") == check:
740 return self.set(day=dt.day)
742 return False
744 def _first_of_quarter(self, day_of_week=None):
745 """
746 Modify to the first occurrence of a given day of the week
747 in the current quarter. If no day_of_week is provided,
748 modify to the first day of the quarter. Use the supplied consts
749 to indicate the desired day_of_week, ex. pendulum.MONDAY.
751 :type day_of_week: int or None
753 :rtype: Date
754 """
755 return self.set(self.year, self.quarter * 3 - 2, 1).first_of(
756 "month", day_of_week
757 )
759 def _last_of_quarter(self, day_of_week=None):
760 """
761 Modify to the last occurrence of a given day of the week
762 in the current quarter. If no day_of_week is provided,
763 modify to the last day of the quarter. Use the supplied consts
764 to indicate the desired day_of_week, ex. pendulum.MONDAY.
766 :type day_of_week: int or None
768 :rtype: Date
769 """
770 return self.set(self.year, self.quarter * 3, 1).last_of("month", day_of_week)
772 def _nth_of_quarter(self, nth, day_of_week):
773 """
774 Modify to the given occurrence of a given day of the week
775 in the current quarter. If the calculated occurrence is outside,
776 the scope of the current quarter, then return False and no
777 modifications are made. Use the supplied consts
778 to indicate the desired day_of_week, ex. pendulum.MONDAY.
780 :type nth: int
782 :type day_of_week: int or None
784 :rtype: Date
785 """
786 if nth == 1:
787 return self.first_of("quarter", day_of_week)
789 dt = self.replace(self.year, self.quarter * 3, 1)
790 last_month = dt.month
791 year = dt.year
792 dt = dt.first_of("quarter")
793 for i in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
794 dt = dt.next(day_of_week)
796 if last_month < dt.month or year != dt.year:
797 return False
799 return self.set(self.year, dt.month, dt.day)
801 def _first_of_year(self, day_of_week=None):
802 """
803 Modify to the first occurrence of a given day of the week
804 in the current year. If no day_of_week is provided,
805 modify to the first day of the year. Use the supplied consts
806 to indicate the desired day_of_week, ex. pendulum.MONDAY.
808 :type day_of_week: int or None
810 :rtype: Date
811 """
812 return self.set(month=1).first_of("month", day_of_week)
814 def _last_of_year(self, day_of_week=None):
815 """
816 Modify to the last occurrence of a given day of the week
817 in the current year. If no day_of_week is provided,
818 modify to the last day of the year. Use the supplied consts
819 to indicate the desired day_of_week, ex. pendulum.MONDAY.
821 :type day_of_week: int or None
823 :rtype: Date
824 """
825 return self.set(month=MONTHS_PER_YEAR).last_of("month", day_of_week)
827 def _nth_of_year(self, nth, day_of_week):
828 """
829 Modify to the given occurrence of a given day of the week
830 in the current year. If the calculated occurrence is outside,
831 the scope of the current year, then return False and no
832 modifications are made. Use the supplied consts
833 to indicate the desired day_of_week, ex. pendulum.MONDAY.
835 :type nth: int
837 :type day_of_week: int or None
839 :rtype: Date
840 """
841 if nth == 1:
842 return self.first_of("year", day_of_week)
844 dt = self.first_of("year")
845 year = dt.year
846 for i in range(nth - (1 if dt.day_of_week == day_of_week else 0)):
847 dt = dt.next(day_of_week)
849 if year != dt.year:
850 return False
852 return self.set(self.year, dt.month, dt.day)
854 def average(self, dt=None):
855 """
856 Modify the current instance to the average
857 of a given instance (default now) and the current instance.
859 :type dt: Date or date
861 :rtype: Date
862 """
863 if dt is None:
864 dt = Date.today()
866 return self.add(days=int(self.diff(dt, False).in_days() / 2))
868 # Native methods override
870 @classmethod
871 def today(cls):
872 return pendulum.today().date()
874 @classmethod
875 def fromtimestamp(cls, t):
876 dt = super(Date, cls).fromtimestamp(t)
878 return cls(dt.year, dt.month, dt.day)
880 @classmethod
881 def fromordinal(cls, n):
882 dt = super(Date, cls).fromordinal(n)
884 return cls(dt.year, dt.month, dt.day)
886 def replace(self, year=None, month=None, day=None):
887 year = year if year is not None else self.year
888 month = month if month is not None else self.month
889 day = day if day is not None else self.day
891 return self.__class__(year, month, day)