Changeset - 944c19da8d69
[Not reviewed]
0 2 0
Brett Smith - 4 years ago 2020-06-10 19:59:56
brettcsmith@brettcsmith.org
books: Add date-fetching methods to FiscalYear.
2 files changed with 63 insertions and 0 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/books.py
Show inline comments
...
 
@@ -21,64 +21,77 @@ from pathlib import Path
 
from beancount import loader as bc_loader
 

	
 
from typing import (
 
    Any,
 
    Iterable,
 
    Iterator,
 
    Mapping,
 
    NamedTuple,
 
    Optional,
 
    Union,
 
)
 
from .beancount_types import (
 
    Error,
 
    Errors,
 
    LoadResult,
 
)
 

	
 
PathLike = Union[str, Path]
 
Year = Union[int, datetime.date]
 

	
 
class FiscalYear(NamedTuple):
 
    month: int = 3
 
    day: int = 1
 

	
 
    def for_date(self, date: Optional[datetime.date]=None) -> int:
 
        if date is None:
 
            date = datetime.date.today()
 
        if (date.month, date.day) < self:
 
            return date.year - 1
 
        else:
 
            return date.year
 

	
 
    def first_date(self, year: Year) -> datetime.date:
 
        if isinstance(year, datetime.date):
 
            year = self.for_date(year)
 
        return datetime.date(year, self.month, self.day)
 

	
 
    def last_date(self, year: Year) -> datetime.date:
 
        return self.next_fy_date(year) - datetime.timedelta(days=1)
 

	
 
    def next_fy_date(self, year: Year) -> datetime.date:
 
        if isinstance(year, datetime.date):
 
            year = self.for_date(year)
 
        return datetime.date(year + 1, self.month, self.day)
 

	
 
    def range(self, from_fy: Year, to_fy: Optional[Year]=None) -> Iterable[int]:
 
        """Return a range of fiscal years
 

	
 
        Both arguments can be either a year (represented as an integer) or a
 
        date. Dates will be converted into a year by calling for_date() on
 
        them.
 

	
 
        If the first argument is negative or below 1000, it will be treated as
 
        an offset. You'll get a range of fiscal years between the second
 
        argument offset by this amount.
 

	
 
        If the second argument is omitted, it defaults to the current fiscal
 
        year.
 

	
 
        Note that unlike normal Python ranges, these ranges include the final
 
        fiscal year.
 

	
 
        Examples:
 

	
 
          range(2015)  # Iterate all fiscal years from 2015 to today, inclusive
 

	
 
          range(-1)  # Iterate the previous fiscal year and current fiscal year
 
        """
 
        if not isinstance(from_fy, int):
 
            from_fy = self.for_date(from_fy)
 
        if to_fy is None:
 
            to_fy = self.for_date()
 
        elif not isinstance(to_fy, int):
 
            to_fy = self.for_date(to_fy - datetime.timedelta(days=1))
 
        if from_fy < 1:
 
            from_fy += to_fy
 
        elif from_fy < 1000:
tests/test_books_fiscal_year.py
Show inline comments
...
 
@@ -105,32 +105,82 @@ def test_range_offset_and_date(conservancy_fy, year_offset, month_offset):
 
    end_date = datetime.date(2020, conservancy_fy.month + month_offset, 10)
 
    base_year = end_date.year
 
    if month_offset < 0:
 
        base_year -= 1
 
    if year_offset < 0:
 
        expected = range(base_year + year_offset, base_year + 1)
 
    else:
 
        expected = range(base_year, base_year + year_offset + 1)
 
    actual = list(conservancy_fy.range(year_offset, end_date))
 
    assert actual == list(expected)
 

	
 
@pytest.mark.parametrize('year_offset,year', itertools.product(
 
    range(-3, 3),
 
    [2010, 2015],
 
))
 
def test_range_offset_and_year(conservancy_fy, year_offset, year):
 
    if year_offset < 0:
 
        expected = range(year + year_offset, year + 1)
 
    else:
 
        expected = range(year, year + year_offset + 1)
 
    actual = list(conservancy_fy.range(year_offset, year))
 
    assert actual == list(expected)
 

	
 
@pytest.mark.parametrize('year_offset', range(-3, 3))
 
def test_range_offset_only(cy_fy, year_offset):
 
    year = datetime.date.today().year
 
    if year_offset < 0:
 
        expected = range(year + year_offset, year + 1)
 
    else:
 
        expected = range(year, year + year_offset + 1)
 
    actual = list(cy_fy.range(year_offset))
 
    assert actual == list(expected)
 

	
 
@pytest.mark.parametrize('year', range(2016, 2022))
 
def test_first_date_year_arg(conservancy_fy, year):
 
    assert conservancy_fy.first_date(year) == datetime.date(year, 3, 1)
 

	
 
@pytest.mark.parametrize('date', [
 
    datetime.date(2019, 1, 1),
 
    datetime.date(2019, 10, 10),
 
    datetime.date(2020, 2, 2),
 
    datetime.date(2020, 12, 12),
 
])
 
def test_first_date_date_arg(conservancy_fy, date):
 
    year = date.year
 
    if date.month < 3:
 
        year -= 1
 
    assert conservancy_fy.first_date(date) == datetime.date(year, 3, 1)
 

	
 
@pytest.mark.parametrize('year', range(2016, 2022))
 
def test_last_date_year_arg(conservancy_fy, year):
 
    day = 28 if year % 4 else 29
 
    assert conservancy_fy.last_date(year - 1) == datetime.date(year, 2, day)
 

	
 
@pytest.mark.parametrize('date', [
 
    datetime.date(2019, 1, 1),
 
    datetime.date(2019, 10, 10),
 
    datetime.date(2020, 2, 2),
 
    datetime.date(2020, 12, 12),
 
])
 
def test_last_date_date_arg(conservancy_fy, date):
 
    year = date.year
 
    if date.month >= 3:
 
        year += 1
 
    day = 28 if year % 4 else 29
 
    assert conservancy_fy.last_date(date) == datetime.date(year, 2, day)
 

	
 
@pytest.mark.parametrize('year', range(2016, 2022))
 
def test_next_fy_date_year_arg(conservancy_fy, year):
 
    assert conservancy_fy.next_fy_date(year) == datetime.date(year + 1, 3, 1)
 

	
 
@pytest.mark.parametrize('date', [
 
    datetime.date(2019, 1, 1),
 
    datetime.date(2019, 10, 10),
 
    datetime.date(2020, 2, 29),
 
    datetime.date(2020, 12, 12),
 
])
 
def test_next_fy_date_date_arg(conservancy_fy, date):
 
    year = date.year
 
    if date.month >= 3:
 
        year += 1
 
    assert conservancy_fy.next_fy_date(date) == datetime.date(year, 3, 1)
0 comments (0 inline, 0 general)