from __future__ import annotations
import pendulum
from . import config, types as t
from .date_unit import DateUnit
[docs]
class Instant(tuple[int, int, int]):
"""An instant in time (year, month, day).
An :class:`.Instant` represents the most atomic and indivisible
legislation's date unit.
Current implementation considers this unit to be a day, so
:obj:`instants <.Instant>` can be thought of as "day dates".
Examples:
>>> instant = Instant((2021, 9, 13))
>>> repr(Instant)
"<class 'openfisca_core.periods.instant_.Instant'>"
>>> repr(instant)
'Instant((2021, 9, 13))'
>>> str(instant)
'2021-09-13'
>>> dict([(instant, (2021, 9, 13))])
{Instant((2021, 9, 13)): (2021, 9, 13)}
>>> list(instant)
[2021, 9, 13]
>>> instant[0]
2021
>>> instant[0] in instant
True
>>> len(instant)
3
>>> instant == (2021, 9, 13)
True
>>> instant != (2021, 9, 13)
False
>>> instant > (2020, 9, 13)
True
>>> instant < (2020, 9, 13)
False
>>> instant >= (2020, 9, 13)
True
>>> instant <= (2020, 9, 13)
False
>>> instant.year
2021
>>> instant.month
9
>>> instant.day
13
>>> instant.date
Date(2021, 9, 13)
>>> year, month, day = instant
"""
__slots__ = ()
def __repr__(self) -> str:
return f"{self.__class__.__name__}({super().__repr__()})"
def __str__(self) -> t.InstantStr:
instant_str = config.str_by_instant_cache.get(self)
if instant_str is None:
instant_str = t.InstantStr(self.date.isoformat())
config.str_by_instant_cache[self] = instant_str
return instant_str
def __lt__(self, other: object) -> bool:
if isinstance(other, Instant):
return super().__lt__(other)
return NotImplemented
def __le__(self, other: object) -> bool:
if isinstance(other, Instant):
return super().__le__(other)
return NotImplemented
@property
def date(self) -> pendulum.Date:
instant_date = config.date_by_instant_cache.get(self)
if instant_date is None:
instant_date = pendulum.date(*self)
config.date_by_instant_cache[self] = instant_date
return instant_date
@property
def day(self) -> int:
return self[2]
@property
def month(self) -> int:
return self[1]
@property
def year(self) -> int:
return self[0]
@property
def is_eternal(self) -> bool:
return self == self.eternity()
def offset(self, offset: str | int, unit: t.DateUnit) -> t.Instant | None:
"""Increments/decrements the given instant with offset units.
Args:
offset: How much of ``unit`` to offset.
unit: What to offset
Returns:
:obj:`.Instant`: A new :obj:`.Instant` in time.
Raises:
:exc:`AssertionError`: When ``unit`` is not a date unit.
:exc:`AssertionError`: When ``offset`` is not either ``first-of``,
``last-of``, or any :obj:`int`.
Examples:
>>> Instant((2020, 12, 31)).offset("first-of", DateUnit.MONTH)
Instant((2020, 12, 1))
>>> Instant((2020, 1, 1)).offset("last-of", DateUnit.YEAR)
Instant((2020, 12, 31))
>>> Instant((2020, 1, 1)).offset(1, DateUnit.YEAR)
Instant((2021, 1, 1))
>>> Instant((2020, 1, 1)).offset(-3, DateUnit.DAY)
Instant((2019, 12, 29))
"""
year, month, _ = self
assert unit in (
DateUnit.isoformat + DateUnit.isocalendar
), f"Invalid unit: {unit} of type {type(unit)}"
if offset == "first-of":
if unit == DateUnit.YEAR:
return self.__class__((year, 1, 1))
if unit == DateUnit.MONTH:
return self.__class__((year, month, 1))
if unit == DateUnit.WEEK:
date = self.date
date = date.start_of("week")
return self.__class__((date.year, date.month, date.day))
return None
if offset == "last-of":
if unit == DateUnit.YEAR:
return self.__class__((year, 12, 31))
if unit == DateUnit.MONTH:
date = self.date
date = date.end_of("month")
return self.__class__((date.year, date.month, date.day))
if unit == DateUnit.WEEK:
date = self.date
date = date.end_of("week")
return self.__class__((date.year, date.month, date.day))
return None
assert isinstance(
offset,
int,
), f"Invalid offset: {offset} of type {type(offset)}"
if unit == DateUnit.YEAR:
date = self.date
date = date.add(years=offset)
return self.__class__((date.year, date.month, date.day))
if unit == DateUnit.MONTH:
date = self.date
date = date.add(months=offset)
return self.__class__((date.year, date.month, date.day))
if unit == DateUnit.WEEK:
date = self.date
date = date.add(weeks=offset)
return self.__class__((date.year, date.month, date.day))
if unit in (DateUnit.DAY, DateUnit.WEEKDAY):
date = self.date
date = date.add(days=offset)
return self.__class__((date.year, date.month, date.day))
return None
@classmethod
def eternity(cls) -> t.Instant:
"""Return an eternity instant."""
return cls((-1, -1, -1))
__all__ = ["Instant"]