diff --git a/src/undate/converters/base.py b/src/undate/converters/base.py index bcd90c2..5fefe49 100644 --- a/src/undate/converters/base.py +++ b/src/undate/converters/base.py @@ -138,13 +138,21 @@ class BaseCalendarConverter(BaseDateConverter): name: str = "Base Calendar Converter" def min_month(self) -> int: - """First month for this calendar.""" + """Smallest numeric month for this calendar.""" raise NotImplementedError - def max_month(self) -> int: - """Last month for this calendar.""" + def max_month(self, year: int) -> int: + """Maximum numeric month for this calendar""" raise NotImplementedError + def first_month(self) -> int: + """first month in this calendar; by default, returns :meth:`min_month`.""" + return self.min_month() + + def last_month(self, year: int) -> int: + """last month in this calendar; by default, returns :meth:`max_month`.""" + return self.max_month(year) + def max_day(self, year: int, month: int) -> int: """maximum numeric day for the specified year and month in this calendar""" raise NotImplementedError diff --git a/src/undate/converters/calendars/gregorian.py b/src/undate/converters/calendars/gregorian.py index af8ea25..59cde48 100644 --- a/src/undate/converters/calendars/gregorian.py +++ b/src/undate/converters/calendars/gregorian.py @@ -19,7 +19,7 @@ def min_month(self) -> int: """First month for the Gregorian calendar.""" return 1 - def max_month(self) -> int: + def max_month(self, year: int) -> int: """maximum numeric month for the specified year in the Gregorian calendar""" return 12 diff --git a/src/undate/converters/calendars/hebrew/converter.py b/src/undate/converters/calendars/hebrew/converter.py index 7d83dc7..b8b4620 100644 --- a/src/undate/converters/calendars/hebrew/converter.py +++ b/src/undate/converters/calendars/hebrew/converter.py @@ -25,14 +25,21 @@ def __init__(self): self.transformer = HebrewDateTransformer() def min_month(self) -> int: - """first numeric month for the specified year in this calendar""" - # hebrew calendar civil year starts in Tishri + """Smallest numeric month for this calendar.""" + return 1 + + def max_month(self, year: int) -> int: + """Maximum numeric month for this calendar. In Hebrew calendar, this is 12 or 13 + depending on whether it is a leap year.""" + return hebrew.year_months(year) + + def first_month(self) -> int: + """First month in this calendar. The Hebrew civil year starts in Tishri.""" return hebrew.TISHRI - def max_month(self) -> int: - """last numeric month for the specified year in this calendar""" - # hebrew calendar civil year starts in Tishri - # Elul is the month before Tishri + def last_month(self, year: int) -> int: + """Last month in this calendar. Hebrew civil year starts in Tishri, + Elul is the month before Tishri.""" return hebrew.ELUL def max_day(self, year: int, month: int) -> int: diff --git a/src/undate/converters/calendars/hijri/converter.py b/src/undate/converters/calendars/hijri/converter.py index 1cb7c82..b4b81b1 100644 --- a/src/undate/converters/calendars/hijri/converter.py +++ b/src/undate/converters/calendars/hijri/converter.py @@ -29,11 +29,11 @@ def max_day(self, year: int, month: int) -> int: return islamic.month_length(year, month) def min_month(self) -> int: - """First month for this calendar.""" + """smallest numeric month for this calendar.""" return 1 - def max_month(self) -> int: - """maximum numeric month for the specified year in this calendar""" + def max_month(self, year: int) -> int: + """maximum numeric month for this calendar""" return 12 def to_gregorian(self, year: int, month: int, day: int) -> tuple[int, int, int]: diff --git a/src/undate/undate.py b/src/undate/undate.py index 0c635c0..fab277c 100644 --- a/src/undate/undate.py +++ b/src/undate/undate.py @@ -124,24 +124,24 @@ def calculate_earliest_latest(self, year, month, day): if month == "XX": month = None - # get first and last month from the calendar, since it is not - # always 1 and 12 - # TODO need to differentiate between min/max and first/last! + # get first and last month from the calendar (not always 1 and 12) + # as well as min/max months + earliest_month = self.calendar_converter.first_month() + latest_month = self.calendar_converter.last_month(max_year) + min_month = self.calendar_converter.min_month() - max_month = self.calendar_converter.max_month() + max_month = self.calendar_converter.max_month(max_year) if month is not None: try: # treat as an integer if we can month = int(month) # update initial value self.initial_values["month"] = month - min_month = max_month = month + earliest_month = latest_month = month except ValueError: # if not, calculate min/max for missing digits - min_month, max_month = self._missing_digit_minmax( - str(month), - 1, - 12, # min_month, max_month + earliest_month, latest_month = self._missing_digit_minmax( + str(month), min_month, max_month ) # similar to month above — unknown day, but day-level granularity if day == "XX": @@ -159,7 +159,7 @@ def calculate_earliest_latest(self, year, month, day): rel_year = year if year and isinstance(year, int) else None # use month if it is an integer; otherwise use previusly determined # max month (which may not be 12 depending if partially unknown) - rel_month = month if month and isinstance(month, int) else max_month + rel_month = month if month and isinstance(month, int) else latest_month max_day = self.calendar_converter.max_day(rel_year, rel_month) @@ -175,10 +175,10 @@ def calculate_earliest_latest(self, year, month, day): # convert to Gregorian calendar so earliest/latest can always # be used for comparison self.earliest = Date( - *self.calendar_converter.to_gregorian(min_year, min_month, min_day) + *self.calendar_converter.to_gregorian(min_year, earliest_month, min_day) ) self.latest = Date( - *self.calendar_converter.to_gregorian(max_year, max_month, max_day) + *self.calendar_converter.to_gregorian(max_year, latest_month, max_day) ) def set_calendar(self, calendar: Union[str, Calendar]): diff --git a/tests/test_converters/test_calendars/test_hebrew/test_hebrew_converter.py b/tests/test_converters/test_calendars/test_hebrew/test_hebrew_converter.py index 319b551..c3c8b7c 100644 --- a/tests/test_converters/test_calendars/test_hebrew/test_hebrew_converter.py +++ b/tests/test_converters/test_calendars/test_hebrew/test_hebrew_converter.py @@ -84,11 +84,13 @@ def test_partially_known(self): unknown_month = HebrewUndate(1243, "XX") assert unknown_month.precision == DatePrecision.MONTH assert unknown_month.earliest == Date( - *converter.to_gregorian(1243, converter.min_month(), 1) + *converter.to_gregorian(1243, converter.first_month(), 1) ) - max_month = converter.max_month() + last_month = converter.last_month(year=1243) assert unknown_month.latest == Date( - *converter.to_gregorian(1243, max_month, converter.max_day(1243, max_month)) + *converter.to_gregorian( + 1243, last_month, converter.max_day(1243, last_month) + ) ) partially_unknown_month = HebrewUndate(1243, "1X") @@ -96,8 +98,12 @@ def test_partially_known(self): assert partially_unknown_month.earliest == Date( *converter.to_gregorian(1243, 10, 1) ) + # for unknown digit, assume largest possible value instead + # of last semantic monthin the year + last_month = converter.max_month(year=1243) + last_day = converter.max_day(1243, last_month) assert partially_unknown_month.latest == Date( - *converter.to_gregorian(1243, 12, 30) + *converter.to_gregorian(1243, last_month, last_day) ) # second month has 29 days