Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion datetimeparser/enums.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,8 +274,9 @@ class Keywords:
NEXT = Constant('next')
IN = Constant('in')
FOR = Constant('for')
PAST = Constant('past')

ALL = [OF, AFTER, BEFORE, NEXT, IN, FOR]
ALL = [OF, AFTER, BEFORE, NEXT, IN, FOR, PAST]


class Method:
Expand Down
54 changes: 34 additions & 20 deletions datetimeparser/parsermethods.py
Original file line number Diff line number Diff line change
Expand Up @@ -307,11 +307,8 @@ def parse(self, string: str) -> Optional[Tuple[MethodEnum, Tuple]]: # noqa: C90
return Method.CONSTANTS, (constant, RelativeDateTime(days=-1))
elif preposition in self.FUTURE_PREPOSITIONS:
if constant in DatetimeConstants.ALL:
if constant in DatetimeConstants.TIME:
return Method.CONSTANTS, (RelativeDatetimeHelper.from_keyword(constant, delta=1),)
elif constant in DatetimeConstants.DATE:
return Method.CONSTANTS, (RelativeDatetimeHelper.from_keyword(constant, delta=1),)
elif constant in [*WeekdayConstants.ALL, *Constants.ALL]:
return Method.CONSTANTS, (RelativeDatetimeHelper.from_keyword(constant, delta=1),)
elif constant in (*WeekdayConstants.ALL, *Constants.ALL, *DatetimeDeltaConstants.ALL):
return Method.CONSTANTS, (constant,)
else:
return None
Expand Down Expand Up @@ -637,10 +634,12 @@ class AbsolutePrepositionParser:
ABSOLUTE_PREPOSITION_TOKENS = (
Keywords.OF,
Keywords.AFTER,
Keywords.BEFORE
Keywords.BEFORE,
Keywords.PAST
)

RELATIVE_DATETIME_CONSTANTS = (*NumberCountConstants.ALL, *NumberConstants.ALL, *DatetimeConstants.ALL)
RELATIVE_TIME_CONSTANTS = DatetimeConstants.ALL

RELATIVE_DATA_SKIPPABLE_WORDS = (
"and",
Expand Down Expand Up @@ -691,7 +690,7 @@ def _split_data(self, string: str) -> Optional[Tuple[dict, dict, dict]]:

return {'type': 'relative', 'data': relative}, {'type': 'keyword', 'data': word}, {'type': 'absolute', 'data': absolute}

def _parse_relative_statement(self, relative_statement: str) -> Optional[List[Union[int, Constant]]]: # noqa: C901
def _parse_relative_statement(self, relative_statement: str, preposition: str) -> Optional[List[Union[int, Constant]]]: # noqa: C901
"""
Parses strings like "3 seconds, 2 minutes and 4 hours", "the fifth day", "4. week"
It resembles `relative_datetimes`
Expand Down Expand Up @@ -722,7 +721,7 @@ def _parse_relative_statement(self, relative_statement: str) -> Optional[List[Un
# '1st', '1.', 'first', ...
# 'one', 'two', 'three', ...
# 'seconds', 'minutes', 'hours', ...
for keyword in self.RELATIVE_DATETIME_CONSTANTS:
for keyword in self.RELATIVE_DATETIME_CONSTANTS if preposition != "past" else (*self.RELATIVE_DATETIME_CONSTANTS, self.RELATIVE_TIME_CONSTANTS):
for alias in keyword.get_all():
if alias == argument:
if keyword in DatetimeConstants.ALL:
Expand Down Expand Up @@ -800,25 +799,39 @@ def _parse_absolute_statement(self, data: Union[str, Tuple]) -> Optional:
"""

if isinstance(data, str):
# TODO: Call ConstantRelativeExtensionsParser as well
# Try constants (e.g. "(three days after) christmas")
constants_parser = ConstantsParser()
constants_parser.CONSTANT_KEYWORDS = (*Constants.ALL, *MonthConstants.ALL, *WeekdayConstants.ALL)
constants_parser.CONSTANT_KEYWORDS = (*Constants.ALL, *DatetimeDeltaConstants.ALL, *MonthConstants.ALL, *WeekdayConstants.ALL)
constants_parser.PREPOSITIONS = ("last", "next", "this", "previous")
constants_parser.PAST_PREPOSITIONS = ("last", "previous")
constants_parser.FUTURE_PREPOSITIONS = ("next", "this")
constants_parser.CUTOFF_KEYWORDS = ("the",)
constants_parser.CUTOFF_KEYWORDS = ("at the", "the", "at")

result = constants_parser.parse(data)

if result is None:
# If the result is None there may be just a normal year (e.g. "2018")
if parse_int(data) is not None:
return (AbsoluteDateTime(year=int(data)),)
else:
return None
else:
if result is not None:
# The first element is the Method signature (Method.CONSTANTS)
return result[1]

# If the result is None there may be just a normal year (e.g. "(three days after) 2018")
if parse_int(data) is not None:
return (AbsoluteDateTime(year=int(data)),)

# Try relative constants (e.g. "(2 hours after) daylight change yesterday")
constant_relative_extensions_parser = ConstantRelativeExtensionsParser()
result = constant_relative_extensions_parser.parse(data)

if result is not None:
return result[1]

# Try datetime delta constants (e.g. "(two quarters past) five")
datetime_delta_parser = DatetimeDeltaConstantsParser()
result = datetime_delta_parser.parse(data)

if result is not None:
return (result[1],)

return None
else:
return self._convert_tokens(data)

Expand All @@ -835,13 +848,14 @@ def _convert_tokens(self, tokens: Tuple[dict, dict, dict]) -> Optional[List[Unio
# Choose the right parsing method for the specific data types
for i, data_part in enumerate(tokens):
if data_part["type"] == "relative":
preposition = tokens[1]["data"]

# "3 hours and 4 minutes (after christmas)", "the 5th (of july)", ...
relative_data = self._parse_relative_statement(data_part["data"])
relative_data = self._parse_relative_statement(data_part["data"], preposition)

if relative_data is None:
return None

preposition = tokens[1]["data"]
relative_data = self._concatenate_relative_data(relative_data, preposition)

for relative_part_data in relative_data:
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
long_description_content_type="text/markdown",
long_description=long_description,
packages=['datetimeparser'],
version='0.8rc2', # version number: https://peps.python.org/pep-0440/
version='0.9rc1', # version number: https://peps.python.org/pep-0440/
license='MIT',
description='A parser library built for parsing the english language into datetime objects.',
author='Ari24',
Expand Down
144 changes: 75 additions & 69 deletions tests/testcases.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,32 @@
import datetime
from datetime import datetime
from dateutil.relativedelta import relativedelta


today = datetime.datetime.strptime(datetime.datetime.today().strftime("%Y-%m-%d %H:%M:%S"), "%Y-%m-%d %H:%M:%S")
today = datetime.strptime(datetime.today().strftime("%Y-%m-%d %H:%M:%S"), "%Y-%m-%d %H:%M:%S")

testcases = {
# Absolute datetime formats
"2017.08.03 03:04:05": datetime.datetime(year=2017, month=8, day=3, hour=3, minute=4, second=5),
"05.05.2017 03:04:10": datetime.datetime(year=2017, month=5, day=5, hour=3, minute=4, second=10),
"10.01.2022": datetime.datetime(year=2022, month=1, day=10),
"2023.01.10": datetime.datetime(year=2023, month=1, day=10),
"03:02:10": datetime.datetime(year=today.year, month=today.month, day=today.day, hour=3, minute=2, second=10),
"01.01.2023 05:03": datetime.datetime(year=2023, month=1, day=1, hour=5, minute=3),
"07:16": datetime.datetime(year=today.year, month=today.month, day=today.day, hour=7, minute=16),
"08:20": datetime.datetime(year=today.year, month=today.month, day=today.day, hour=8, minute=20),
"2017.08.03 03:04:05": datetime(year=2017, month=8, day=3, hour=3, minute=4, second=5),
"05.05.2017 03:04:10": datetime(year=2017, month=5, day=5, hour=3, minute=4, second=10),
"10.01.2022": datetime(year=2022, month=1, day=10),
"2023.01.10": datetime(year=2023, month=1, day=10),
"03:02:10": datetime(year=today.year, month=today.month, day=today.day, hour=3, minute=2, second=10),
"01.01.2023 05:03": datetime(year=2023, month=1, day=1, hour=5, minute=3),
"07:16": datetime(year=today.year, month=today.month, day=today.day, hour=7, minute=16),
"08:20": datetime(year=today.year, month=today.month, day=today.day, hour=8, minute=20),
# Absolute prepositions
"second day after christmas": datetime.datetime(year=today.year, month=12, day=27),
"3rd week of august": datetime.datetime(year=today.year, month=8, day=22),
"4. week of august": datetime.datetime(year=today.year, month=8, day=29),
"1st of august": datetime.datetime(year=today.year, month=8, day=1),
"fifth month of 2021": datetime.datetime(year=2021, month=5, day=1),
"three days after the fifth of august 2018": datetime.datetime(year=2018, month=8, day=8),
"second day after august": datetime.datetime(year=2022, month=8, day=3),
"3 months before the fifth week of august 2020": datetime.datetime(year=2020, month=5, day=31),
"10 days and 2 hours after 3 months before christmas 2020": datetime.datetime(year=2020, month=10, day=5, hour=2),
"a day and 3 minutes after 4 months before christmas 2021": datetime.datetime(year=2021, month=8, day=26, minute=3),
"3 minutes and 4 hours, 2 seconds after new years eve 2000": datetime.datetime(year=2000, month=12, day=31, hour=4, minute=3, second=2),
"2 days after christmas 2023": datetime.datetime(year=2023, month=12, day=27),
"second day after christmas": datetime(year=today.year, month=12, day=27),
"3rd week of august": datetime(year=today.year, month=8, day=22),
"4. week of august": datetime(year=today.year, month=8, day=29),
"1st of august": datetime(year=today.year, month=8, day=1),
"fifth month of 2021": datetime(year=2021, month=5, day=1),
"three days after the fifth of august 2018": datetime(year=2018, month=8, day=8),
"second day after august": datetime(year=2022, month=8, day=3),
"3 months before the fifth week of august 2020": datetime(year=2020, month=5, day=31),
"10 days and 2 hours after 3 months before christmas 2020": datetime(year=2020, month=10, day=5, hour=2),
"a day and 3 minutes after 4 months before christmas 2021": datetime(year=2021, month=8, day=26, minute=3),
"3 minutes and 4 hours, 2 seconds after new years eve 2000": datetime(year=2000, month=12, day=31, hour=4, minute=3, second=2),
"2 days after christmas 2023": datetime(year=2023, month=12, day=27),
# Infinity
"infinity": -1,
"inf": -1,
Expand All @@ -42,62 +42,68 @@
"next 2 years": today + relativedelta(years=2),
"last 4 years": today + relativedelta(years=-4),
"next three months": today + relativedelta(months=3),
"today": datetime.datetime(today.year, today.month, today.day),
"today": datetime(today.year, today.month, today.day),
"now": today,
# Constants
"next christmas": datetime.datetime(today.year, 12, 25),
"at the next christmas": datetime.datetime(today.year, 12, 25),
"the next christmas": datetime.datetime(today.year, 12, 25),
"christmas": datetime.datetime(today.year, 12, 25),
"new years eve": datetime.datetime(today.year, 12, 31),
"xmas 2025": datetime.datetime(2025, 12, 25),
"next christmas": datetime(today.year, 12, 25),
"at the next christmas": datetime(today.year, 12, 25),
"the next christmas": datetime(today.year, 12, 25),
"christmas": datetime(today.year, 12, 25),
"new years eve": datetime(today.year, 12, 31),
"xmas 2025": datetime(2025, 12, 25),
"next friday": None,
"next second": today + relativedelta(seconds=1),
"last month": today + relativedelta(months=-1),
"next hour": today + relativedelta(hours=1),
"next year": today + relativedelta(years=1),
"eastern 2010": datetime.datetime(2010, 4, 4),
"halloween 2030": datetime.datetime(2030, 10, 31),
"next april fools day": datetime.datetime(today.year, 4, 1),
"thanksgiving": datetime.datetime(year=today.year, month=11, day=24),
"next st patricks day": datetime.datetime(year=2023, month=3, day=17),
"valentine day 2010": datetime.datetime(2010, 2, 14),
"summer": datetime.datetime(today.year, 6, 1),
"winter 2021": datetime.datetime(2021, 12, 1),
"next spring": datetime.datetime(2023, 3, 1),
"begin of fall 2010": datetime.datetime(2010, 9, 1),
"summer end": datetime.datetime(today.year, 8, 31, 23, 59, 59),
"end of winter": datetime.datetime(2023, 2, 28, 23, 59, 59),
"end of the spring": datetime.datetime(today.year, 5, 31, 23, 59, 59),
"end of autumn 2020": datetime.datetime(2020, 11, 30, 23, 59, 59),
"begin of advent of code 2022": datetime.datetime(2022, 12, 1, 6),
"end of aoc 2022": datetime.datetime(2022, 12, 26, 6),
"end of the year": datetime.datetime(today.year, 12, 31, 23, 59, 59),
"eastern 2010": datetime(2010, 4, 4),
"halloween 2030": datetime(2030, 10, 31),
"next april fools day": datetime(today.year, 4, 1),
"thanksgiving": datetime(year=today.year, month=11, day=24),
"next st patricks day": datetime(year=2023, month=3, day=17),
"valentine day 2010": datetime(2010, 2, 14),
"summer": datetime(today.year, 6, 1),
"winter 2021": datetime(2021, 12, 1),
"next spring": datetime(2023, 3, 1),
"begin of fall 2010": datetime(2010, 9, 1),
"summer end": datetime(today.year, 8, 31, 23, 59, 59),
"end of winter": datetime(2023, 2, 28, 23, 59, 59),
"end of the spring": datetime(today.year, 5, 31, 23, 59, 59),
"end of autumn 2020": datetime(2020, 11, 30, 23, 59, 59),
"begin of advent of code 2022": datetime(2022, 12, 1, 6),
"end of aoc 2022": datetime(2022, 12, 26, 6),
"end of the year": datetime(today.year, 12, 31, 23, 59, 59),
# Constant Relative Extensions
"daylight change tomorrow": datetime.datetime(today.year, today.month, today.day, 6) + relativedelta(days=1),
"daylight change yesterday": datetime.datetime(today.year, today.month, today.day, 6) + relativedelta(days=-1),
"daylight change 01.01.2022": datetime.datetime(2022, 1, 1, 6),
"daylight change tomorrow": datetime(today.year, today.month, today.day, 6) + relativedelta(days=1),
"daylight change yesterday": datetime(today.year, today.month, today.day, 6) + relativedelta(days=-1),
"daylight change 01.01.2022": datetime(2022, 1, 1, 6),
"monday in two weeks": None,
"tuesday in two years": None,
"tomorrow 12 o'clock": datetime.datetime(today.year, today.month, today.day, 12) + relativedelta(days=1),
"tomorrow at 7pm": datetime.datetime(today.year, today.month, today.day, 19) + relativedelta(days=1),
"tomorrow at 17h": datetime.datetime(today.year, today.month, today.day, 17) + relativedelta(days=1),
"tomorrow at 17:13": datetime.datetime(today.year, today.month, today.day, 17, 13) + relativedelta(days=1),
"tomorrow at 17": datetime.datetime(today.year, today.month, today.day, 17) + relativedelta(days=1),
"tomorrow afternoon": datetime.datetime(today.year, today.month, today.day, 15) + relativedelta(days=1),
"at the next afternoon at tomorrow": datetime.datetime(today.year, today.month, today.day, 15) + relativedelta(days=1),
"the day before yesterday": datetime.datetime(today.year, today.month, today.day) + relativedelta(days=-2),
"the day after tomorrow": datetime.datetime(today.year, today.month, today.day) + relativedelta(days=2),
"a day before yesterday": datetime.datetime(today.year, today.month, today.day) + relativedelta(days=-2),
"a day after tomorrow": datetime.datetime(today.year, today.month, today.day) + relativedelta(days=2),
"day before yesterday": datetime.datetime(today.year, today.month, today.day) + relativedelta(days=-2),
"day after tomorrow": datetime.datetime(today.year, today.month, today.day) + relativedelta(days=2),
"two days after tomorrow": datetime.datetime(today.year, today.month, today.day) + relativedelta(days=3),
"two days before yesterday": datetime.datetime(today.year, today.month, today.day) + relativedelta(days=-3),

"tomorrow 12 o'clock": datetime(today.year, today.month, today.day, 12) + relativedelta(days=1),
"tomorrow at 7pm": datetime(today.year, today.month, today.day, 19) + relativedelta(days=1),
"tomorrow at 17h": datetime(today.year, today.month, today.day, 17) + relativedelta(days=1),
"tomorrow at 17:13": datetime(today.year, today.month, today.day, 17, 13) + relativedelta(days=1),
"tomorrow at 17": datetime(today.year, today.month, today.day, 17) + relativedelta(days=1),
"tomorrow afternoon": datetime(today.year, today.month, today.day, 15) + relativedelta(days=1),
"at the next afternoon at tomorrow": datetime(today.year, today.month, today.day, 15) + relativedelta(days=1),
"the day before yesterday": datetime(today.year, today.month, today.day) + relativedelta(days=-2),
"the day after tomorrow": datetime(today.year, today.month, today.day) + relativedelta(days=2),
"a day before yesterday": datetime(today.year, today.month, today.day) + relativedelta(days=-2),
"a day after tomorrow": datetime(today.year, today.month, today.day) + relativedelta(days=2),
"day before yesterday": datetime(today.year, today.month, today.day) + relativedelta(days=-2),
"day after tomorrow": datetime(today.year, today.month, today.day) + relativedelta(days=2),
"two days after tomorrow": datetime(today.year, today.month, today.day) + relativedelta(days=3),
"two days before yesterday": datetime(today.year, today.month, today.day) + relativedelta(days=-3),
# Datetime Delta
"at 9pm": datetime.datetime(today.year, today.month, today.day, 21),
"at 9:00pm": datetime.datetime(today.year, today.month, today.day, 21),
"at 10 in the evening": datetime.datetime(today.year, today.month, today.day, 22),
"5 in the morning": datetime.datetime(today.year, today.month, today.day, 5),
"at 9pm": datetime(today.year, today.month, today.day, 21),
"at 9:00pm": datetime(today.year, today.month, today.day, 21),
"at 10 in the evening": datetime(today.year, today.month, today.day, 22),
"5 in the morning": datetime(today.year, today.month, today.day, 5),
# GitHub issue #78
"quarter past 5pm": datetime(today.year, today.month, today.day, 17, 15, 0),
"two quarters past 5h": datetime(today.year, today.month, today.day, 17, 30, 0),
"one quarter before 10pm": datetime(today.year, today.month, today.day, 21, 45, 0),
"ten quarters after 03:01:10am": datetime(today.year, today.month, today.day, 5, 41, 10),
"hour past christmas": datetime(today.year, 12, 25, 1),
"30 minutes past easter": None
}