From 060a52f7a2feb76bc5a4606157f03e4d58a3e408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BE=9D=E7=91=AA=E8=B2=93?= Date: Thu, 9 Mar 2023 19:10:21 +0800 Subject: [PATCH] Moved the period specification parser from the "accounting.report.period.period" module to the "accounting.report.period.parser" module. --- src/accounting/report/period/parser.py | 87 ++++++++++++++++++++++++++ src/accounting/report/period/period.py | 70 +-------------------- 2 files changed, 89 insertions(+), 68 deletions(-) create mode 100644 src/accounting/report/period/parser.py diff --git a/src/accounting/report/period/parser.py b/src/accounting/report/period/parser.py new file mode 100644 index 0000000..d870228 --- /dev/null +++ b/src/accounting/report/period/parser.py @@ -0,0 +1,87 @@ +# The Mia! Accounting Flask Project. +# Author: imacat@mail.imacat.idv.tw (imacat), 2023/3/4 + +# Copyright (c) 2023 imacat. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""The period specification parser. + +""" +import calendar +import re +from datetime import date + +DATE_SPEC_RE: str = r"(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?" +"""The regular expression of a date specification.""" + + +def parse_spec(text: str) -> tuple[date | None, date | None]: + """Parses the period specification. + + :param text: The period specification. + :return: The start and end day of the period. The start and end day + may be None. + :raise ValueError: When the date is invalid. + """ + if text == "-": + return None, None + m = re.match(f"^{DATE_SPEC_RE}$", text) + if m is not None: + return __get_start(m[1], m[2], m[3]), \ + __get_end(m[1], m[2], m[3]) + m = re.match(f"^{DATE_SPEC_RE}-$", text) + if m is not None: + return __get_start(m[1], m[2], m[3]), None + m = re.match(f"-{DATE_SPEC_RE}$", text) + if m is not None: + return None, __get_end(m[1], m[2], m[3]) + m = re.match(f"^{DATE_SPEC_RE}-{DATE_SPEC_RE}$", text) + if m is not None: + return __get_start(m[1], m[2], m[3]), \ + __get_end(m[4], m[5], m[6]) + raise ValueError + + +def __get_start(year: str, month: str | None, day: str | None) -> date: + """Returns the start of the period from the date representation. + + :param year: The year. + :param month: The month, if any. + :param day: The day, if any. + :return: The start of the period. + :raise ValueError: When the date is invalid. + """ + if day is not None: + return date(int(year), int(month), int(day)) + if month is not None: + return date(int(year), int(month), 1) + return date(int(year), 1, 1) + + +def __get_end(year: str, month: str | None, day: str | None) -> date: + """Returns the end of the period from the date representation. + + :param year: The year. + :param month: The month, if any. + :param day: The day, if any. + :return: The end of the period. + :raise ValueError: When the date is invalid. + """ + if day is not None: + return date(int(year), int(month), int(day)) + if month is not None: + year_n: int = int(year) + month_n: int = int(month) + day_n: int = calendar.monthrange(year_n, month_n)[1] + return date(year_n, month_n, day_n) + return date(int(year), 12, 31) diff --git a/src/accounting/report/period/period.py b/src/accounting/report/period/period.py index 180188e..6ee25c7 100644 --- a/src/accounting/report/period/period.py +++ b/src/accounting/report/period/period.py @@ -21,12 +21,12 @@ This file is largely taken from the NanoParma ERP project, first written in """ import calendar -import re import typing as t from datetime import date, timedelta from accounting.locale import gettext from .description import PeriodDescription +from .parser import parse_spec from .specification import PeriodSpecification @@ -123,7 +123,7 @@ class Period: } if spec in named_periods: return named_periods[spec]() - start, end = _parse_period_spec(spec) + start, end = parse_spec(spec) if start is not None and end is not None and start > end: raise ValueError return cls(start, end) @@ -302,72 +302,6 @@ class YearPeriod(Period): super().__init__(start, end) -DATE_SPEC_RE: str = r"(\d{4})(?:-(\d{2})(?:-(\d{2}))?)?" -"""The regular expression of a date specification.""" - - -def _parse_period_spec(text: str) -> tuple[date | None, date | None]: - """Parses the period specification. - - :param text: The period specification. - :return: The start and end day of the period. The start and end day - may be None. - :raise ValueError: When the date is invalid. - """ - if text == "-": - return None, None - m = re.match(f"^{DATE_SPEC_RE}$", text) - if m is not None: - return __get_start(m[1], m[2], m[3]), \ - __get_end(m[1], m[2], m[3]) - m = re.match(f"^{DATE_SPEC_RE}-$", text) - if m is not None: - return __get_start(m[1], m[2], m[3]), None - m = re.match(f"-{DATE_SPEC_RE}$", text) - if m is not None: - return None, __get_end(m[1], m[2], m[3]) - m = re.match(f"^{DATE_SPEC_RE}-{DATE_SPEC_RE}$", text) - if m is not None: - return __get_start(m[1], m[2], m[3]), \ - __get_end(m[4], m[5], m[6]) - raise ValueError - - -def __get_start(year: str, month: str | None, day: str | None) -> date: - """Returns the start of the period from the date representation. - - :param year: The year. - :param month: The month, if any. - :param day: The day, if any. - :return: The start of the period. - :raise ValueError: When the date is invalid. - """ - if day is not None: - return date(int(year), int(month), int(day)) - if month is not None: - return date(int(year), int(month), 1) - return date(int(year), 1, 1) - - -def __get_end(year: str, month: str | None, day: str | None) -> date: - """Returns the end of the period from the date representation. - - :param year: The year. - :param month: The month, if any. - :param day: The day, if any. - :return: The end of the period. - :raise ValueError: When the date is invalid. - """ - if day is not None: - return date(int(year), int(month), int(day)) - if month is not None: - year_n: int = int(year) - month_n: int = int(month) - day_n: int = calendar.monthrange(year_n, month_n)[1] - return date(year_n, month_n, day_n) - return date(int(year), 12, 31) - - def _month_end(day: date) -> date: """Returns the end day of month for a date.