Spaces:
Sleeping
Sleeping
File size: 4,085 Bytes
ed76d6e | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 | import re
from datetime import datetime
from typing import Union
from dateutil import parser as gregorian_parser
from num2fawords import words
from num2words import num2words
from persiantools import digits
from persiantools.jdatetime import JalaliDate
from rules.base import BaseProcessor
import random
Date = Union[datetime, JalaliDate]
GREGORIAN_MONTHS = (
"January",
"February",
"March",
"April",
"May",
"June",
"July",
"August",
"September",
"October",
"November",
"December",
)
DATE_PATTERN = re.compile(
r"""
(?:
(?P<year>\d{4})[^\d]+(?P<month>\d{1,2})[^\d]+(?P<day>\d{1,2})
|
(?P<day_first>\d{1,2})[^\d]+(?P<month_first>\d{1,2})[^\d]+(?P<year_first>\d{4})
)
""",
re.VERBOSE,
)
class DateProcessor(BaseProcessor):
def __init__(self, epsilon:float=1.0):
super(DateProcessor, self).__init__(epsilon=epsilon)
def process(self, date_string: str, language: str, *args, **kwargs) -> str:
if random.random() > self.epsilon:
return date_string
if not self.is_date(date_string):
return date_string
try:
date = self._parse_date(date_string)
except ValueError:
return date_string
if date is None:
raise date_string
return self._date_to_text(date, language)
@staticmethod
def is_date(date_string: str) -> bool:
date_string = digits.fa_to_en(date_string)
return DATE_PATTERN.fullmatch(date_string) is not None
def _parse_date(self, date_string: str) -> Date | None:
clean_string = digits.fa_to_en(date_string)
match = DATE_PATTERN.search(clean_string)
if match:
year, month, day = self._extract_date_parts(match)
# Years less than 1600 is considered as Jalali
if year < 1600:
try:
return JalaliDate(year, month, day)
except ValueError:
pass
# Try Gregorian parsing.
try:
date = gregorian_parser.parse(clean_string)
if date.year >= 1600:
return date
except (ValueError, OverflowError):
pass
# If regex matched, give Jalali parsing one final attempt so that
# invalid Jalali dates produce a useful error.
if match:
year, month, day = self._extract_date_parts(match)
try:
return JalaliDate(year, month, day)
except ValueError as exc:
raise ValueError(
f"Invalid Jalali date: {date_string!r}"
) from exc
return None
@staticmethod
def _extract_date_parts(
match: re.Match[str],
) -> tuple[int, int, int]:
if match.group("year"):
return (
int(match.group("year")),
int(match.group("month")),
int(match.group("day")),
)
return (
int(match.group("year_first")),
int(match.group("month_first")),
int(match.group("day_first")),
)
def _date_to_text(self, date: Date, language: str) -> str:
if language == "fa":
return self._date_to_persian(date)
if language == "en":
return self._date_to_english(date)
raise ValueError(f"Unsupported language: {language!r}")
@staticmethod
def _date_to_persian(date: Date) -> str:
return f"{words(date.day)} {words(date.month)} {words(date.year)}"
@staticmethod
def _date_to_english(date: Date) -> str:
year = date.year
if "00" in str(year) or len(str(year)) < 4:
year_text = num2words(year)
else:
year_text = (
f"{num2words(year // 100)} "
f"{num2words(year % 100)}"
)
return (
f"{num2words(date.day, ordinal=True)} "
f"of {GREGORIAN_MONTHS[date.month - 1]}, "
f"{year_text}"
) |