From 0fefcec38abe3e850acc18557868d7a34ac8e5bb Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Thu, 20 Feb 2025 16:46:57 -0500 Subject: [PATCH 1/3] Move interval object and tests into separate files --- docs/undate/core.rst | 7 +- src/undate/__init__.py | 5 +- .../converters/calendars/hebrew/converter.py | 2 +- .../calendars/hebrew/transformer.py | 2 +- .../converters/calendars/hijri/converter.py | 2 +- .../converters/calendars/hijri/transformer.py | 2 +- src/undate/converters/edtf/converter.py | 3 +- src/undate/converters/edtf/transformer.py | 2 +- src/undate/converters/iso8601.py | 2 +- src/undate/interval.py | 125 +++++++++++++++ src/undate/undate.py | 107 +------------ .../edtf/test_edtf_transformer.py | 3 +- tests/test_converters/test_edtf.py | 2 +- tests/test_converters/test_iso8601.py | 2 +- tests/test_interval.py | 145 ++++++++++++++++++ tests/test_undate.py | 119 +------------- 16 files changed, 299 insertions(+), 231 deletions(-) create mode 100644 src/undate/interval.py create mode 100644 tests/test_interval.py diff --git a/docs/undate/core.rst b/docs/undate/core.rst index e7b6b4b..4cc3e6b 100644 --- a/docs/undate/core.rst +++ b/docs/undate/core.rst @@ -1,13 +1,16 @@ Undate objects ============== -undates and undate intervals +dates, intervals, and calendar ------------------------------ .. autoclass:: undate.undate.Undate :members: -.. autoclass:: undate.undate.UndateInterval +.. autoclass:: undate.undate.Calendar + :members: + +.. autoclass:: undate.interval.UndateInterval :members: date, timedelta, and date precision diff --git a/src/undate/__init__.py b/src/undate/__init__.py index 290f83f..00cedc3 100644 --- a/src/undate/__init__.py +++ b/src/undate/__init__.py @@ -1,6 +1,7 @@ __version__ = "0.4.0.dev0" from undate.date import DatePrecision -from undate.undate import Undate, UndateInterval +from undate.undate import Undate, Calendar +from undate.interval import UndateInterval -__all__ = ["Undate", "UndateInterval", "DatePrecision", "__version__"] +__all__ = ["Undate", "UndateInterval", "Calendar", "DatePrecision", "__version__"] diff --git a/src/undate/converters/calendars/hebrew/converter.py b/src/undate/converters/calendars/hebrew/converter.py index b8b4620..d540021 100644 --- a/src/undate/converters/calendars/hebrew/converter.py +++ b/src/undate/converters/calendars/hebrew/converter.py @@ -3,10 +3,10 @@ from convertdate import hebrew # type: ignore from lark.exceptions import UnexpectedCharacters +from undate import Undate, UndateInterval from undate.converters.base import BaseCalendarConverter from undate.converters.calendars.hebrew.parser import hebrew_parser from undate.converters.calendars.hebrew.transformer import HebrewDateTransformer -from undate.undate import Undate, UndateInterval class HebrewDateConverter(BaseCalendarConverter): diff --git a/src/undate/converters/calendars/hebrew/transformer.py b/src/undate/converters/calendars/hebrew/transformer.py index a6d2888..48e8b20 100644 --- a/src/undate/converters/calendars/hebrew/transformer.py +++ b/src/undate/converters/calendars/hebrew/transformer.py @@ -1,6 +1,6 @@ from lark import Transformer, Tree -from undate.undate import Undate, Calendar +from undate import Undate, Calendar class HebrewUndate(Undate): diff --git a/src/undate/converters/calendars/hijri/converter.py b/src/undate/converters/calendars/hijri/converter.py index b4b81b1..12a04d8 100644 --- a/src/undate/converters/calendars/hijri/converter.py +++ b/src/undate/converters/calendars/hijri/converter.py @@ -3,10 +3,10 @@ from convertdate import islamic # type: ignore from lark.exceptions import UnexpectedCharacters +from undate import Undate, UndateInterval from undate.converters.base import BaseCalendarConverter from undate.converters.calendars.hijri.parser import hijri_parser from undate.converters.calendars.hijri.transformer import HijriDateTransformer -from undate.undate import Undate, UndateInterval class HijriDateConverter(BaseCalendarConverter): diff --git a/src/undate/converters/calendars/hijri/transformer.py b/src/undate/converters/calendars/hijri/transformer.py index b575df9..8b78b2c 100644 --- a/src/undate/converters/calendars/hijri/transformer.py +++ b/src/undate/converters/calendars/hijri/transformer.py @@ -1,6 +1,6 @@ from lark import Transformer, Tree -from undate.undate import Undate, Calendar +from undate import Undate, Calendar class HijriUndate(Undate): diff --git a/src/undate/converters/edtf/converter.py b/src/undate/converters/edtf/converter.py index 95a1364..d0b742f 100644 --- a/src/undate/converters/edtf/converter.py +++ b/src/undate/converters/edtf/converter.py @@ -2,11 +2,12 @@ from lark.exceptions import UnexpectedCharacters +from undate import Undate, UndateInterval from undate.converters.base import BaseDateConverter from undate.converters.edtf.parser import edtf_parser from undate.converters.edtf.transformer import EDTFTransformer from undate.date import DatePrecision -from undate.undate import Undate, UndateInterval + #: character for unspecified digits EDTF_UNSPECIFIED_DIGIT: str = "X" diff --git a/src/undate/converters/edtf/transformer.py b/src/undate/converters/edtf/transformer.py index d5bcfcb..0b1de76 100644 --- a/src/undate/converters/edtf/transformer.py +++ b/src/undate/converters/edtf/transformer.py @@ -1,6 +1,6 @@ from lark import Token, Transformer, Tree -from undate.undate import Undate, UndateInterval +from undate import Undate, UndateInterval class EDTFTransformer(Transformer): diff --git a/src/undate/converters/iso8601.py b/src/undate/converters/iso8601.py index 09399eb..4f05b69 100644 --- a/src/undate/converters/iso8601.py +++ b/src/undate/converters/iso8601.py @@ -1,7 +1,7 @@ from typing import Dict, List, Union +from undate import Undate, UndateInterval from undate.converters.base import BaseDateConverter -from undate.undate import Undate, UndateInterval class ISO8601DateFormat(BaseDateConverter): diff --git a/src/undate/interval.py b/src/undate/interval.py new file mode 100644 index 0000000..787aa5a --- /dev/null +++ b/src/undate/interval.py @@ -0,0 +1,125 @@ +import datetime + +# Pre 3.10 requires Union for multiple types, e.g. Union[int, None] instead of int | None +from typing import Optional, Union + + +from undate import Undate +from undate.date import ONE_DAY, ONE_YEAR, Timedelta +from undate.converters.base import BaseDateConverter + + +class UndateInterval: + """A date range between two uncertain dates. + + :param earliest: Earliest undate + :type earliest: `undate.Undate` + :param latest: Latest undate + :type latest: `undate.Undate` + :param label: A string to label a specific undate interval, similar to labels of `undate.Undate`. + :type label: `str` + """ + + # date range between two undates + earliest: Union[Undate, None] + latest: Union[Undate, None] + label: Union[str, None] + + # TODO: let's think about adding an optional precision / length /size field + # using DatePrecision + + def __init__( + self, + earliest: Optional[Undate] = None, + latest: Optional[Undate] = None, + label: Optional[str] = None, + ): + # for now, assume takes two undate objects; + # support conversion from datetime + if earliest and not isinstance(earliest, Undate): + # NOTE: some overlap with Undate._comparison_type method + # maybe support conversion from other formats later + if isinstance(earliest, datetime.date): + earliest = Undate.from_datetime_date(earliest) + else: + raise ValueError( + f"earliest date {earliest} cannot be converted to Undate" + ) + if latest and not isinstance(latest, Undate): + if isinstance(latest, datetime.date): + latest = Undate.from_datetime_date(latest) + else: + raise ValueError(f"latest date {latest} cannot be converted to Undate") + + # check that the interval is valid + if latest and earliest and latest <= earliest: + raise ValueError(f"invalid interval {earliest}-{latest}") + + self.earliest = earliest + self.latest = latest + self.label = label + + def __str__(self) -> str: + # using EDTF syntax for open ranges + return "%s/%s" % (self.earliest or "..", self.latest or "") + + def format(self, format) -> str: + """format this undate interval as a string using the specified format; + for now, only supports named converters""" + converter_cls = BaseDateConverter.available_converters().get(format, None) + print(f"converter_cls == {converter_cls}") + if converter_cls: + return converter_cls().to_string(self) + + raise ValueError(f"Unsupported format '{format}'") + + def __repr__(self) -> str: + if self.label: + return "" % (self.label, self) + return "" % self + + def __eq__(self, other) -> bool: + # consider interval equal if both dates are equal + return self.earliest == other.earliest and self.latest == other.latest + + def duration(self) -> Timedelta: + """Calculate the duration between two undates. + Note that durations are inclusive (i.e., a closed interval), and + include both the earliest and latest date rather than the difference + between them. + + :returns: A duration + :rtype: Timedelta + """ + # what is the duration of this date range? + + # if range is open-ended, can't calculate + if self.earliest is None or self.latest is None: + return NotImplemented + + # if both years are known, subtract end of range from beginning of start + if self.latest.known_year and self.earliest.known_year: + return self.latest.latest - self.earliest.earliest + ONE_DAY + + # if neither year is known... + elif not self.latest.known_year and not self.earliest.known_year: + # under what circumstances can we assume that if both years + # are unknown the dates are in the same year or sequential? + duration = self.latest.earliest - self.earliest.earliest + # if we get a negative, we've wrapped from end of one year + # to the beginning of the next; + # recalculate assuming second date is in the subsequent year + if duration.days < 0: + end = self.latest.earliest + ONE_YEAR + duration = end - self.earliest.earliest + + # add the additional day *after* checking for a negative + # or after recalculating with adjusted year + duration += ONE_DAY + + return duration + + else: + # is there any meaningful way to calculate duration + # if one year is known and the other is not? + raise NotImplementedError diff --git a/src/undate/undate.py b/src/undate/undate.py index c12d022..2008914 100644 --- a/src/undate/undate.py +++ b/src/undate/undate.py @@ -1,8 +1,12 @@ -import datetime -import re +from __future__ import annotations +import datetime from enum import auto +import re +from typing import TYPE_CHECKING +if TYPE_CHECKING: + from undate.interval import UndateInterval try: # StrEnum was only added in python 3.11 from enum import StrEnum @@ -14,7 +18,7 @@ from typing import Dict, Optional, Union from undate.converters.base import BaseDateConverter -from undate.date import ONE_DAY, ONE_MONTH_MAX, ONE_YEAR, Date, DatePrecision, Timedelta +from undate.date import ONE_DAY, ONE_MONTH_MAX, Date, DatePrecision, Timedelta class Calendar(StrEnum): @@ -218,7 +222,7 @@ def __repr__(self) -> str: return f"" @classmethod - def parse(cls, date_string, format) -> Union["Undate", "UndateInterval"]: + def parse(cls, date_string, format) -> Union["Undate", UndateInterval]: """parse a string to an undate or undate interval using the specified format; for now, only supports named converters""" converter_cls = BaseDateConverter.available_converters().get(format, None) @@ -487,98 +491,3 @@ def _missing_digit_minmax( min_val = int("".join(new_min_val)) max_val = int("".join(new_max_val)) return (min_val, max_val) - - -class UndateInterval: - """A date range between two uncertain dates. - - :param earliest: Earliest undate - :type earliest: `undate.Undate` - :param latest: Latest undate - :type latest: `undate.Undate` - :param label: A string to label a specific undate interval, similar to labels of `undate.Undate`. - :type label: `str` - """ - - # date range between two undates - earliest: Union[Undate, None] - latest: Union[Undate, None] - label: Union[str, None] - - # TODO: let's think about adding an optional precision / length /size field - # using DatePrecision - - def __init__( - self, - earliest: Optional[Undate] = None, - latest: Optional[Undate] = None, - label: Optional[str] = None, - ): - # for now, assume takes two undate objects - self.earliest = earliest - self.latest = latest - self.label = label - - def __str__(self) -> str: - # using EDTF syntax for open ranges - return "%s/%s" % (self.earliest or "..", self.latest or "") - - def format(self, format) -> str: - """format this undate interval as a string using the specified format; - for now, only supports named converters""" - converter_cls = BaseDateConverter.available_converters().get(format, None) - if converter_cls: - return converter_cls().to_string(self) - - raise ValueError(f"Unsupported format '{format}'") - - def __repr__(self) -> str: - if self.label: - return "" % (self.label, self) - return "" % self - - def __eq__(self, other) -> bool: - # consider interval equal if both dates are equal - return self.earliest == other.earliest and self.latest == other.latest - - def duration(self) -> Timedelta: - """Calculate the duration between two undates. - Note that durations are inclusive (i.e., a closed interval), and - include both the earliest and latest date rather than the difference - between them. - - :returns: A duration - :rtype: Timedelta - """ - # what is the duration of this date range? - - # if range is open-ended, can't calculate - if self.earliest is None or self.latest is None: - return NotImplemented - - # if both years are known, subtract end of range from beginning of start - if self.latest.known_year and self.earliest.known_year: - return self.latest.latest - self.earliest.earliest + ONE_DAY - - # if neither year is known... - elif not self.latest.known_year and not self.earliest.known_year: - # under what circumstances can we assume that if both years - # are unknown the dates are in the same year or sequential? - duration = self.latest.earliest - self.earliest.earliest - # if we get a negative, we've wrapped from end of one year - # to the beginning of the next; - # recalculate assuming second date is in the subsequent year - if duration.days < 0: - end = self.latest.earliest + ONE_YEAR - duration = end - self.earliest.earliest - - # add the additional day *after* checking for a negative - # or after recalculating with adjusted year - duration += ONE_DAY - - return duration - - else: - # is there any meaningful way to calculate duration - # if one year is known and the other is not? - raise NotImplementedError diff --git a/tests/test_converters/edtf/test_edtf_transformer.py b/tests/test_converters/edtf/test_edtf_transformer.py index 66488f6..3e82eb1 100644 --- a/tests/test_converters/edtf/test_edtf_transformer.py +++ b/tests/test_converters/edtf/test_edtf_transformer.py @@ -1,7 +1,8 @@ import pytest + +from undate import Undate, UndateInterval from undate.converters.edtf.parser import edtf_parser from undate.converters.edtf.transformer import EDTFTransformer -from undate.undate import Undate, UndateInterval # for now, just test that valid dates can be parsed diff --git a/tests/test_converters/test_edtf.py b/tests/test_converters/test_edtf.py index f159970..5210e98 100644 --- a/tests/test_converters/test_edtf.py +++ b/tests/test_converters/test_edtf.py @@ -1,7 +1,7 @@ import pytest from undate.converters.edtf import EDTFDateConverter from undate.date import DatePrecision -from undate.undate import Undate, UndateInterval +from undate import Undate, UndateInterval class TestEDTFDateConverter: diff --git a/tests/test_converters/test_iso8601.py b/tests/test_converters/test_iso8601.py index 73f645e..519eeb2 100644 --- a/tests/test_converters/test_iso8601.py +++ b/tests/test_converters/test_iso8601.py @@ -1,5 +1,5 @@ +from undate import Undate, UndateInterval from undate.converters.iso8601 import ISO8601DateFormat -from undate.undate import Undate, UndateInterval class TestISO8601DateFormat: diff --git a/tests/test_interval.py b/tests/test_interval.py new file mode 100644 index 0000000..dea8710 --- /dev/null +++ b/tests/test_interval.py @@ -0,0 +1,145 @@ +import calendar +import datetime + +import pytest + +from undate import Undate, UndateInterval +from undate.date import Timedelta + + +class TestUndateInterval: + def test_init_types(self): + # datetime.date - autoconvert + interval = UndateInterval(datetime.date(2022, 1, 1), None) + assert isinstance(interval.earliest, Undate) + interval = UndateInterval(None, datetime.date(2022, 1, 1)) + assert isinstance(interval.latest, Undate) + + # unsupported type should raise exception + with pytest.raises( + ValueError, match="earliest date 2022 cannot be converted to Undate" + ): + UndateInterval(2022, None) + + with pytest.raises( + ValueError, match="latest date 1982 cannot be converted to Undate" + ): + UndateInterval(None, "1982") + + def test_init_validation(self): + with pytest.raises(ValueError, match="invalid interval"): + UndateInterval(Undate(2020), Undate(1010)) + + def test_str(self): + # 2022 - 2023 + assert str(UndateInterval(Undate(2022), Undate(2023))) == "2022/2023" + # 2022 - 2023-05 + assert str(UndateInterval(Undate(2022), Undate(2023, 5))) == "2022/2023-05" + # 2022-11-01 to 2022-11-07 + assert ( + str(UndateInterval(Undate(2022, 11, 1), Undate(2023, 11, 7))) + == "2022-11-01/2023-11-07" + ) + + def test_format(self): + interval = UndateInterval(Undate(2000), Undate(2001)) + assert interval.format("EDTF") == "2000/2001" + assert interval.format("ISO8601") == "2000/2001" + + # Open-ended intervals + open_start = UndateInterval(latest=Undate(2000)) + assert open_start.format("EDTF") == "../2000" + assert open_start.format("ISO8601") == "/2000" + + open_end = UndateInterval(earliest=Undate(2000)) + assert open_end.format("EDTF") == "2000/.." + assert open_end.format("ISO8601") == "2000/" + + def test_repr(self): + assert ( + repr(UndateInterval(Undate(2022), Undate(2023))) + == "" + ) + assert ( + repr(UndateInterval(Undate(2022), Undate(2023), label="Fancy Epoch")) + == "" + ) + + def test_str_open_range(self): + # 900 - + assert str(UndateInterval(Undate(900))) == "0900/" + # - 1900 + assert str(UndateInterval(latest=Undate(1900))) == "../1900" + # - 1900-12 + assert str(UndateInterval(latest=Undate(1900, 12))) == "../1900-12" + + def test_eq(self): + assert UndateInterval(Undate(2022), Undate(2023)) == UndateInterval( + Undate(2022), Undate(2023) + ) + assert UndateInterval(Undate(2022), Undate(2023, 5)) == UndateInterval( + Undate(2022), Undate(2023, 5) + ) + assert UndateInterval(Undate(2022, 5)) == UndateInterval(Undate(2022, 5)) + + def test_not_eq(self): + assert UndateInterval(Undate(2022), Undate(2023)) != UndateInterval( + Undate(2022), Undate(2024) + ) + assert UndateInterval(Undate(2022), Undate(2023, 5)) != UndateInterval( + Undate(2022), Undate(2023, 6) + ) + assert UndateInterval(Undate(2022), Undate(2023, 5)) != UndateInterval( + Undate(2022), Undate(2023) + ) + assert UndateInterval(Undate(2022, 5)) != UndateInterval(Undate(2022, 6)) + + def test_min_year_non_leapyear(self): + assert not calendar.isleap(Undate.MIN_ALLOWABLE_YEAR) + + def test_duration(self): + week_duration = UndateInterval( + Undate(2022, 11, 1), Undate(2022, 11, 7) + ).duration() + assert isinstance(week_duration, Timedelta) + assert week_duration.days == 7 + + twomonths = UndateInterval(Undate(2022, 11), Undate(2022, 12)).duration() + # november - december = 30 days + 31 days + assert twomonths.days == 30 + 31 + + twoyears = UndateInterval(Undate(2021), Undate(2022)).duration() + assert twoyears.days == 365 * 2 + + # special case: month/day with no year (assumes same year) + week_noyear_duration = UndateInterval( + Undate(None, 11, 1), Undate(None, 11, 7) + ).duration() + assert week_noyear_duration.days == 7 + # special case 2: month/day with no year, wrapping from december to january + # (assumes sequential years) + month_noyear_duration = UndateInterval( + Undate(None, 12, 1), Undate(None, 1, 1) + ).duration() + assert month_noyear_duration.days == 32 + + # real world test cases from Shakespeare and Company Project data; + # second date is a year minus one day in the future + month_noyear_duration = UndateInterval( + Undate(None, 6, 7), Undate(None, 6, 6) + ).duration() + assert month_noyear_duration.days == 365 + + # durations that span february in unknown years should assume + # non-leap years + jan_march_duration = UndateInterval( + Undate(None, 2, 28), Undate(None, 3, 1) + ).duration() + assert jan_march_duration.days == 2 + + # duration is not supported for open-ended intervals + assert UndateInterval(Undate(2000), None).duration() == NotImplemented + + # one year set and the other not currently raises not implemented error + with pytest.raises(NotImplementedError): + UndateInterval(Undate(2000), Undate(month=10)).duration() diff --git a/tests/test_undate.py b/tests/test_undate.py index ecf0777..8f8a5c8 100644 --- a/tests/test_undate.py +++ b/tests/test_undate.py @@ -1,11 +1,10 @@ -import calendar from datetime import date import pytest +from undate import Undate, UndateInterval, Calendar from undate.converters.base import BaseCalendarConverter from undate.date import DatePrecision, Timedelta -from undate.undate import Undate, UndateInterval, Calendar class TestUndate: @@ -452,122 +451,6 @@ def test_format(self): Undate(1984).format("%Y-%m") -class TestUndateInterval: - def test_str(self): - # 2022 - 2023 - assert str(UndateInterval(Undate(2022), Undate(2023))) == "2022/2023" - # 2022 - 2023-05 - assert str(UndateInterval(Undate(2022), Undate(2023, 5))) == "2022/2023-05" - # 2022-11-01 to 2022-11-07 - assert ( - str(UndateInterval(Undate(2022, 11, 1), Undate(2023, 11, 7))) - == "2022-11-01/2023-11-07" - ) - - def test_format(self): - interval = UndateInterval(Undate(2000), Undate(2001)) - assert interval.format("EDTF") == "2000/2001" - assert interval.format("ISO8601") == "2000/2001" - - # Open-ended intervals - open_start = UndateInterval(latest=Undate(2000)) - assert open_start.format("EDTF") == "../2000" - assert open_start.format("ISO8601") == "/2000" - - open_end = UndateInterval(earliest=Undate(2000)) - assert open_end.format("EDTF") == "2000/.." - assert open_end.format("ISO8601") == "2000/" - - def test_repr(self): - assert ( - repr(UndateInterval(Undate(2022), Undate(2023))) - == "" - ) - assert ( - repr(UndateInterval(Undate(2022), Undate(2023), label="Fancy Epoch")) - == "" - ) - - def test_str_open_range(self): - # 900 - - assert str(UndateInterval(Undate(900))) == "0900/" - # - 1900 - assert str(UndateInterval(latest=Undate(1900))) == "../1900" - # - 1900-12 - assert str(UndateInterval(latest=Undate(1900, 12))) == "../1900-12" - - def test_eq(self): - assert UndateInterval(Undate(2022), Undate(2023)) == UndateInterval( - Undate(2022), Undate(2023) - ) - assert UndateInterval(Undate(2022), Undate(2023, 5)) == UndateInterval( - Undate(2022), Undate(2023, 5) - ) - assert UndateInterval(Undate(2022, 5)) == UndateInterval(Undate(2022, 5)) - - def test_not_eq(self): - assert UndateInterval(Undate(2022), Undate(2023)) != UndateInterval( - Undate(2022), Undate(2024) - ) - assert UndateInterval(Undate(2022), Undate(2023, 5)) != UndateInterval( - Undate(2022), Undate(2023, 6) - ) - assert UndateInterval(Undate(2022), Undate(2023, 5)) != UndateInterval( - Undate(2022), Undate(2023) - ) - assert UndateInterval(Undate(2022, 5)) != UndateInterval(Undate(2022, 6)) - - def test_min_year_non_leapyear(self): - assert not calendar.isleap(Undate.MIN_ALLOWABLE_YEAR) - - def test_duration(self): - week_duration = UndateInterval( - Undate(2022, 11, 1), Undate(2022, 11, 7) - ).duration() - assert isinstance(week_duration, Timedelta) - assert week_duration.days == 7 - - twomonths = UndateInterval(Undate(2022, 11), Undate(2022, 12)).duration() - # november - december = 30 days + 31 days - assert twomonths.days == 30 + 31 - - twoyears = UndateInterval(Undate(2021), Undate(2022)).duration() - assert twoyears.days == 365 * 2 - - # special case: month/day with no year (assumes same year) - week_noyear_duration = UndateInterval( - Undate(None, 11, 1), Undate(None, 11, 7) - ).duration() - assert week_noyear_duration.days == 7 - # special case 2: month/day with no year, wrapping from december to january - # (assumes sequential years) - month_noyear_duration = UndateInterval( - Undate(None, 12, 1), Undate(None, 1, 1) - ).duration() - assert month_noyear_duration.days == 32 - - # real world test cases from Shakespeare and Company Project data; - # second date is a year minus one day in the future - month_noyear_duration = UndateInterval( - Undate(None, 6, 7), Undate(None, 6, 6) - ).duration() - assert month_noyear_duration.days == 365 - - # durations that span february in unknown years should assume - # non-leap years - jan_march_duration = UndateInterval( - Undate(None, 2, 28), Undate(None, 3, 1) - ).duration() - assert jan_march_duration.days == 2 - - # duration is not supported for open-ended intervals - assert UndateInterval(Undate(2000), None).duration() == NotImplemented - - # one year set and the other not currently raises not implemented error - with pytest.raises(NotImplementedError): - UndateInterval(Undate(2000), Undate()).duration() - - def test_calendar_get_converter(): # ensure we can retrieve a calendar converter for each # calendar named in our calendar enum From 60aefc41426c66cdbfc3482361efaee2df3df11b Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Thu, 20 Feb 2025 17:36:22 -0500 Subject: [PATCH 2/3] Update example notebook to import UndateInterval from new location --- examples/notebooks/shxco_partial_date_durations.ipynb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/notebooks/shxco_partial_date_durations.ipynb b/examples/notebooks/shxco_partial_date_durations.ipynb index 9e291f9..486981a 100644 --- a/examples/notebooks/shxco_partial_date_durations.ipynb +++ b/examples/notebooks/shxco_partial_date_durations.ipynb @@ -316,14 +316,14 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 1, "metadata": { "id": "y_MqgrQW64uI" }, "outputs": [], "source": [ + "from undate import UndateInterval\n", "from undate.date import ONE_DAY\n", - "from undate.undate import UndateInterval\n", "from undate.converters.iso8601 import ISO8601DateFormat\n", "\n", "def undate_duration(start_date, end_date):\n", From 1607b98806acae946714262ddbdba6bd016e5086 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Wed, 5 Mar 2025 18:42:41 -0500 Subject: [PATCH 3/3] Remove debug print statement flagged by code rabbit --- src/undate/interval.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/undate/interval.py b/src/undate/interval.py index 787aa5a..33ec200 100644 --- a/src/undate/interval.py +++ b/src/undate/interval.py @@ -67,7 +67,6 @@ def format(self, format) -> str: """format this undate interval as a string using the specified format; for now, only supports named converters""" converter_cls = BaseDateConverter.available_converters().get(format, None) - print(f"converter_cls == {converter_cls}") if converter_cls: return converter_cls().to_string(self)