aboutsummaryrefslogblamecommitdiffstats
path: root/datamaps/core/temporal.py
blob: 23d4b2374bd59e31c307d201165cb34f9695f8fe (plain) (tree)
1
2
3
               

                


















































                                                                                          



























                                                                                                
                                                             



                       
                                                             



                       
                                                             



                       
                                                             







                                                                    











                                                                                       
 
                     



                           


                   



                                

     









                                                                        


                                                                           


                                                                   















                                                          


















                                                                                     
                                                                      
                                       
import calendar
import datetime
import itertools


class Month:
    """An object representing a calendar Month."""

    _end_ints = {
        1: 31,
        2: 28,
        3: 31,
        4: 30,
        5: 31,
        6: 30,
        7: 31,
        8: 31,
        9: 30,
        10: 31,
        11: 30,
        12: 31,
    }

    def __init__(self, month: int, year: int):
        self._month = month
        self.year = year

    @property
    def start_date(self):
        return datetime.date(self.year, self._month, 1)

    @property
    def end_date(self):
        if self._month == 2 and calendar.isleap(self.year):
            return datetime.date(self.year, self._month, Month._end_ints[self._month] + 1)
        else:
            return datetime.date(self.year, self._month, Month._end_ints[self._month])

    @property
    def month(self):
        return [
            "January",
            "February",
            "March",
            "April",
            "May",
            "June",
            "July",
            "August",
            "September",
            "October",
            "November",
            "December",
        ][self._month - 1]


class FinancialYear:
    """An object representing a financial year.

    Used by ``bcompiler`` internally when creating :py:class:`bcompiler.api.Master` objects.
    Can be used to calculate start and ends dates and :py:class:`bcompiler.api.Quarter` objects.

    If parameter ``year`` must be in the range 150 - 2100.

    """

    def __init__(self, year):
        if isinstance(year, int) and (year in range(150, 2100)):
            self.year = year
        else:
            raise ValueError("A year must be an integer between 1950 and 2100")
        self._generate_quarters()
        self._q1 = self.quarters[0]
        self._q2 = self.quarters[1]
        self._q3 = self.quarters[2]
        self._q4 = self.quarters[3]

        self.start_date = self.q1.start_date
        self.end_date = self.q4.end_date

    @property
    def q1(self) -> datetime.date:
        """Quarter 1 as a :py:class:`datetime.date` object"""
        return self._q1

    @property
    def q2(self):
        """Quarter 2 as a :py:class:`datetime.date` object"""
        return self._q2

    @property
    def q3(self):
        """Quarter 3 as a :py:class:`datetime.date` object"""
        return self._q3

    @property
    def q4(self):
        """Quarter 4 as a :py:class:`datetime.date` object"""
        return self._q4

    def __str__(self):
        return f"FY{str(self.year)}/{str(self.year + 1)[2:]}"

    def _generate_quarters(self):
        self.quarters = [Quarter(x, self.year) for x in range(1, 5)]

    def __repr__(self):
        return f"FinancialYear({self.year})"


class Quarter:
    """An object representing a financial quarter. This is mainly required for building
    a :py:class:`core.master.Master` object.

    Args:
        quarter (int): e.g.1, 2, 3 or 4
        year (int): e.g. 2013
    """

    _start_months = {
        1: (4, "April"),
        2: (7, "July"),
        3: (10, "October"),
        4: (1, "January"),
    }

    _end_months = {
        1: (6, "June", 30),
        2: (9, "September", 30),
        3: (12, "December", 31),
        4: (3, "March", 31),
    }

    def __init__(self, quarter: int, year: int) -> None:

        if isinstance(quarter, int) and (quarter >= 1 and quarter <= 4):
            self.quarter = quarter
        else:
            raise ValueError("A quarter must be either 1, 2, 3 or 4")

        if isinstance(year, int) and (year in range(1950, 2100)):
            self.year = year
        else:
            raise ValueError(
                "Year must be between 1950 and 2100 - surely that will do?"
            )

        self.start_date = self._start_date(self.quarter, self.year)
        self.end_date = self._end_date(self.quarter, self.year)
        self.months = []
        self._populate_months()

    def _populate_months(self):
        months_ints = []
        start_int = Quarter._start_months[self.quarter][0]
        strt = itertools.count(start_int)
        for x in range(3):
            months_ints.append(next(strt))
        for m in months_ints:
            if self.quarter == 4:
                year = self.year + 1
            else:
                year = self.year
            self.months.append(Month(m, year))


    def __str__(self):
        return f"Q{self.quarter} {str(self.year)[2:]}/{str(self.year + 1)[2:]}"

    def _start_date(self, q, y):
        if q == 4:
            y = y + 1
        return datetime.date(y, Quarter._start_months[q][0], 1)

    def _end_date(self, q, y):
        if q == 4:
            y = y + 1
        return datetime.date(y, Quarter._end_months[q][0], Quarter._end_months[q][2])

    def __repr__(self):
        return f"Quarter({self.quarter}, {self.year})"

    @property
    def fy(self):
        """Return a :py:class:`core.temporal.FinancialYear` object."""
        return FinancialYear(self.year)