diff --git a/README.md b/README.md index cd73453..c1b662d 100644 --- a/README.md +++ b/README.md @@ -121,13 +121,13 @@ earliest and latest possible dates for comparison purposes so you can sort dates and compare with equals, greater than, and less than. You can also compare with python `datetime.date` objects. -```python +```python >>> november7_2020 = Undate(2020, 11, 7) >>> november_2001 = Undate(2001, 11) >>> year2k = Undate(2000) >>> ad100 = Undate(100) >>> sorted([november7_2020, november_2001, year2k, ad100]) -[, , , ] +[undate.Undate(year=100, calendar="Gregorian"), undate.Undate(year=2000, calendar="Gregorian"), undate.Undate(year=2001, month=11, calendar="Gregorian"), undate.Undate(year=2020, month=11, day=7, calendar="Gregorian")] >>> november7_2020 > november_2001 True >>> year2k < ad100 @@ -161,17 +161,17 @@ and latest date as part of the range. ```python >>> from undate import UndateInterval >>> UndateInterval(Undate(1900), Undate(2000)) - +undate.UndateInterval(earliest=undate.Undate(year=1900, calendar="Gregorian"), latest=undate.Undate(year=2000, calendar="Gregorian")) >>> UndateInterval(Undate(1801), Undate(1900), label="19th century") +undate.UndateInterval(earliest=undate.Undate(year=1801, calendar="Gregorian"), latest=undate.Undate(year=1900, calendar="Gregorian"), label="19th century") >>> UndateInterval(Undate(1801), Undate(1900), label="19th century").duration().days 36524 - >>> UndateInterval(Undate(1901), Undate(2000), label="20th century") - +undate.UndateInterval(earliest=undate.Undate(year=1901, calendar="Gregorian"), latest=undate.Undate(year=2000, calendar="Gregorian"), label="20th century") >>> UndateInterval(latest=Undate(2000)) # before 2000 - +undate.UndateInterval(latest=undate.Undate(year=2000, calendar="Gregorian")) >>> UndateInterval(Undate(1900)) # after 1900 - +undate.UndateInterval(earliest=undate.Undate(year=1900, calendar="Gregorian")) >>> UndateInterval(Undate(1900), Undate(2000), label="19th century").duration().days 36890 >>> UndateInterval(Undate(2000, 1, 1), Undate(2000, 1,31)).duration().days @@ -186,15 +186,15 @@ are "ISO8601" and "EDTF" and supported calendars. ```python >>> from undate import Undate >>> Undate.parse("2002", "ISO8601") - +undate.Undate(year=2002, calendar="Gregorian") >>> Undate.parse("2002-05", "EDTF") - +undate.Undate(year=2002, month=5, calendar="Gregorian") >>> Undate.parse("--05-03", "ISO8601") - +undate.Undate(month=5, day=3, calendar="Gregorian") >>> Undate.parse("--05-03", "ISO8601").format("EDTF") 'XXXX-05-03' ->>> Undate.parse("1800/1900") - +>>> Undate.parse("1800/1900", format="EDTF") +undate.UndateInterval(earliest=undate.Undate(year=1800, calendar="Gregorian"), latest=undate.Undate(year=1900, calendar="Gregorian")) ``` ### Calendars @@ -215,26 +215,26 @@ comparison across dates from different calendars. >>> from undate import Undate >>> tammuz4816 = Undate.parse("26 Tammuz 4816", "Hebrew") >>> tammuz4816 - +undate.Undate(year=4816, month=4, day=26, label="26 Tammuz 4816 Anno Mundi", calendar="Hebrew") >>> rajab495 = Undate.parse("Rajab 495", "Islamic") >>> rajab495 - +undate.Undate(year=495, month=7, label="Rajab 495 Islamic", calendar="Islamic") >>> y2k = Undate.parse("2001", "EDTF") >>> y2k - +undate.Undate(year=2001, calendar="Gregorian") >>> [str(d.earliest) for d in [rajab495, tammuz4816, y2k]] ['1102-04-28', '1056-07-17', '2001-01-01'] >>> [str(d.precision) for d in [rajab495, tammuz4816, y2k]] ['MONTH', 'DAY', 'YEAR'] >>> sorted([rajab495, tammuz4816, y2k]) -[, , ] +[undate.Undate(year=4816, month=4, day=26, label="26 Tammuz 4816 Anno Mundi", calendar="Hebrew"), undate.Undate(year=495, month=7, label="Rajab 495 Islamic", calendar="Islamic"), undate.Undate(year=2001, calendar="Gregorian")] ``` --- -For more examples, refer to the code notebooks included in the[examples] -(https://github.com/dh-tech/undate-python/tree/main/examples/) in this -repository. +For more examples, refer to the code notebooks included in the +[examples](https://github.com/dh-tech/undate-python/tree/main/examples/) +directory in this repository. ## Documentation diff --git a/src/undate/__init__.py b/src/undate/__init__.py index 71e09ae..44e9b04 100644 --- a/src/undate/__init__.py +++ b/src/undate/__init__.py @@ -1,7 +1,14 @@ __version__ = "0.6.0.dev0" -from undate.date import DatePrecision +from undate.date import DatePrecision, UnDelta from undate.undate import Undate, Calendar from undate.interval import UndateInterval -__all__ = ["Undate", "UndateInterval", "Calendar", "DatePrecision", "__version__"] +__all__ = [ + "Undate", + "UndateInterval", + "Calendar", + "DatePrecision", + "UnDelta", + "__version__", +] diff --git a/src/undate/date.py b/src/undate/date.py index 4e9eddc..44f79fa 100644 --- a/src/undate/date.py +++ b/src/undate/date.py @@ -145,7 +145,7 @@ def __init__(self, *days: int): def __repr__(self): # customize string representation for simpler notation; default # specifies full UnInt initialization with upper and lower keywords - return f"{self.__class__.__name__}(days=[{self.days.lower},{self.days.upper}])" + return f"undate.{self.__class__.__name__}({self.days.lower},{self.days.upper})" def __eq__(self, other: object) -> bool: # is an uncertain duration ever *equal* another, even if the values are the same? diff --git a/src/undate/interval.py b/src/undate/interval.py index ddfacdb..a7fbe55 100644 --- a/src/undate/interval.py +++ b/src/undate/interval.py @@ -70,9 +70,15 @@ def format(self, format) -> str: raise ValueError(f"Unsupported format '{format}'") def __repr__(self) -> str: - if self.label: - return "" % (self.label, self) - return "" % self + init_opts = { + "earliest": repr(self.earliest) if self.earliest else None, + "latest": repr(self.latest) if self.latest else None, + "label": f"{self.label!r}" if self.label else None, + } + init_str = ", ".join( + [f"{key}={val}" for key, val in init_opts.items() if val is not None] + ) + return f"undate.UndateInterval({init_str})" def __eq__(self, other) -> bool: # currently doesn't support comparison with any other types diff --git a/src/undate/undate.py b/src/undate/undate.py index e6561bf..5eaf6b9 100644 --- a/src/undate/undate.py +++ b/src/undate/undate.py @@ -250,8 +250,15 @@ def __str__(self) -> str: return self.converter.to_string(self) def __repr__(self) -> str: - label_str = f" '{self.label}'" if self.label else "" - return f"" + init_opts = {k: v for k, v in self.initial_values.items() if v is not None} + # include label if set + if self.label: + init_opts["label"] = self.label + # always include calendar + init_opts["calendar"] = self.calendar.value.title() + # combine parameters; use !r to quote strings + init_str = ", ".join([f"{key}={val!r}" for key, val in init_opts.items()]) + return f"undate.Undate({init_str})" @classmethod def parse(cls, date_string, format) -> Union["Undate", UndateInterval]: diff --git a/tests/test_date.py b/tests/test_date.py index 24703cb..fc6cc72 100644 --- a/tests/test_date.py +++ b/tests/test_date.py @@ -230,8 +230,20 @@ def test_init_validation(self): UnDelta(10) def test_repr(self): - # customized string representation - assert repr(UnDelta(28, 29)) == "UnDelta(days=[28,29])" + # test customized string representation + + # import undate to test eval of fully-qualified repr string + import undate # noqa: F401 + + feb_undelt = UnDelta(28, 29) + assert repr(feb_undelt) == "undate.UnDelta(28,29)" + # can't compare directly because uncertain deltas aren't equal, + # but compare values + assert eval(repr(feb_undelt.days.lower)) == feb_undelt.days.lower + assert eval(repr(feb_undelt.days.upper)) == feb_undelt.days.upper + + larger_undelt = UnDelta(10, 12, 14, 16) + assert repr(larger_undelt) == "undate.UnDelta(10,16)" def test_eq(self): # uncertain deltas are not equivalent diff --git a/tests/test_interval.py b/tests/test_interval.py index cf1a716..dbf28b3 100644 --- a/tests/test_interval.py +++ b/tests/test_interval.py @@ -56,14 +56,33 @@ def test_format(self): assert open_end.format("ISO8601") == "2000/" def test_repr(self): + # import undate to test eval of fully-qualified repr string + import undate # noqa: F401 + + # interval with start and end + closed_interval = UndateInterval(Undate(2022), Undate(2023)) + assert ( + repr(closed_interval) + == f"undate.UndateInterval(earliest={repr(closed_interval.earliest)}, latest={repr(closed_interval.latest)})" + ) + # should be able to evaluate repr string to get an equivalent object + assert eval(repr(closed_interval)) == closed_interval + # interval with a label + fancy_epoch = UndateInterval(Undate(2022), Undate(2023), label="Fancy Epoch") assert ( - repr(UndateInterval(Undate(2022), Undate(2023))) - == "" + repr(fancy_epoch) + == f"undate.UndateInterval(earliest={repr(fancy_epoch.earliest)}, latest={repr(fancy_epoch.latest)}, label='Fancy Epoch')" + ) + assert eval(repr(fancy_epoch)) == fancy_epoch + + open_interval = UndateInterval( + Undate(33), ) assert ( - repr(UndateInterval(Undate(2022), Undate(2023), label="Fancy Epoch")) - == "" + repr(open_interval) + == f"undate.UndateInterval(earliest={repr(open_interval.earliest)})" ) + assert eval(repr(open_interval)) == open_interval def test_str_open_range(self): # 900 - diff --git a/tests/test_undate.py b/tests/test_undate.py index 2cbaf7d..9c0e862 100644 --- a/tests/test_undate.py +++ b/tests/test_undate.py @@ -29,12 +29,36 @@ def test_partially_known_str(self): # assert str(Undate(2022, day=7)) == "2022-XX-07" @ currently returns 2022-07 def test_repr(self): - assert repr(Undate(2022, 11, 7)) == "" + # import undate to test eval of fully-qualified undate repr string + import undate # noqa: F401 + + nov2022 = Undate(2022, 11, 7) + # repr string should provide sufficient details to initialize + assert ( + repr(nov2022) + == "undate.Undate(year=2022, month=11, day=7, calendar='Gregorian')" + ) + # eval on repr string should be equivalent to the object + assert eval(repr(nov2022)) == nov2022 + nov2022_labeled = Undate(2022, 11, 7, label="A Special Day") + assert ( + repr(nov2022_labeled) + == "undate.Undate(year=2022, month=11, day=7, label='A Special Day', calendar='Gregorian')" + ) + assert eval(repr(nov2022_labeled)) == nov2022_labeled + # different calendar, missing fields + islamic_date = Undate(484, calendar=Calendar.ISLAMIC) + assert repr(islamic_date) == "undate.Undate(year=484, calendar='Islamic')" + assert eval(repr(islamic_date)) == islamic_date + + # test string values for month/day + unknown_year = Undate(month="1X", day="3X") assert ( - repr(Undate(2022, 11, 7, label="A Special Day")) - == "" + repr(unknown_year) + == "undate.Undate(month='1X', day='3X', calendar='Gregorian')" ) - assert repr(Undate(484, calendar=Calendar.ISLAMIC)) == "" + # unknown dates aren't equal, but string representation should match + assert str(eval(repr(unknown_year))) == str(unknown_year) def test_init_str(self): assert Undate("2000").earliest.year == 2000