X-Git-Url: https://wannabe.guru.org/gitweb/?a=blobdiff_plain;f=dateparse%2Fdateparse_utils.py;h=bd0d491f17e55fc4eef1894cfbe8a544b88787f2;hb=532df2c5b57c7517dfb3dddd8c1358fbadf8baf3;hp=6ba647c847e48931017292947bfe36cb423f772f;hpb=f2600f30801c849fc1d139386e3ddc3c9eb43e30;p=python_utils.git diff --git a/dateparse/dateparse_utils.py b/dateparse/dateparse_utils.py index 6ba647c..bd0d491 100755 --- a/dateparse/dateparse_utils.py +++ b/dateparse/dateparse_utils.py @@ -1,10 +1,11 @@ #!/usr/bin/env python3 # type: ignore +# pylint: disable=W0201 +# pylint: disable=R0904 -""" -Parse dates in a variety of formats. +# © Copyright 2021-2022, Scott Gasch -""" +"""Parse dates in a variety of formats.""" import datetime import functools @@ -57,6 +58,7 @@ class ParseException(Exception): """An exception thrown during parsing because of unrecognized input.""" def __init__(self, message: str) -> None: + super().__init__() self.message = message @@ -91,6 +93,8 @@ class RaisingErrorListener(antlr4.DiagnosticErrorListener): ), ) class DateParser(dateparse_utilsListener): + """A class to parse dates expressed in human language.""" + PARSE_TYPE_SINGLE_DATE_EXPR = 1 PARSE_TYPE_BASE_AND_OFFSET_EXPR = 2 PARSE_TYPE_SINGLE_TIME_EXPR = 3 @@ -160,6 +164,11 @@ class DateParser(dateparse_utilsListener): 'yea': TimeUnit.YEARS, } self.override_now_for_test_purposes = override_now_for_test_purposes + + # Note: _reset defines several class fields. It is used both here + # in the c'tor but also in between parse operations to restore the + # class' state and allow it to be reused. + # self._reset() def parse(self, date_string: str) -> Optional[datetime.datetime]: @@ -283,7 +292,7 @@ class DateParser(dateparse_utilsListener): name = DateParser._normalize_special_day_name(self.context['special']) # Yesterday, today, tomorrow -- ignore any next/last - if name == 'today' or name == 'now': + if name in ('today', 'now'): return today if name == 'yeste': return today + datetime.timedelta(days=-1) @@ -367,7 +376,8 @@ class DateParser(dateparse_utilsListener): day=self.context['day'], ) - def _parse_tz(self, txt: str) -> Any: + @staticmethod + def _parse_tz(txt: str) -> Any: if txt == 'Z': txt = 'UTC' @@ -390,7 +400,7 @@ class DateParser(dateparse_utilsListener): # Try constructing an offset in seconds try: txt_sign = txt[0] - if txt_sign == '-' or txt_sign == '+': + if txt_sign in ('-', '+'): sign = +1 if txt_sign == '+' else -1 hour = int(txt[1:3]) minute = int(txt[-2:]) @@ -401,14 +411,16 @@ class DateParser(dateparse_utilsListener): pass return None - def _get_int(self, txt: str) -> int: + @staticmethod + def _get_int(txt: str) -> int: while not txt[0].isdigit() and txt[0] != '-' and txt[0] != '+': txt = txt[1:] while not txt[-1].isdigit(): txt = txt[:-1] return int(txt) - # -- overridden methods invoked by parse walk -- + # -- overridden methods invoked by parse walk. Note: not part of the class' + # public API(!!) -- def visitErrorNode(self, node: antlr4.ErrorNode) -> None: pass @@ -491,12 +503,7 @@ class DateParser(dateparse_utilsListener): # Adjust count's sign based on the presence of 'before' or 'after'. if 'delta_before_after' in self.context: before_after = self.context['delta_before_after'].lower() - if ( - before_after == 'before' - or before_after == 'until' - or before_after == 'til' - or before_after == 'to' - ): + if before_after in ('before', 'until', 'til', 'to'): count = -count # What are we counting units of? @@ -532,12 +539,7 @@ class DateParser(dateparse_utilsListener): # Adjust count's sign based on the presence of 'before' or 'after'. if 'time_delta_before_after' in self.context: before_after = self.context['time_delta_before_after'].lower() - if ( - before_after == 'before' - or before_after == 'until' - or before_after == 'til' - or before_after == 'to' - ): + if before_after in ('before', 'until', 'til', 'to'): count = -count # What are we counting units of... assume minutes. @@ -560,10 +562,10 @@ class DateParser(dateparse_utilsListener): if n is None: raise ParseException(f'Bad N in Delta +/- Expr: {ctx.getText()}') n = n.getText() - n = self._get_int(n) + n = DateParser._get_int(n) unit = self._figure_out_date_unit(ctx.deltaUnit().getText().lower()) - except Exception: - raise ParseException(f'Invalid Delta +/-: {ctx.getText()}') + except Exception as e: + raise ParseException(f'Invalid Delta +/-: {ctx.getText()}') from e else: self.context['delta_int'] = n self.context['delta_unit'] = unit @@ -571,16 +573,16 @@ class DateParser(dateparse_utilsListener): def exitNextLastUnit(self, ctx: dateparse_utilsParser.DeltaUnitContext) -> None: try: unit = self._figure_out_date_unit(ctx.getText().lower()) - except Exception: - raise ParseException(f'Bad delta unit: {ctx.getText()}') + except Exception as e: + raise ParseException(f'Bad delta unit: {ctx.getText()}') from e else: self.context['delta_unit'] = unit def exitDeltaNextLast(self, ctx: dateparse_utilsParser.DeltaNextLastContext) -> None: try: txt = ctx.getText().lower() - except Exception: - raise ParseException(f'Bad next/last: {ctx.getText()}') + except Exception as e: + raise ParseException(f'Bad next/last: {ctx.getText()}') from e if 'month' in self.context or 'day' in self.context or 'year' in self.context: raise ParseException('Next/last expression expected to be relative to today.') if txt[:4] == 'next': @@ -606,8 +608,8 @@ class DateParser(dateparse_utilsListener): try: unit = self._figure_out_time_unit(ctx.deltaTimeUnit().getText().lower()) self.context['time_delta_unit'] = unit - except Exception: - raise ParseException(f'Bad delta unit: {ctx.getText()}') + except Exception as e: + raise ParseException(f'Bad delta unit: {ctx.getText()}') from e if 'time_delta_before_after' not in self.context: raise ParseException(f'Bad Before/After: {ctx.getText()}') @@ -622,22 +624,22 @@ class DateParser(dateparse_utilsListener): self.context['time_delta_unit'] = TimeUnit.MINUTES else: raise ParseException(f'Bad time fraction {ctx.getText()}') - except Exception: - raise ParseException(f'Bad time fraction {ctx.getText()}') + except Exception as e: + raise ParseException(f'Bad time fraction {ctx.getText()}') from e def exitDeltaBeforeAfter(self, ctx: dateparse_utilsParser.DeltaBeforeAfterContext) -> None: try: txt = ctx.getText().lower() - except Exception: - raise ParseException(f'Bad delta before|after: {ctx.getText()}') + except Exception as e: + raise ParseException(f'Bad delta before|after: {ctx.getText()}') from e else: self.context['delta_before_after'] = txt def exitDeltaTimeBeforeAfter(self, ctx: dateparse_utilsParser.DeltaBeforeAfterContext) -> None: try: txt = ctx.getText().lower() - except Exception: - raise ParseException(f'Bad delta before|after: {ctx.getText()}') + except Exception as e: + raise ParseException(f'Bad delta before|after: {ctx.getText()}') from e else: self.context['time_delta_before_after'] = txt @@ -694,8 +696,8 @@ class DateParser(dateparse_utilsListener): self.context['month'] = month self.context['day'] = 1 self.main_type = DateParser.PARSE_TYPE_BASE_AND_OFFSET_EXPR - except Exception: - raise ParseException(f'Invalid nthWeekday expression: {ctx.getText()}') + except Exception as e: + raise ParseException(f'Invalid nthWeekday expression: {ctx.getText()}') from e def exitFirstLastWeekdayInMonthMaybeYearExpr( self, @@ -705,9 +707,9 @@ class DateParser(dateparse_utilsListener): def exitNth(self, ctx: dateparse_utilsParser.NthContext) -> None: try: - i = self._get_int(ctx.getText()) - except Exception: - raise ParseException(f'Bad nth expression: {ctx.getText()}') + i = DateParser._get_int(ctx.getText()) + except Exception as e: + raise ParseException(f'Bad nth expression: {ctx.getText()}') from e else: self.context['nth'] = i @@ -720,8 +722,8 @@ class DateParser(dateparse_utilsListener): txt = -1 else: raise ParseException(f'Bad first|last expression: {ctx.getText()}') - except Exception: - raise ParseException(f'Bad first|last expression: {ctx.getText()}') + except Exception as e: + raise ParseException(f'Bad first|last expression: {ctx.getText()}') from e else: self.context['nth'] = txt @@ -729,8 +731,8 @@ class DateParser(dateparse_utilsListener): try: dow = ctx.getText().lower()[:3] dow = self.day_name_to_number.get(dow, None) - except Exception: - raise ParseException('Bad day of week') + except Exception as e: + raise ParseException('Bad day of week') from e else: self.context['dow'] = dow @@ -746,11 +748,11 @@ class DateParser(dateparse_utilsListener): if day[:3] == 'kal': self.context['day'] = 1 return - day = self._get_int(day) + day = DateParser._get_int(day) if day < 1 or day > 31: raise ParseException(f'Bad dayOfMonth expression: {ctx.getText()}') - except Exception: - raise ParseException(f'Bad dayOfMonth expression: {ctx.getText()}') + except Exception as e: + raise ParseException(f'Bad dayOfMonth expression: {ctx.getText()}') from e self.context['day'] = day def exitMonthName(self, ctx: dateparse_utilsParser.MonthNameContext) -> None: @@ -762,28 +764,28 @@ class DateParser(dateparse_utilsListener): month = self.month_name_to_number.get(month, None) if month is None: raise ParseException(f'Bad monthName expression: {ctx.getText()}') - except Exception: - raise ParseException(f'Bad monthName expression: {ctx.getText()}') + except Exception as e: + raise ParseException(f'Bad monthName expression: {ctx.getText()}') from e else: self.context['month'] = month def exitMonthNumber(self, ctx: dateparse_utilsParser.MonthNumberContext) -> None: try: - month = self._get_int(ctx.getText()) + month = DateParser._get_int(ctx.getText()) if month < 1 or month > 12: raise ParseException(f'Bad monthNumber expression: {ctx.getText()}') - except Exception: - raise ParseException(f'Bad monthNumber expression: {ctx.getText()}') + except Exception as e: + raise ParseException(f'Bad monthNumber expression: {ctx.getText()}') from e else: self.context['month'] = month def exitYear(self, ctx: dateparse_utilsParser.YearContext) -> None: try: - year = self._get_int(ctx.getText()) + year = DateParser._get_int(ctx.getText()) if year < 1: raise ParseException(f'Bad year expression: {ctx.getText()}') - except Exception: - raise ParseException(f'Bad year expression: {ctx.getText()}') + except Exception as e: + raise ParseException(f'Bad year expression: {ctx.getText()}') from e else: self.saw_overt_year = True self.context['year'] = year @@ -794,8 +796,10 @@ class DateParser(dateparse_utilsListener): try: special = ctx.specialDate().getText().lower() self.context['special'] = special - except Exception: - raise ParseException(f'Bad specialDate expression: {ctx.specialDate().getText()}') + except Exception as e: + raise ParseException( + f'Bad specialDate expression: {ctx.specialDate().getText()}' + ) from e try: mod = ctx.thisNextLast() if mod is not None: @@ -805,19 +809,19 @@ class DateParser(dateparse_utilsListener): self.context['special_next_last'] = 'next' elif mod.LAST() is not None: self.context['special_next_last'] = 'last' - except Exception: - raise ParseException(f'Bad specialDateNextLast expression: {ctx.getText()}') + except Exception as e: + raise ParseException(f'Bad specialDateNextLast expression: {ctx.getText()}') from e def exitNFoosFromTodayAgoExpr( self, ctx: dateparse_utilsParser.NFoosFromTodayAgoExprContext ) -> None: d = self.now_datetime try: - count = self._get_int(ctx.unsignedInt().getText()) + count = DateParser._get_int(ctx.unsignedInt().getText()) unit = ctx.deltaUnit().getText().lower() ago_from_now = ctx.AGO_FROM_NOW().getText() - except Exception: - raise ParseException(f'Bad NFoosFromTodayAgoExpr: {ctx.getText()}') + except Exception as e: + raise ParseException(f'Bad NFoosFromTodayAgoExpr: {ctx.getText()}') from e if "ago" in ago_from_now or "back" in ago_from_now: count = -count @@ -872,8 +876,8 @@ class DateParser(dateparse_utilsListener): count = +1 else: raise ParseException(f'Bad This/Next/Last modifier: {mod}') - except Exception: - raise ParseException(f'Bad DeltaRelativeToTodayExpr: {ctx.getText()}') + except Exception as e: + raise ParseException(f'Bad DeltaRelativeToTodayExpr: {ctx.getText()}') from e d = n_timeunits_from_base(count, TimeUnit(unit), d) self.context['year'] = d.year self.context['month'] = d.month @@ -882,10 +886,10 @@ class DateParser(dateparse_utilsListener): def exitSpecialTimeExpr(self, ctx: dateparse_utilsParser.SpecialTimeExprContext) -> None: try: txt = ctx.specialTime().getText().lower() - except Exception: - raise ParseException(f'Bad special time expression: {ctx.getText()}') + except Exception as e: + raise ParseException(f'Bad special time expression: {ctx.getText()}') from e else: - if txt == 'noon' or txt == 'midday': + if txt in ('noon', 'midday'): self.context['hour'] = 12 self.context['minute'] = 0 self.context['seconds'] = 0 @@ -900,7 +904,7 @@ class DateParser(dateparse_utilsListener): try: tz = ctx.tzExpr().getText() - self.context['tz'] = self._parse_tz(tz) + self.context['tz'] = DateParser._parse_tz(tz) except Exception: pass @@ -909,14 +913,14 @@ class DateParser(dateparse_utilsListener): hour = ctx.hour().getText() while not hour[-1].isdigit(): hour = hour[:-1] - hour = self._get_int(hour) - except Exception: - raise ParseException(f'Bad hour: {ctx.hour().getText()}') + hour = DateParser._get_int(hour) + except Exception as e: + raise ParseException(f'Bad hour: {ctx.hour().getText()}') from e if hour <= 0 or hour > 12: raise ParseException(f'Bad hour (out of range): {hour}') try: - minute = self._get_int(ctx.minute().getText()) + minute = DateParser._get_int(ctx.minute().getText()) except Exception: minute = 0 if minute < 0 or minute > 59: @@ -924,7 +928,7 @@ class DateParser(dateparse_utilsListener): self.context['minute'] = minute try: - seconds = self._get_int(ctx.second().getText()) + seconds = DateParser._get_int(ctx.second().getText()) except Exception: seconds = 0 if seconds < 0 or seconds > 59: @@ -932,7 +936,7 @@ class DateParser(dateparse_utilsListener): self.context['seconds'] = seconds try: - micros = self._get_int(ctx.micros().getText()) + micros = DateParser._get_int(ctx.micros().getText()) except Exception: micros = 0 if micros < 0 or micros > 1000000: @@ -941,8 +945,8 @@ class DateParser(dateparse_utilsListener): try: ampm = ctx.ampm().getText() - except Exception: - raise ParseException(f'Bad ampm: {ctx.ampm().getText()}') + except Exception as e: + raise ParseException(f'Bad ampm: {ctx.ampm().getText()}') from e if hour == 12: hour = 0 if ampm[0] == 'p': @@ -951,7 +955,7 @@ class DateParser(dateparse_utilsListener): try: tz = ctx.tzExpr().getText() - self.context['tz'] = self._parse_tz(tz) + self.context['tz'] = DateParser._parse_tz(tz) except Exception: pass @@ -962,15 +966,15 @@ class DateParser(dateparse_utilsListener): hour = ctx.hour().getText() while not hour[-1].isdigit(): hour = hour[:-1] - hour = self._get_int(hour) - except Exception: - raise ParseException(f'Bad hour: {ctx.hour().getText()}') + hour = DateParser._get_int(hour) + except Exception as e: + raise ParseException(f'Bad hour: {ctx.hour().getText()}') from e if hour < 0 or hour > 23: raise ParseException(f'Bad hour (out of range): {hour}') self.context['hour'] = hour try: - minute = self._get_int(ctx.minute().getText()) + minute = DateParser._get_int(ctx.minute().getText()) except Exception: minute = 0 if minute < 0 or minute > 59: @@ -978,7 +982,7 @@ class DateParser(dateparse_utilsListener): self.context['minute'] = minute try: - seconds = self._get_int(ctx.second().getText()) + seconds = DateParser._get_int(ctx.second().getText()) except Exception: seconds = 0 if seconds < 0 or seconds > 59: @@ -986,7 +990,7 @@ class DateParser(dateparse_utilsListener): self.context['seconds'] = seconds try: - micros = self._get_int(ctx.micros().getText()) + micros = DateParser._get_int(ctx.micros().getText()) except Exception: micros = 0 if micros < 0 or micros >= 1000000: @@ -995,7 +999,7 @@ class DateParser(dateparse_utilsListener): try: tz = ctx.tzExpr().getText() - self.context['tz'] = self._parse_tz(tz) + self.context['tz'] = DateParser._parse_tz(tz) except Exception: pass