Source code for openfisca_core.periods.period_

from __future__ import annotations

from collections.abc import Sequence

import calendar
import datetime

import pendulum

from . import helpers, types as t
from .date_unit import DateUnit
from .instant_ import Instant


[docs] class Period(tuple[t.DateUnit, t.Instant, int]): """Toolbox to handle date intervals. A :class:`.Period` is a triple (``unit``, ``start``, ``size``). Attributes: unit (:obj:`str`): Either ``year``, ``month``, ``day`` or ``eternity``. start (:obj:`.Instant`): The "instant" the :obj:`.Period` starts at. size (:obj:`int`): The amount of ``unit``, starting at ``start``, at least ``1``. Args: (tuple(tuple(str, .Instant, int))): The ``unit``, ``start``, and ``size``, accordingly. Examples: >>> instant = Instant((2021, 10, 1)) >>> period = Period((DateUnit.YEAR, instant, 3)) >>> repr(Period) "<class 'openfisca_core.periods.period_.Period'>" >>> repr(period) "Period((<DateUnit.YEAR: 'year'>, Instant((2021, 10, 1)), 3))" >>> str(period) 'year:2021-10:3' >>> dict([period, instant]) Traceback (most recent call last): ValueError: dictionary update sequence element #0 has length 3... >>> list(period) [<DateUnit.YEAR: 'year'>, Instant((2021, 10, 1)), 3] >>> period[0] <DateUnit.YEAR: 'year'> >>> period[0] in period True >>> len(period) 3 >>> period == Period((DateUnit.YEAR, instant, 3)) True >>> period != Period((DateUnit.YEAR, instant, 3)) False >>> period > Period((DateUnit.YEAR, instant, 3)) False >>> period < Period((DateUnit.YEAR, instant, 3)) False >>> period >= Period((DateUnit.YEAR, instant, 3)) True >>> period <= Period((DateUnit.YEAR, instant, 3)) True >>> period.days 1096 >>> period.size_in_months 36 >>> period.size_in_days 1096 >>> period.stop Instant((2024, 9, 30)) >>> period.unit <DateUnit.YEAR: 'year'> >>> period.last_3_months Period((<DateUnit.MONTH: 'month'>, Instant((2021, 7, 1)), 3)) >>> period.last_month Period((<DateUnit.MONTH: 'month'>, Instant((2021, 9, 1)), 1)) >>> period.last_year Period((<DateUnit.YEAR: 'year'>, Instant((2020, 1, 1)), 1)) >>> period.n_2 Period((<DateUnit.YEAR: 'year'>, Instant((2019, 1, 1)), 1)) >>> period.this_year Period((<DateUnit.YEAR: 'year'>, Instant((2021, 1, 1)), 1)) >>> period.first_month Period((<DateUnit.MONTH: 'month'>, Instant((2021, 10, 1)), 1)) >>> period.first_day Period((<DateUnit.DAY: 'day'>, Instant((2021, 10, 1)), 1)) Since a period is a triple it can be used as a dictionary key. """ __slots__ = () def __repr__(self) -> str: return f"{self.__class__.__name__}({super().__repr__()})" def __str__(self) -> t.PeriodStr: unit, start_instant, size = self if unit == DateUnit.ETERNITY: return t.PeriodStr(unit.upper()) # ISO format date units. f_year, month, day = start_instant # ISO calendar date units. c_year, week, weekday = datetime.date(f_year, month, day).isocalendar() # 1 year long period if unit == DateUnit.MONTH and size == 12 or unit == DateUnit.YEAR and size == 1: if month == 1: # civil year starting from january return t.PeriodStr(str(f_year)) # rolling year return t.PeriodStr(f"{DateUnit.YEAR}:{f_year}-{month:02d}") # simple month if unit == DateUnit.MONTH and size == 1: return t.PeriodStr(f"{f_year}-{month:02d}") # several civil years if unit == DateUnit.YEAR and month == 1: return t.PeriodStr(f"{unit}:{f_year}:{size}") if unit == DateUnit.DAY: if size == 1: return t.PeriodStr(f"{f_year}-{month:02d}-{day:02d}") return t.PeriodStr(f"{unit}:{f_year}-{month:02d}-{day:02d}:{size}") # 1 week if unit == DateUnit.WEEK and size == 1: if week < 10: return t.PeriodStr(f"{c_year}-W0{week}") return t.PeriodStr(f"{c_year}-W{week}") # several weeks if unit == DateUnit.WEEK and size > 1: if week < 10: return t.PeriodStr(f"{unit}:{c_year}-W0{week}:{size}") return t.PeriodStr(f"{unit}:{c_year}-W{week}:{size}") # 1 weekday if unit == DateUnit.WEEKDAY and size == 1: if week < 10: return t.PeriodStr(f"{c_year}-W0{week}-{weekday}") return t.PeriodStr(f"{c_year}-W{week}-{weekday}") # several weekdays if unit == DateUnit.WEEKDAY and size > 1: if week < 10: return t.PeriodStr(f"{unit}:{c_year}-W0{week}-{weekday}:{size}") return t.PeriodStr(f"{unit}:{c_year}-W{week}-{weekday}:{size}") # complex period return t.PeriodStr(f"{unit}:{f_year}-{month:02d}:{size}") @property def unit(self) -> t.DateUnit: """The ``unit`` of the ``Period``. Example: >>> instant = Instant((2021, 10, 1)) >>> period = Period((DateUnit.YEAR, instant, 3)) >>> period.unit <DateUnit.YEAR: 'year'> """ return self[0] @property def start(self) -> t.Instant: """The ``Instant`` at which the ``Period`` starts. Example: >>> instant = Instant((2021, 10, 1)) >>> period = Period((DateUnit.YEAR, instant, 3)) >>> period.start Instant((2021, 10, 1)) """ return self[1] @property def size(self) -> int: """The ``size`` of the ``Period``. Example: >>> instant = Instant((2021, 10, 1)) >>> period = Period((DateUnit.YEAR, instant, 3)) >>> period.size 3 """ return self[2] @property def date(self) -> pendulum.Date: """The date representation of the ``Period`` start date. Examples: >>> instant = Instant((2021, 10, 1)) >>> period = Period((DateUnit.YEAR, instant, 1)) >>> period.date Date(2021, 10, 1) >>> period = Period((DateUnit.YEAR, instant, 3)) >>> period.date Traceback (most recent call last): ValueError: "date" is undefined for a period of size > 1: year:2021-10:3. """ if self.size != 1: msg = f'"date" is undefined for a period of size > 1: {self}.' raise ValueError(msg) return self.start.date @property def size_in_years(self) -> int: """The ``size`` of the ``Period`` in years. Examples: >>> instant = Instant((2021, 10, 1)) >>> period = Period((DateUnit.YEAR, instant, 3)) >>> period.size_in_years 3 >>> period = Period((DateUnit.MONTH, instant, 3)) >>> period.size_in_years Traceback (most recent call last): ValueError: Can't calculate number of years in a month. """ if self.unit == DateUnit.YEAR: return self.size msg = f"Can't calculate number of years in a {self.unit}." raise ValueError(msg) @property def size_in_months(self) -> int: """The ``size`` of the ``Period`` in months. Examples: >>> instant = Instant((2021, 10, 1)) >>> period = Period((DateUnit.YEAR, instant, 3)) >>> period.size_in_months 36 >>> period = Period((DateUnit.DAY, instant, 3)) >>> period.size_in_months Traceback (most recent call last): ValueError: Can't calculate number of months in a day. """ if self.unit == DateUnit.YEAR: return self.size * 12 if self.unit == DateUnit.MONTH: return self.size msg = f"Can't calculate number of months in a {self.unit}." raise ValueError(msg) @property def size_in_days(self) -> int: """The ``size`` of the ``Period`` in days. Examples: >>> instant = Instant((2019, 10, 1)) >>> period = Period((DateUnit.YEAR, instant, 3)) >>> period.size_in_days 1096 >>> period = Period((DateUnit.MONTH, instant, 3)) >>> period.size_in_days 92 """ if self.unit in (DateUnit.YEAR, DateUnit.MONTH): last = self.start.offset(self.size, self.unit) if last is None: raise NotImplementedError last_day = last.offset(-1, DateUnit.DAY) if last_day is None: raise NotImplementedError return (last_day.date - self.start.date).days + 1 if self.unit == DateUnit.WEEK: return self.size * 7 if self.unit in (DateUnit.DAY, DateUnit.WEEKDAY): return self.size msg = f"Can't calculate number of days in a {self.unit}." raise ValueError(msg) @property def size_in_weeks(self) -> int: """The ``size`` of the ``Period`` in weeks. Examples: >>> instant = Instant((2019, 10, 1)) >>> period = Period((DateUnit.YEAR, instant, 3)) >>> period.size_in_weeks 156 >>> period = Period((DateUnit.YEAR, instant, 5)) >>> period.size_in_weeks 261 """ if self.unit == DateUnit.YEAR: start = self.start.date cease = start.add(years=self.size) delta = start.diff(cease) return delta.in_weeks() if self.unit == DateUnit.MONTH: start = self.start.date cease = start.add(months=self.size) delta = start.diff(cease) return delta.in_weeks() if self.unit == DateUnit.WEEK: return self.size msg = f"Can't calculate number of weeks in a {self.unit}." raise ValueError(msg) @property def size_in_weekdays(self) -> int: """The ``size`` of the ``Period`` in weekdays. Examples: >>> instant = Instant((2019, 10, 1)) >>> period = Period((DateUnit.YEAR, instant, 3)) >>> period.size_in_weekdays 1092 >>> period = Period((DateUnit.WEEK, instant, 3)) >>> period.size_in_weekdays 21 """ if self.unit == DateUnit.YEAR: return self.size_in_weeks * 7 if DateUnit.MONTH in self.unit: last = self.start.offset(self.size, self.unit) if last is None: raise NotImplementedError last_day = last.offset(-1, DateUnit.DAY) if last_day is None: raise NotImplementedError return (last_day.date - self.start.date).days + 1 if self.unit == DateUnit.WEEK: return self.size * 7 if self.unit in (DateUnit.DAY, DateUnit.WEEKDAY): return self.size msg = f"Can't calculate number of weekdays in a {self.unit}." raise ValueError(msg) @property def days(self) -> int: """Same as ``size_in_days``.""" return (self.stop.date - self.start.date).days + 1 def intersection( self, start: t.Instant | None, stop: t.Instant | None ) -> t.Period | None: if start is None and stop is None: return self period_start = self[1] period_stop = self.stop if start is None: start = period_start if stop is None: stop = period_stop if stop < period_start or period_stop < start: return None intersection_start = max(period_start, start) intersection_stop = min(period_stop, stop) if intersection_start == period_start and intersection_stop == period_stop: return self if ( intersection_start.day == 1 and intersection_start.month == 1 and intersection_stop.day == 31 and intersection_stop.month == 12 ): return self.__class__( ( DateUnit.YEAR, intersection_start, intersection_stop.year - intersection_start.year + 1, ), ) if ( intersection_start.day == 1 and intersection_stop.day == calendar.monthrange(intersection_stop.year, intersection_stop.month)[1] ): return self.__class__( ( DateUnit.MONTH, intersection_start, ( (intersection_stop.year - intersection_start.year) * 12 + intersection_stop.month - intersection_start.month + 1 ), ), ) return self.__class__( ( DateUnit.DAY, intersection_start, (intersection_stop.date - intersection_start.date).days + 1, ), )
[docs] def get_subperiods(self, unit: t.DateUnit) -> Sequence[t.Period]: """Return the list of periods of unit ``unit`` contained in self. Examples: >>> period = Period((DateUnit.YEAR, Instant((2021, 1, 1)), 1)) >>> period.get_subperiods(DateUnit.MONTH) [Period((<DateUnit.MONTH: 'month'>, Instant((2021, 1, 1)), 1)),...] >>> period = Period((DateUnit.YEAR, Instant((2021, 1, 1)), 2)) >>> period.get_subperiods(DateUnit.YEAR) [Period((<DateUnit.YEAR: 'year'>, Instant((2021, 1, 1)), 1)), P...] """ if helpers.unit_weight(self.unit) < helpers.unit_weight(unit): msg = f"Cannot subdivide {self.unit} into {unit}" raise ValueError(msg) if unit == DateUnit.YEAR: return [self.this_year.offset(i, DateUnit.YEAR) for i in range(self.size)] if unit == DateUnit.MONTH: return [ self.first_month.offset(i, DateUnit.MONTH) for i in range(self.size_in_months) ] if unit == DateUnit.DAY: return [ self.first_day.offset(i, DateUnit.DAY) for i in range(self.size_in_days) ] if unit == DateUnit.WEEK: return [ self.first_week.offset(i, DateUnit.WEEK) for i in range(self.size_in_weeks) ] if unit == DateUnit.WEEKDAY: return [ self.first_weekday.offset(i, DateUnit.WEEKDAY) for i in range(self.size_in_weekdays) ] msg = f"Cannot subdivide {self.unit} into {unit}" raise ValueError(msg)
[docs] def offset(self, offset: str | int, unit: t.DateUnit | None = None) -> t.Period: """Increment (or decrement) the given period with offset units. Examples: >>> Period((DateUnit.DAY, Instant((2021, 1, 1)), 365)).offset(1) Period((<DateUnit.DAY: 'day'>, Instant((2021, 1, 2)), 365)) >>> Period((DateUnit.DAY, Instant((2021, 1, 1)), 365)).offset( ... 1, DateUnit.DAY ... ) Period((<DateUnit.DAY: 'day'>, Instant((2021, 1, 2)), 365)) >>> Period((DateUnit.DAY, Instant((2021, 1, 1)), 365)).offset( ... 1, DateUnit.MONTH ... ) Period((<DateUnit.DAY: 'day'>, Instant((2021, 2, 1)), 365)) >>> Period((DateUnit.DAY, Instant((2021, 1, 1)), 365)).offset( ... 1, DateUnit.YEAR ... ) Period((<DateUnit.DAY: 'day'>, Instant((2022, 1, 1)), 365)) >>> Period((DateUnit.MONTH, Instant((2021, 1, 1)), 12)).offset(1) Period((<DateUnit.MONTH: 'month'>, Instant((2021, 2, 1)), 12)) >>> Period((DateUnit.MONTH, Instant((2021, 1, 1)), 12)).offset( ... 1, DateUnit.DAY ... ) Period((<DateUnit.MONTH: 'month'>, Instant((2021, 1, 2)), 12)) >>> Period((DateUnit.MONTH, Instant((2021, 1, 1)), 12)).offset( ... 1, DateUnit.MONTH ... ) Period((<DateUnit.MONTH: 'month'>, Instant((2021, 2, 1)), 12)) >>> Period((DateUnit.MONTH, Instant((2021, 1, 1)), 12)).offset( ... 1, DateUnit.YEAR ... ) Period((<DateUnit.MONTH: 'month'>, Instant((2022, 1, 1)), 12)) >>> Period((DateUnit.YEAR, Instant((2021, 1, 1)), 1)).offset(1) Period((<DateUnit.YEAR: 'year'>, Instant((2022, 1, 1)), 1)) >>> Period((DateUnit.YEAR, Instant((2021, 1, 1)), 1)).offset( ... 1, DateUnit.DAY ... ) Period((<DateUnit.YEAR: 'year'>, Instant((2021, 1, 2)), 1)) >>> Period((DateUnit.YEAR, Instant((2021, 1, 1)), 1)).offset( ... 1, DateUnit.MONTH ... ) Period((<DateUnit.YEAR: 'year'>, Instant((2021, 2, 1)), 1)) >>> Period((DateUnit.YEAR, Instant((2021, 1, 1)), 1)).offset( ... 1, DateUnit.YEAR ... ) Period((<DateUnit.YEAR: 'year'>, Instant((2022, 1, 1)), 1)) >>> Period((DateUnit.DAY, Instant((2011, 2, 28)), 1)).offset(1) Period((<DateUnit.DAY: 'day'>, Instant((2011, 3, 1)), 1)) >>> Period((DateUnit.MONTH, Instant((2011, 2, 28)), 1)).offset(1) Period((<DateUnit.MONTH: 'month'>, Instant((2011, 3, 28)), 1)) >>> Period((DateUnit.YEAR, Instant((2011, 2, 28)), 1)).offset(1) Period((<DateUnit.YEAR: 'year'>, Instant((2012, 2, 28)), 1)) >>> Period((DateUnit.DAY, Instant((2011, 3, 1)), 1)).offset(-1) Period((<DateUnit.DAY: 'day'>, Instant((2011, 2, 28)), 1)) >>> Period((DateUnit.MONTH, Instant((2011, 3, 1)), 1)).offset(-1) Period((<DateUnit.MONTH: 'month'>, Instant((2011, 2, 1)), 1)) >>> Period((DateUnit.YEAR, Instant((2011, 3, 1)), 1)).offset(-1) Period((<DateUnit.YEAR: 'year'>, Instant((2010, 3, 1)), 1)) >>> Period((DateUnit.DAY, Instant((2014, 1, 30)), 1)).offset(3) Period((<DateUnit.DAY: 'day'>, Instant((2014, 2, 2)), 1)) >>> Period((DateUnit.MONTH, Instant((2014, 1, 30)), 1)).offset(3) Period((<DateUnit.MONTH: 'month'>, Instant((2014, 4, 30)), 1)) >>> Period((DateUnit.YEAR, Instant((2014, 1, 30)), 1)).offset(3) Period((<DateUnit.YEAR: 'year'>, Instant((2017, 1, 30)), 1)) >>> Period((DateUnit.DAY, Instant((2021, 1, 1)), 365)).offset(-3) Period((<DateUnit.DAY: 'day'>, Instant((2020, 12, 29)), 365)) >>> Period((DateUnit.MONTH, Instant((2021, 1, 1)), 12)).offset(-3) Period((<DateUnit.MONTH: 'month'>, Instant((2020, 10, 1)), 12)) >>> Period((DateUnit.YEAR, Instant((2014, 1, 1)), 1)).offset(-3) Period((<DateUnit.YEAR: 'year'>, Instant((2011, 1, 1)), 1)) >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 1)).offset( ... "first-of", DateUnit.MONTH ... ) Period((<DateUnit.DAY: 'day'>, Instant((2014, 2, 1)), 1)) >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 1)).offset( ... "first-of", DateUnit.YEAR ... ) Period((<DateUnit.DAY: 'day'>, Instant((2014, 1, 1)), 1)) >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 4)).offset( ... "first-of", DateUnit.MONTH ... ) Period((<DateUnit.DAY: 'day'>, Instant((2014, 2, 1)), 4)) >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 4)).offset( ... "first-of", DateUnit.YEAR ... ) Period((<DateUnit.DAY: 'day'>, Instant((2014, 1, 1)), 4)) >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 1)).offset("first-of") Period((<DateUnit.MONTH: 'month'>, Instant((2014, 2, 1)), 1)) >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 1)).offset( ... "first-of", DateUnit.MONTH ... ) Period((<DateUnit.MONTH: 'month'>, Instant((2014, 2, 1)), 1)) >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 1)).offset( ... "first-of", DateUnit.YEAR ... ) Period((<DateUnit.MONTH: 'month'>, Instant((2014, 1, 1)), 1)) >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 4)).offset("first-of") Period((<DateUnit.MONTH: 'month'>, Instant((2014, 2, 1)), 4)) >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 4)).offset( ... "first-of", DateUnit.MONTH ... ) Period((<DateUnit.MONTH: 'month'>, Instant((2014, 2, 1)), 4)) >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 4)).offset( ... "first-of", DateUnit.YEAR ... ) Period((<DateUnit.MONTH: 'month'>, Instant((2014, 1, 1)), 4)) >>> Period((DateUnit.YEAR, Instant((2014, 1, 30)), 1)).offset("first-of") Period((<DateUnit.YEAR: 'year'>, Instant((2014, 1, 1)), 1)) >>> Period((DateUnit.YEAR, Instant((2014, 1, 30)), 1)).offset( ... "first-of", DateUnit.MONTH ... ) Period((<DateUnit.YEAR: 'year'>, Instant((2014, 1, 1)), 1)) >>> Period((DateUnit.YEAR, Instant((2014, 1, 30)), 1)).offset( ... "first-of", DateUnit.YEAR ... ) Period((<DateUnit.YEAR: 'year'>, Instant((2014, 1, 1)), 1)) >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset("first-of") Period((<DateUnit.YEAR: 'year'>, Instant((2014, 1, 1)), 1)) >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset( ... "first-of", DateUnit.MONTH ... ) Period((<DateUnit.YEAR: 'year'>, Instant((2014, 2, 1)), 1)) >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset( ... "first-of", DateUnit.YEAR ... ) Period((<DateUnit.YEAR: 'year'>, Instant((2014, 1, 1)), 1)) >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 1)).offset( ... "last-of", DateUnit.MONTH ... ) Period((<DateUnit.DAY: 'day'>, Instant((2014, 2, 28)), 1)) >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 1)).offset( ... "last-of", DateUnit.YEAR ... ) Period((<DateUnit.DAY: 'day'>, Instant((2014, 12, 31)), 1)) >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 4)).offset( ... "last-of", DateUnit.MONTH ... ) Period((<DateUnit.DAY: 'day'>, Instant((2014, 2, 28)), 4)) >>> Period((DateUnit.DAY, Instant((2014, 2, 3)), 4)).offset( ... "last-of", DateUnit.YEAR ... ) Period((<DateUnit.DAY: 'day'>, Instant((2014, 12, 31)), 4)) >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 1)).offset("last-of") Period((<DateUnit.MONTH: 'month'>, Instant((2014, 2, 28)), 1)) >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 1)).offset( ... "last-of", DateUnit.MONTH ... ) Period((<DateUnit.MONTH: 'month'>, Instant((2014, 2, 28)), 1)) >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 1)).offset( ... "last-of", DateUnit.YEAR ... ) Period((<DateUnit.MONTH: 'month'>, Instant((2014, 12, 31)), 1)) >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 4)).offset("last-of") Period((<DateUnit.MONTH: 'month'>, Instant((2014, 2, 28)), 4)) >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 4)).offset( ... "last-of", DateUnit.MONTH ... ) Period((<DateUnit.MONTH: 'month'>, Instant((2014, 2, 28)), 4)) >>> Period((DateUnit.MONTH, Instant((2014, 2, 3)), 4)).offset( ... "last-of", DateUnit.YEAR ... ) Period((<DateUnit.MONTH: 'month'>, Instant((2014, 12, 31)), 4)) >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset("last-of") Period((<DateUnit.YEAR: 'year'>, Instant((2014, 12, 31)), 1)) >>> Period((DateUnit.YEAR, Instant((2014, 1, 1)), 1)).offset( ... "last-of", DateUnit.MONTH ... ) Period((<DateUnit.YEAR: 'year'>, Instant((2014, 1, 31)), 1)) >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset( ... "last-of", DateUnit.YEAR ... ) Period((<DateUnit.YEAR: 'year'>, Instant((2014, 12, 31)), 1)) >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset("last-of") Period((<DateUnit.YEAR: 'year'>, Instant((2014, 12, 31)), 1)) >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset( ... "last-of", DateUnit.MONTH ... ) Period((<DateUnit.YEAR: 'year'>, Instant((2014, 2, 28)), 1)) >>> Period((DateUnit.YEAR, Instant((2014, 2, 3)), 1)).offset( ... "last-of", DateUnit.YEAR ... ) Period((<DateUnit.YEAR: 'year'>, Instant((2014, 12, 31)), 1)) """ start: None | t.Instant = self[1].offset( offset, self[0] if unit is None else unit ) if start is None: raise NotImplementedError return self.__class__( ( self[0], start, self[2], ), )
[docs] def contains(self, other: t.Period) -> bool: """Returns ``True`` if the period contains ``other``. For instance, ``period(2015)`` contains ``period(2015-01)``. """ return self.start <= other.start and self.stop >= other.stop
@property def stop(self) -> t.Instant: """Return the last day of the period as an Instant instance. Examples: >>> Period((DateUnit.YEAR, Instant((2022, 1, 1)), 1)).stop Instant((2022, 12, 31)) >>> Period((DateUnit.MONTH, Instant((2022, 1, 1)), 12)).stop Instant((2022, 12, 31)) >>> Period((DateUnit.DAY, Instant((2022, 1, 1)), 365)).stop Instant((2022, 12, 31)) >>> Period((DateUnit.YEAR, Instant((2012, 2, 29)), 1)).stop Instant((2013, 2, 27)) >>> Period((DateUnit.MONTH, Instant((2012, 2, 29)), 1)).stop Instant((2012, 3, 28)) >>> Period((DateUnit.DAY, Instant((2012, 2, 29)), 1)).stop Instant((2012, 2, 29)) >>> Period((DateUnit.YEAR, Instant((2012, 2, 29)), 2)).stop Instant((2014, 2, 27)) >>> Period((DateUnit.MONTH, Instant((2012, 2, 29)), 2)).stop Instant((2012, 4, 28)) >>> Period((DateUnit.DAY, Instant((2012, 2, 29)), 2)).stop Instant((2012, 3, 1)) """ unit, start_instant, size = self if unit == DateUnit.ETERNITY: return Instant.eternity() if unit == DateUnit.YEAR: date = start_instant.date.add(years=size, days=-1) return Instant((date.year, date.month, date.day)) if unit == DateUnit.MONTH: date = start_instant.date.add(months=size, days=-1) return Instant((date.year, date.month, date.day)) if unit == DateUnit.WEEK: date = start_instant.date.add(weeks=size, days=-1) return Instant((date.year, date.month, date.day)) if unit in (DateUnit.DAY, DateUnit.WEEKDAY): date = start_instant.date.add(days=size - 1) return Instant((date.year, date.month, date.day)) raise ValueError @property def is_eternal(self) -> bool: return self == self.eternity() # Reference periods @property def last_week(self) -> t.Period: return self.first_week.offset(-1) @property def last_fortnight(self) -> t.Period: start: t.Instant = self.first_week.start return self.__class__((DateUnit.WEEK, start, 1)).offset(-2) @property def last_2_weeks(self) -> t.Period: start: t.Instant = self.first_week.start return self.__class__((DateUnit.WEEK, start, 2)).offset(-2) @property def last_26_weeks(self) -> t.Period: start: t.Instant = self.first_week.start return self.__class__((DateUnit.WEEK, start, 26)).offset(-26) @property def last_52_weeks(self) -> t.Period: start: t.Instant = self.first_week.start return self.__class__((DateUnit.WEEK, start, 52)).offset(-52) @property def last_month(self) -> t.Period: return self.first_month.offset(-1) @property def last_3_months(self) -> t.Period: start: t.Instant = self.first_month.start return self.__class__((DateUnit.MONTH, start, 3)).offset(-3) @property def last_year(self) -> t.Period: start: None | t.Instant = self.start.offset("first-of", DateUnit.YEAR) if start is None: raise NotImplementedError return self.__class__((DateUnit.YEAR, start, 1)).offset(-1) @property def n_2(self) -> t.Period: start: None | t.Instant = self.start.offset("first-of", DateUnit.YEAR) if start is None: raise NotImplementedError return self.__class__((DateUnit.YEAR, start, 1)).offset(-2) @property def this_year(self) -> t.Period: start: None | t.Instant = self.start.offset("first-of", DateUnit.YEAR) if start is None: raise NotImplementedError return self.__class__((DateUnit.YEAR, start, 1)) @property def first_month(self) -> t.Period: start: None | t.Instant = self.start.offset("first-of", DateUnit.MONTH) if start is None: raise NotImplementedError return self.__class__((DateUnit.MONTH, start, 1)) @property def first_day(self) -> t.Period: return self.__class__((DateUnit.DAY, self.start, 1)) @property def first_week(self) -> t.Period: start: None | t.Instant = self.start.offset("first-of", DateUnit.WEEK) if start is None: raise NotImplementedError return self.__class__((DateUnit.WEEK, start, 1)) @property def first_weekday(self) -> t.Period: return self.__class__((DateUnit.WEEKDAY, self.start, 1))
[docs] @classmethod def eternity(cls) -> t.Period: """Return an eternity period.""" return cls((DateUnit.ETERNITY, Instant.eternity(), -1))
__all__ = ["Period"]