From a8802d6fce30adf3cac8869028338850049a8b30 Mon Sep 17 00:00:00 2001 From: rlskoeser Date: Thu, 29 Aug 2024 18:18:02 -0400 Subject: [PATCH] Add string format and parse methods to undate and undate interval --- src/undate/undate.py | 30 ++++++++++++++++++++++++ tests/test_undate.py | 54 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/src/undate/undate.py b/src/undate/undate.py index 86b3f6b..3a506d7 100644 --- a/src/undate/undate.py +++ b/src/undate/undate.py @@ -163,6 +163,27 @@ def __repr__(self) -> str: return "" % (self.label, self) return "" % self + @classmethod + 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 formatters""" + formatter_cls = BaseDateFormat.available_formatters().get(format, None) + if formatter_cls: + # NOTE: some parsers may return intervals; is that ok here? + return formatter_cls().parse(date_string) + + raise ValueError(f"Unsupported format '{format}'") + + def format(self, format) -> str: + """format this undate as a string using the specified format; + for now, only supports named formatters""" + formatter_cls = BaseDateFormat.available_formatters().get(format, None) + if formatter_cls: + # NOTE: some parsers may return intervals; is that ok here? + return formatter_cls().to_string(self) + + raise ValueError(f"Unsupported format '{format}'") + def _comparison_type(self, other: object) -> "Undate": """Common logic for type handling in comparison methods. Converts to Undate object if possible, otherwise raises @@ -424,6 +445,15 @@ 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 formatters""" + formatter_cls = BaseDateFormat.available_formatters().get(format, None) + if formatter_cls: + return formatter_cls().to_string(self) + + raise ValueError(f"Unsupported format '{format}'") + def __repr__(self) -> str: if self.label: return "" % (self.label, self) diff --git a/tests/test_undate.py b/tests/test_undate.py index 08d4104..5a18131 100644 --- a/tests/test_undate.py +++ b/tests/test_undate.py @@ -379,6 +379,46 @@ def test_is_known_day(self): assert Undate(month=1, day="X5").is_known("day") is False assert Undate(month=1, day="XX").is_known("day") is False + def test_parse(self): + assert Undate.parse("1984", "EDTF") == Undate(1984) + assert Undate.parse("1984-04", "EDTF") == Undate(1984, 4) + assert Undate.parse("1984-04", "EDTF") == Undate(1984, 4) + assert Undate.parse("2000/2001", "EDTF") == UndateInterval( + Undate(2000), Undate(2001) + ) + + assert Undate.parse("1984", "ISO8601") == Undate(1984) + assert Undate.parse("1984-04", "ISO8601") == Undate(1984, 4) + assert Undate.parse("--12-31", "ISO8601") == Undate(month=12, day=31) + + # unsupported format + with pytest.raises(ValueError, match="Unsupported format"): + Undate.parse("1984", "foobar") + with pytest.raises(ValueError, match="Unsupported format"): + Undate.parse("1984", "%Y-%m") + + def test_format(self): + # EDTF format + assert Undate(1984).format("EDTF") == "1984" + assert Undate(1984, 4).format("EDTF") == "1984-04" + assert Undate(1984, 4, 15).format("EDTF") == "1984-04-15" + assert Undate("19XX").format("EDTF") == "19XX" + assert Undate(1984, "XX").format("EDTF") == "1984-XX" + assert Undate(1984, 4, "XX").format("EDTF") == "1984-04-XX" + assert Undate(month=12, day=31).format("EDTF") == "XXXX-12-31" + + # ISO8601 format + assert Undate(1984).format("ISO8601") == "1984" + assert Undate(1984, 4).format("ISO8601") == "1984-04" + assert Undate(1984, 4, 15).format("ISO8601") == "1984-04-15" + assert Undate(month=12, day=31).format("ISO8601") == "--12-31" + + # unsupported format + with pytest.raises(ValueError, match="Unsupported format"): + Undate(1984).format("foobar") + with pytest.raises(ValueError, match="Unsupported format"): + Undate(1984).format("%Y-%m") + class TestUndateInterval: def test_str(self): @@ -392,6 +432,20 @@ def test_str(self): == "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)))