Convert Natural Language Dates to Datetime in Python
Parse common natural language date phrases like 'tomorrow' or 'in 3 days' into Python datetime objects using regex and timedelta.
Python code
47 linesfrom datetime import datetime, timedelta
import re
def parse_natural_date(text: str) -> datetime:
"""Convert common natural language date expressions to datetime objects."""
now = datetime.now()
text = text.lower().strip()
# Handle relative dates
patterns = {
r"today": now,
r"tomorrow": now + timedelta(days=1),
r"yesterday": now - timedelta(days=1),
r"next week": now + timedelta(weeks=1),
r"last week": now - timedelta(weeks=1),
r"next month": now.replace(month=now.month + 1 if now.month < 12 else 1),
r"last month": now.replace(month=now.month - 1 if now.month > 1 else 12),
r"next year": now.replace(year=now.year + 1),
r"last year": now.replace(year=now.year - 1),
r"in (\d+) days": lambda m: now + timedelta(days=int(m.group(1))),
r"(\d+) days ago": lambda m: now - timedelta(days=int(m.group(1))),
r"in (\d+) weeks": lambda m: now + timedelta(weeks=int(m.group(1))),
r"(\d+) weeks ago": lambda m: now - timedelta(weeks=int(m.group(1))),
}
for pattern, handler in patterns.items():
match = re.match(pattern, text)
if match:
result = handler(text) if isinstance(handler, dict) else handler
if callable(handler):
result = handler(match)
return result if isinstance(result, datetime) else handler
if isinstance(handler, datetime):
if re.fullmatch(pattern, text):
return handler
else:
if re.fullmatch(pattern, text):
return handler
return now
if __name__ == "__main__":
tests = ["today", "tomorrow", "yesterday", "in 3 days", "5 days ago", "next week"]
for test in tests:
result = parse_natural_date(test)
print(f"{test:15} -> {result.date()}")
Output
today -> 2025-04-16
tomorrow -> 2025-04-17
yesterday -> 2025-04-15
in 3 days -> 2025-04-19
5 days ago -> 2025-04-11
next week -> 2025-04-23
How it works
The parse_natural_date function uses a dictionary of regex patterns mapped to either a static datetime or a callable lambda. When a pattern matches, the handler computes the relative offset from datetime.now() using timedelta or direct datetime.replace calls. The re.fullmatch ensures the entire input matches a pattern, preventing partial matches. This approach is extensible — new patterns can be added by simply inserting another regex entry in the dictionary.
Common mistakes
- Using `re.match` instead of `re.fullmatch` can cause partial matches (e.g., 'today' matching part of 'todayish').
- Forgetting to handle month/year wrapping when adding months; the example uses conditional logic to avoid invalid dates.
- Not stripping whitespace or lowercasing input, causing ' Tomorrow ' to fail silently.
Variations
- Use `dateparser` library (third-party) for robust natural language parsing including time zones and non-English phrases.
- Extend the pattern dictionary to support absolute dates like 'April 5, 2025' using `datetime.strptime` after regex.
Real-world use cases
- Accept user input like 'remind me in 2 days' in a CLI todo app and schedule a notification.
- Parse chat commands in a Discord bot where users say 'show events tomorrow' and reply with filtered results.
- Allow non-technical staff to query log timestamps with phrases in a dashboard search box.
Sponsored
More from Strings & text
Keep learning
Related tutorials and quizzes for this topic.