diff --git a/src/undate/converters/calendars/__init__.py b/src/undate/converters/calendars/__init__.py new file mode 100644 index 0000000..edc3efc --- /dev/null +++ b/src/undate/converters/calendars/__init__.py @@ -0,0 +1,3 @@ +from undate.converters.calendars.hijri import HijriDateConverter + +__all__ = ["HijriDateConverter"] diff --git a/src/undate/converters/calendars/hijri/__init__.py b/src/undate/converters/calendars/hijri/__init__.py index e69de29..4ac5b4b 100644 --- a/src/undate/converters/calendars/hijri/__init__.py +++ b/src/undate/converters/calendars/hijri/__init__.py @@ -0,0 +1,3 @@ +from undate.converters.calendars.hijri.converter import HijriDateConverter + +__all__ = ["HijriDateConverter"] diff --git a/src/undate/converters/calendars/hijri/converter.py b/src/undate/converters/calendars/hijri/converter.py new file mode 100644 index 0000000..0502a12 --- /dev/null +++ b/src/undate/converters/calendars/hijri/converter.py @@ -0,0 +1,48 @@ +from typing import Union + +from lark.exceptions import UnexpectedCharacters + +from undate.converters.base import BaseDateConverter +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(BaseDateConverter): + """ + Converter for Hijri / Islamic calendar. + + Support for parsing Hijri dates and converting to Undate and UndateInterval + objects in the Gregorian calendar. + """ + + #: converter name: Hijri + name: str = "Hijri" + calendar_name: str = "Hijrī" + + def __init__(self): + self.transformer = HijriDateTransformer() + + def parse(self, value: str) -> Union[Undate, UndateInterval]: + """ + Parse a Hijri date string and return an :class:`~undate.undate.Undate` or + :class:`~undate.undate.UndateInterval` in Gregorian calendar. + The Hijri date string is preserved in the undate label + """ + if not value: + raise ValueError("Parsing empty string is not supported") + + # parse the input string, then transform to undate object + try: + # parse the string with our Hijri date parser + parsetree = hijri_parser.parse(value) + # transform the parse tree into an undate or undate interval + undate_obj = self.transformer.transform(parsetree) + # set the original date as a label, with the calendar name + undate_obj.label = f"{value} {self.calendar_name}" + return undate_obj + except UnexpectedCharacters: + raise ValueError("Could not parse '%s' as a Hijri date" % value) + + # do we need to support conversion the other direction? + # i.e., generate a Hijri date from an abitrary undate or undate interval? diff --git a/tests/test_converters/test_calendars/test_hijri/test_hijri_converter.py b/tests/test_converters/test_calendars/test_hijri/test_hijri_converter.py new file mode 100644 index 0000000..6493083 --- /dev/null +++ b/tests/test_converters/test_calendars/test_hijri/test_hijri_converter.py @@ -0,0 +1,32 @@ +import pytest +from undate.converters.calendars import HijriDateConverter +from undate.undate import Undate, UndateInterval + + +class TestHijriDateConverter: + def test_parse_(self): + # day + date_str = "7 Jumādā I 1243" + date = HijriDateConverter().parse(date_str) + assert date == Undate(1827, 11, 26) + assert date.label == f"{date_str} {HijriDateConverter.calendar_name}" + + # month + date_str = "Rajab 495" + date = HijriDateConverter().parse(date_str) + assert date == UndateInterval(Undate(1102, 4, 28), Undate(1102, 5, 27)) + assert date.label == f"{date_str} {HijriDateConverter.calendar_name}" + + # year + date_str = "441" + date = HijriDateConverter().parse(date_str) + assert date == UndateInterval(Undate(1049, 6, 11), Undate(1050, 5, 31)) + assert date.label == f"{date_str} {HijriDateConverter.calendar_name}" + + def test_parse_error(self): + # a string we can't parse should raise an error + with pytest.raises(ValueError): + HijriDateConverter().parse("January 2, 1991") + # empty string should also error + with pytest.raises(ValueError): + HijriDateConverter().parse("")