# The Mia! Accounting 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 composer. """ import datetime as dt def get_spec(start: dt.date | None, end: dt.date | None) -> str: """Returns the period specification. :param start: The start of the period. :param end: The end of the period. :return: The period specification. """ if start is None and end is None: return "-" if end is None: return __get_since_spec(start) if start is None: return __get_until_spec(end) try: return __get_year_spec(start, end) except ValueError: pass try: return __get_month_spec(start, end) except ValueError: pass return __get_day_spec(start, end) def __get_since_spec(start: dt.date) -> str: """Returns the period specification without the end day. :param start: The start of the period. :return: The period specification without the end day """ if start.month == 1 and start.day == 1: return start.strftime("%Y-") if start.day == 1: return start.strftime("%Y-%m-") return start.strftime("%Y-%m-%d-") def __get_until_spec(end: dt.date) -> str: """Returns the period specification without the start day. :param end: The end of the period. :return: The period specification without the start day """ if end.month == 12 and end.day == 31: return end.strftime("-%Y") if (end + dt.timedelta(days=1)).day == 1: return end.strftime("-%Y-%m") return end.strftime("-%Y-%m-%d") def __get_year_spec(start: dt.date, end: dt.date) -> str: """Returns the period specification as a year range. :param start: The start of the period. :param end: The end of the period. :return: The period specification as a year range. :raise ValueError: The period is not a year range. """ if start.month != 1 or start.day != 1 \ or end.month != 12 or end.day != 31: raise ValueError start_spec: str = start.strftime("%Y") if start.year == end.year: return start_spec end_spec: str = end.strftime("%Y") return f"{start_spec}-{end_spec}" def __get_month_spec(start: dt.date, end: dt.date) -> str: """Returns the period specification as a month range. :param start: The start of the period. :param end: The end of the period. :return: The period specification as a month range. :raise ValueError: The period is not a month range. """ if start.day != 1 or (end + dt.timedelta(days=1)).day != 1: raise ValueError start_spec: str = start.strftime("%Y-%m") if start.year == end.year and start.month == end.month: return start_spec end_spec: str = end.strftime("%Y-%m") return f"{start_spec}-{end_spec}" def __get_day_spec(start: dt.date, end: dt.date) -> str: """Returns the period specification as a day range. :param start: The start of the period. :param end: The end of the period. :return: The period specification as a day range. :raise ValueError: The period is a month or year range. """ start_spec: str = start.strftime("%Y-%m-%d") if start == end: return start_spec end_spec: str = end.strftime("%Y-%m-%d") return f"{start_spec}-{end_spec}"