From 4483d769996a92e72fa83826bd44f5f6dd96b08b 2020-07-17 13:58:18 From: Brett Smith Date: 2020-07-17 13:58:18 Subject: [PATCH] data: Add Account.is_open_on_date() method. --- diff --git a/conservancy_beancount/data.py b/conservancy_beancount/data.py index fbbdd4f0c167724ca297eec9e79728c08626534f..829cec77ac9f898d0ad0702f9a083accc52dadd2 100644 --- a/conservancy_beancount/data.py +++ b/conservancy_beancount/data.py @@ -250,6 +250,22 @@ class Account(str): def is_credit_card(self) -> bool: return self.is_under('Liabilities:CreditCard') is not None + def is_open_on_date(self, date: datetime.date) -> Optional[bool]: + """Return true if this account is open on the given date. + + This method considers the dates on the account's open and close + directives. If there is no close directive, it just checks the date is + on or after the opening date. If neither exists, returns None. + """ + try: + meta = self.meta + except KeyError: + return None + close_date = meta.close_date + if close_date is None: + close_date = date + datetime.timedelta(days=1) + return meta.open_date <= date < close_date + def is_opening_equity(self) -> bool: return self.is_under('Equity:Funds', 'Equity:OpeningBalance') is not None diff --git a/tests/test_data_account.py b/tests/test_data_account.py index 53cf2c84667ab9a29c64092dc6c41c57bb8e8292..9546d321f2188f2f0ff95cc626e6b7dae6872acb 100644 --- a/tests/test_data_account.py +++ b/tests/test_data_account.py @@ -14,17 +14,17 @@ # You should have received a copy of the GNU Affero General Public License # along with this program. If not, see . +import datetime import pytest from . import testutil -from datetime import date as Date - from beancount.core.data import Open, Close, Booking from beancount.parser import options as bc_options from conservancy_beancount import data +Date = datetime.date clean_account_meta = pytest.fixture()(testutil.clean_account_meta) @pytest.fixture @@ -160,6 +160,50 @@ def test_is_credit_card(acct_name, expected): def test_is_opening_equity(acct_name, expected): assert data.Account(acct_name).is_opening_equity() == expected +@pytest.mark.parametrize('date', [ + testutil.PAST_DATE, + testutil.FY_START_DATE, + testutil.FY_MID_DATE, + testutil.FUTURE_DATE, +]) +def test_is_open_on_date_without_opening(date): + account = data.Account('Assets:Cash') + assert account.is_open_on_date(date) is None + +@pytest.mark.parametrize('days_diff', range(-2, 3)) +def test_is_open_on_date_without_closing(clean_account_meta, days_diff): + open_date = testutil.FY_START_DATE + acct_name = 'Assets:Checking' + data.Account.load_opening(Open({}, open_date, acct_name, None, None)) + account = data.Account(acct_name) + check_date = open_date + datetime.timedelta(days=days_diff) + assert account.is_open_on_date(check_date) == (days_diff >= 0) + +@pytest.mark.parametrize('close_diff,check_diff', [ + (30, -30), + (30, -1), + (30, 0), + (30, 1), + (30, 29), + (30, 30), + (30, 60), + (60, 30), + (60, 59), + (60, 60), + (60, 90), + (60, -60), +]) +def test_is_open_on_date_with_closing(clean_account_meta, close_diff, check_diff): + open_date = testutil.FY_START_DATE + acct_name = 'Assets:Savings' + data.Account.load_opening(Open({}, open_date, acct_name, None, None)) + close_date = open_date + datetime.timedelta(days=close_diff) + data.Account.load_closing(Close({}, close_date, acct_name)) + account = data.Account(acct_name) + check_date = open_date + datetime.timedelta(days=check_diff) + expected = (0 <= check_diff < close_diff) + assert account.is_open_on_date(check_date) == expected + @pytest.mark.parametrize('acct_name', [ 'Assets:Cash', 'Assets:Prepaid:Expenses',