Functional Weave
Code in Python

dates.quarter@1.0.0

impl/python.py

2,303 bytes · the Python implementation · view raw

Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.

from typing import Tuple

from .dates_add_days import CivilDate, days_in_month, format_iso_date, parse_iso_date  ← from dates.add-days ^1.0.0 · built alongside by fune
from .dates_quarter_types import Quarter


def _month_from_index(index: int) -> Tuple[int, int]:
    """A month counted from January of year 0, back to its year and month."""
    year = index // 12
    return year, index - year * 12 + 1


def _first_day(index: int) -> str:
    year, month = _month_from_index(index)
    return format_iso_date(CivilDate(year=year, month=month, day=1))


def _last_day(index: int) -> str:
    year, month = _month_from_index(index)
    return format_iso_date(CivilDate(year=year, month=month, day=days_in_month(year, month)))


def quarter(iso: str, fiscal_year_start_month: int) -> Quarter:
    """The calendar and fiscal quarter of a date.

    The fiscal year is given as dates, not a number, because "FY2027" means the
    year ending in 2027 to one organisation and the year starting in it to
    another.
    """
    if isinstance(fiscal_year_start_month, bool) or not isinstance(fiscal_year_start_month, int):
        raise TypeError("fiscalYearStartMonth must be an integer, received %r" % (fiscal_year_start_month,))
    if fiscal_year_start_month < 1 or fiscal_year_start_month > 12:
        raise ValueError("fiscalYearStartMonth must be 1-12, received %s" % (fiscal_year_start_month,))
    date = parse_iso_date(iso)
    months_into_year = (date.month - fiscal_year_start_month) % 12
    fiscal_quarter = months_into_year // 3 + 1
    # Work in months counted from year 0 so year boundaries need no special case.
    year_start = date.year * 12 + (date.month - 1) - months_into_year
    quarter_start = year_start + 3 * (fiscal_quarter - 1)
    if _month_from_index(year_start)[0] < 1 or _month_from_index(year_start + 11)[0] > 9999:
        raise ValueError(
            'the fiscal year containing "%s" is outside the supported range 0001-01-01 to 9999-12-31' % (iso,)
        )
    return Quarter(
        calendar_year=date.year,
        calendar_quarter=(date.month - 1) // 3 + 1,
        fiscal_quarter=fiscal_quarter,
        fiscal_year_start=_first_day(year_start),
        fiscal_year_end=_last_day(year_start + 11),
        fiscal_quarter_start=_first_day(quarter_start),
        fiscal_quarter_end=_last_day(quarter_start + 2),
    )