#!/usr/bin/env python3
+"""
+Parse dates in a variety of formats.
+
+"""
+
import datetime
import functools
import holidays # type: ignore
import acl
import bootstrap
-import datetime_utils
-import decorator_utils
+from datetime_utils import (
+ TimeUnit, n_timeunits_from_base, datetime_to_date, date_to_datetime
+)
from dateparse.dateparse_utilsLexer import dateparse_utilsLexer # type: ignore
from dateparse.dateparse_utilsListener import dateparse_utilsListener # type: ignore
from dateparse.dateparse_utilsParser import dateparse_utilsParser # type: ignore
+import decorator_utils
logger = logging.getLogger(__name__)
class ParseException(Exception):
"""An exception thrown during parsing because of unrecognized input."""
def __init__(self, message: str) -> None:
- logger.error(message)
self.message = message
def syntaxError(
self, recognizer, offendingSymbol, line, column, msg, e
):
- logger.error(msg)
raise ParseException(msg)
def reportAmbiguity(
'enter*',
'exit*',
],
- denied_patterns=None,
- order_to_check_allow_deny=acl.ACL_ORDER_DENY_ALLOW,
+ denied_patterns=[
+ 'enterEveryRule',
+ 'exitEveryRule'
+ ],
+ order_to_check_allow_deny=acl.Order.DENY_ALLOW,
default_answer=False
)
)
# These TimeUnits are defined in datetime_utils and are used as params
# to datetime_utils.n_timeunits_from_base.
self.time_delta_unit_to_constant = {
- 'hou': datetime_utils.TimeUnit.HOURS,
- 'min': datetime_utils.TimeUnit.MINUTES,
- 'sec': datetime_utils.TimeUnit.SECONDS,
+ 'hou': TimeUnit.HOURS,
+ 'min': TimeUnit.MINUTES,
+ 'sec': TimeUnit.SECONDS,
}
self.delta_unit_to_constant = {
- 'day': datetime_utils.TimeUnit.DAYS,
- 'wor': datetime_utils.TimeUnit.WORKDAYS,
- 'wee': datetime_utils.TimeUnit.WEEKS,
- 'mon': datetime_utils.TimeUnit.MONTHS,
- 'yea': datetime_utils.TimeUnit.YEARS,
+ 'day': TimeUnit.DAYS,
+ 'wor': TimeUnit.WORKDAYS,
+ 'wee': TimeUnit.WEEKS,
+ 'mon': TimeUnit.MONTHS,
+ 'yea': TimeUnit.YEARS,
}
self.override_now_for_test_purposes = override_now_for_test_purposes
self._reset()
This is the main entrypoint to this class for caller code.
"""
+ date_string = date_string.strip()
+ date_string = re.sub('\s+', ' ', date_string)
self._reset()
listener = RaisingErrorListener()
input_stream = antlr4.InputStream(date_string)
self.today = datetime.date.today()
else:
self.now_datetime = self.override_now_for_test_purposes
- self.today = datetime_utils.datetime_to_date(
+ self.today = datetime_to_date(
self.override_now_for_test_purposes
)
self.date: Optional[datetime.date] = None
self.datetime: Optional[datetime.datetime] = None
self.context: Dict[str, Any] = {}
self.timedelta = datetime.timedelta(seconds=0)
+ self.saw_overt_year = False
@staticmethod
def _normalize_special_day_name(name: str) -> str:
name = name.replace('washi', 'presi')
return name
- def _figure_out_date_unit(self, orig: str) -> int:
+ def _figure_out_date_unit(self, orig: str) -> TimeUnit:
"""Figure out what unit a date expression piece is talking about."""
if 'month' in orig:
- return datetime_utils.TimeUnit.MONTHS
+ return TimeUnit.MONTHS
txt = orig.lower()[:3]
if txt in self.day_name_to_number:
- return(self.day_name_to_number[txt])
+ return(TimeUnit(self.day_name_to_number[txt]))
elif txt in self.delta_unit_to_constant:
- return(self.delta_unit_to_constant[txt])
+ return(TimeUnit(self.delta_unit_to_constant[txt]))
raise ParseException(f'Invalid date unit: {orig}')
def _figure_out_time_unit(self, orig: str) -> int:
next_last = self.context.get('special_next_last', '')
if next_last == 'next':
year += 1
+ self.saw_overt_year = True
elif next_last == 'last':
year -= 1
+ self.saw_overt_year = True
# Holiday names
if name == 'easte':
raise ParseException('Missing day')
if 'year' not in self.context:
self.context['year'] = self.today.year
+ self.saw_overt_year = False
+ else:
+ self.saw_overt_year = True
# Handling "ides" and "nones" requires both the day and month.
if (
tz = pytz.timezone(txt)
if tz is not None:
return tz
- except:
+ except Exception:
pass
# Try dateutil
tz = dateutil.tz.gettz(txt)
if tz is not None:
return tz
- except:
+ except Exception:
pass
# Try constructing an offset in seconds
offset = sign * (hour * 60 * 60) + sign * (minute * 60)
tzoffset = dateutil.tz.tzoffset(txt, offset)
return tzoffset
- except:
+ except Exception:
pass
return None
if 'delta_unit' not in self.context:
raise ParseException('Missing delta_unit?!')
unit = self.context['delta_unit']
- dt = datetime_utils.n_timeunits_from_base(
+ dt = n_timeunits_from_base(
count,
- unit,
- datetime_utils.date_to_datetime(self.date)
+ TimeUnit(unit),
+ date_to_datetime(self.date)
)
- self.date = datetime_utils.datetime_to_date(dt)
+ self.date = datetime_to_date(dt)
def exitTimeExpr(self, ctx: dateparse_utilsParser.TimeExprContext) -> None:
# Simple time?
self.timedelta += datetime.timedelta(minutes=count)
else:
unit = self.context['time_delta_unit']
- if unit == datetime_utils.TimeUnit.SECONDS:
+ if unit == TimeUnit.SECONDS:
self.timedelta += datetime.timedelta(seconds=count)
- elif unit == datetime_utils.TimeUnit.MINUTES:
+ elif unit == TimeUnit.MINUTES:
self.timedelta = datetime.timedelta(minutes=count)
- elif unit == datetime_utils.TimeUnit.HOURS:
+ elif unit == TimeUnit.HOURS:
self.timedelta = datetime.timedelta(hours=count)
else:
raise ParseException()
unit = self._figure_out_date_unit(
ctx.deltaUnit().getText().lower()
)
- except:
+ except Exception:
raise ParseException(f'Invalid Delta +/-: {ctx.getText()}')
else:
self.context['delta_int'] = n
) -> None:
try:
unit = self._figure_out_date_unit(ctx.getText().lower())
- except:
+ except Exception:
raise ParseException(f'Bad delta unit: {ctx.getText()}')
else:
self.context['delta_unit'] = unit
) -> None:
try:
txt = ctx.getText().lower()
- except:
+ except Exception:
raise ParseException(f'Bad next/last: {ctx.getText()}')
if (
'month' in self.context or
self.context['day'] = self.now_datetime.day
self.context['month'] = self.now_datetime.month
self.context['year'] = self.now_datetime.year
+ self.saw_overt_year = True
elif txt[:4] == 'last':
self.context['delta_int'] = -1
self.context['day'] = self.now_datetime.day
self.context['month'] = self.now_datetime.month
self.context['year'] = self.now_datetime.year
+ self.saw_overt_year = True
else:
raise ParseException(f'Bad next/last: {ctx.getText()}')
ctx.deltaTimeUnit().getText().lower()
)
self.context['time_delta_unit'] = unit
- except:
+ except Exception:
raise ParseException(f'Bad delta unit: {ctx.getText()}')
if 'time_delta_before_after' not in self.context:
raise ParseException(
self.context['time_delta_int'] = 15
self.context[
'time_delta_unit'
- ] = datetime_utils.TimeUnit.MINUTES
+ ] = TimeUnit.MINUTES
elif txt == 'half':
self.context['time_delta_int'] = 30
self.context[
'time_delta_unit'
- ] = datetime_utils.TimeUnit.MINUTES
+ ] = TimeUnit.MINUTES
else:
raise ParseException(f'Bad time fraction {ctx.getText()}')
- except:
+ except Exception:
raise ParseException(f'Bad time fraction {ctx.getText()}')
def exitDeltaBeforeAfter(
) -> None:
try:
txt = ctx.getText().lower()
- except:
+ except Exception:
raise ParseException(f'Bad delta before|after: {ctx.getText()}')
else:
self.context['delta_before_after'] = txt
) -> None:
try:
txt = ctx.getText().lower()
- except:
+ except Exception:
raise ParseException(f'Bad delta before|after: {ctx.getText()}')
else:
self.context['time_delta_before_after'] = txt
self.context['month'] = month
self.context['day'] = 1
self.main_type = DateParser.PARSE_TYPE_BASE_AND_OFFSET_EXPR
- except:
+ except Exception:
raise ParseException(
f'Invalid nthWeekday expression: {ctx.getText()}'
)
def exitNth(self, ctx: dateparse_utilsParser.NthContext) -> None:
try:
i = self._get_int(ctx.getText())
- except:
+ except Exception:
raise ParseException(f'Bad nth expression: {ctx.getText()}')
else:
self.context['nth'] = i
raise ParseException(
f'Bad first|last expression: {ctx.getText()}'
)
- except:
+ except Exception:
raise ParseException(f'Bad first|last expression: {ctx.getText()}')
else:
self.context['nth'] = txt
try:
dow = ctx.getText().lower()[:3]
dow = self.day_name_to_number.get(dow, None)
- except:
+ except Exception:
raise ParseException('Bad day of week')
else:
self.context['dow'] = dow
raise ParseException(
f'Bad dayOfMonth expression: {ctx.getText()}'
)
- except:
+ except Exception:
raise ParseException(f'Bad dayOfMonth expression: {ctx.getText()}')
self.context['day'] = day
raise ParseException(
f'Bad monthName expression: {ctx.getText()}'
)
- except:
+ except Exception:
raise ParseException(f'Bad monthName expression: {ctx.getText()}')
else:
self.context['month'] = month
raise ParseException(
f'Bad monthNumber expression: {ctx.getText()}'
)
- except:
+ except Exception:
raise ParseException(
f'Bad monthNumber expression: {ctx.getText()}'
)
year = self._get_int(ctx.getText())
if year < 1:
raise ParseException(f'Bad year expression: {ctx.getText()}')
- except:
+ except Exception:
raise ParseException(f'Bad year expression: {ctx.getText()}')
else:
+ self.saw_overt_year = True
self.context['year'] = year
def exitSpecialDateMaybeYearExpr(
try:
special = ctx.specialDate().getText().lower()
self.context['special'] = special
- except:
+ except Exception:
raise ParseException(
f'Bad specialDate expression: {ctx.specialDate().getText()}'
)
self.context['special_next_last'] = 'next'
elif mod.LAST() is not None:
self.context['special_next_last'] = 'last'
- except:
+ except Exception:
raise ParseException(
f'Bad specialDateNextLast expression: {ctx.getText()}'
)
count = self._get_int(ctx.unsignedInt().getText())
unit = ctx.deltaUnit().getText().lower()
ago_from_now = ctx.AGO_FROM_NOW().getText()
- except:
+ except Exception:
raise ParseException(
f'Bad NFoosFromTodayAgoExpr: {ctx.getText()}'
)
count = -count
unit = self._figure_out_date_unit(unit)
- d = datetime_utils.n_timeunits_from_base(
+ d = n_timeunits_from_base(
count,
- unit,
+ TimeUnit(unit),
d)
self.context['year'] = d.year
self.context['month'] = d.month
f'Bad This/Next/Last modifier: {mod}'
)
unit = ctx.deltaUnit().getText().lower()
- except:
+ except Exception:
raise ParseException(
f'Bad DeltaRelativeToTodayExpr: {ctx.getText()}'
)
unit = self._figure_out_date_unit(unit)
- d = datetime_utils.n_timeunits_from_base(
+ d = n_timeunits_from_base(
count,
- unit,
+ TimeUnit(unit),
d)
self.context['year'] = d.year
self.context['month'] = d.month
) -> None:
try:
txt = ctx.specialTime().getText().lower()
- except:
+ except Exception:
raise ParseException(
f'Bad special time expression: {ctx.getText()}'
)
try:
tz = ctx.tzExpr().getText()
self.context['tz'] = self._parse_tz(tz)
- except:
+ except Exception:
pass
def exitTwelveHourTimeExpr(
while not hour[-1].isdigit():
hour = hour[:-1]
hour = self._get_int(hour)
- except:
+ except Exception:
raise ParseException(f'Bad hour: {ctx.hour().getText()}')
if hour <= 0 or hour > 12:
raise ParseException(f'Bad hour (out of range): {hour}')
try:
minute = self._get_int(ctx.minute().getText())
- except:
+ except Exception:
minute = 0
if minute < 0 or minute > 59:
raise ParseException(f'Bad minute (out of range): {minute}')
try:
seconds = self._get_int(ctx.second().getText())
- except:
+ except Exception:
seconds = 0
if seconds < 0 or seconds > 59:
raise ParseException(f'Bad second (out of range): {seconds}')
try:
micros = self._get_int(ctx.micros().getText())
- except:
+ except Exception:
micros = 0
if micros < 0 or micros > 1000000:
raise ParseException(f'Bad micros (out of range): {micros}')
try:
ampm = ctx.ampm().getText()
- except:
+ except Exception:
raise ParseException(f'Bad ampm: {ctx.ampm().getText()}')
if hour == 12:
hour = 0
try:
tz = ctx.tzExpr().getText()
self.context['tz'] = self._parse_tz(tz)
- except:
+ except Exception:
pass
def exitTwentyFourHourTimeExpr(
while not hour[-1].isdigit():
hour = hour[:-1]
hour = self._get_int(hour)
- except:
+ except Exception:
raise ParseException(f'Bad hour: {ctx.hour().getText()}')
if hour < 0 or hour > 23:
raise ParseException(f'Bad hour (out of range): {hour}')
try:
minute = self._get_int(ctx.minute().getText())
- except:
+ except Exception:
minute = 0
if minute < 0 or minute > 59:
raise ParseException(f'Bad minute (out of range): {ctx.getText()}')
try:
seconds = self._get_int(ctx.second().getText())
- except:
+ except Exception:
seconds = 0
if seconds < 0 or seconds > 59:
raise ParseException(f'Bad second (out of range): {ctx.getText()}')
try:
micros = self._get_int(ctx.micros().getText())
- except:
+ except Exception:
micros = 0
if micros < 0 or micros >= 1000000:
raise ParseException(f'Bad micros (out of range): {ctx.getText()}')
try:
tz = ctx.tzExpr().getText()
self.context['tz'] = self._parse_tz(tz)
- except:
+ except Exception:
pass
def main() -> None:
parser = DateParser()
for line in sys.stdin:
if __name__ == "__main__":
- main = bootstrap.initialize(main)
main()