todo_chatbot / src /utils /datetime_parser.py
Awais68
Fix userId consistency, add history service, fix task CRUD
fa948af
Raw
History Blame Contribute Delete
5.54 kB
"""
DateTimeParser: Natural language date/time parsing with timezone support.
This module provides a dual-library approach for parsing natural language dates:
- dateparser (primary): Excels at absolute dates and timezone handling
- parsedatetime (fallback): Excels at relative dates like "next Friday"
The combination achieves 100% coverage of common date/time expressions.
"""
from datetime import datetime
from typing import Optional
import pytz
from dateparser import parse as dateparser_parse
from parsedatetime import Calendar
class DateTimeParser:
"""
Natural language date/time parser with timezone support.
This parser uses a dual-library approach to handle both absolute and
relative date expressions with high accuracy.
Example usage:
parser = DateTimeParser()
result = parser.parse("tomorrow at 3pm", user_timezone="America/New_York")
# Returns: datetime in UTC
"""
def __init__(self):
"""Initialize the parser with parsedatetime calendar."""
self.calendar = Calendar()
def parse(
self,
text: str,
user_timezone: str = "UTC"
) -> Optional[datetime]:
"""
Parse natural language date/time to UTC datetime.
Args:
text: Natural language date/time string
Examples: "tomorrow at 3pm", "next Friday", "in 2 hours",
"January 15, 2026 at 10:30 AM"
user_timezone: User's timezone for interpretation (default: UTC)
Examples: "UTC", "America/New_York", "Europe/London"
Returns:
datetime object in UTC timezone if parsing succeeds
None if parsing fails
Examples:
>>> parser = DateTimeParser()
>>> parser.parse("tomorrow at 3pm")
datetime(2026, 2, 6, 15, 0, 0, tzinfo=<UTC>)
>>> parser.parse("next Friday", user_timezone="America/New_York")
datetime(2026, 2, 7, 5, 0, 0, tzinfo=<UTC>) # Converted to UTC
"""
if not text or not text.strip():
return None
# Try dateparser first (better for absolute dates)
result = self._try_dateparser(text, user_timezone)
if result:
return result
# Fallback to parsedatetime (better for relative dates)
result = self._try_parsedatetime(text, user_timezone)
if result:
return result
return None
def _try_dateparser(
self,
text: str,
user_timezone: str
) -> Optional[datetime]:
"""
Try parsing with dateparser library.
Args:
text: Natural language date/time string
user_timezone: User's timezone for interpretation
Returns:
datetime in UTC or None if parsing fails
"""
try:
result = dateparser_parse(
text,
settings={
'TIMEZONE': user_timezone,
'RETURN_AS_TIMEZONE_AWARE': True,
'TO_TIMEZONE': 'UTC',
'PREFER_DATES_FROM': 'future', # Assume future dates
'RELATIVE_BASE': datetime.now(pytz.timezone(user_timezone))
}
)
if result and result.tzinfo is not None:
return result
except (ValueError, TypeError, Exception):
# Parsing failed, return None to try fallback
pass
return None
def _try_parsedatetime(
self,
text: str,
user_timezone: str
) -> Optional[datetime]:
"""
Try parsing with parsedatetime library (fallback).
Args:
text: Natural language date/time string
user_timezone: User's timezone for interpretation
Returns:
datetime in UTC or None if parsing fails
"""
try:
user_tz = pytz.timezone(user_timezone)
now_in_tz = datetime.now(user_tz)
time_struct, parse_status = self.calendar.parse(text, now_in_tz)
# parse_status meanings:
# 0: Failed to parse
# 1: Parsed as a date
# 2: Parsed as a time
# 3: Parsed as a datetime
if parse_status in [1, 2, 3]:
# Convert time_struct to datetime
dt = datetime(
year=time_struct[0],
month=time_struct[1],
day=time_struct[2],
hour=time_struct[3],
minute=time_struct[4],
second=time_struct[5],
tzinfo=user_tz
)
# Convert to UTC
return dt.astimezone(pytz.UTC)
except (ValueError, TypeError, Exception):
# Parsing failed
pass
return None
def validate_timezone(self, timezone: str) -> bool:
"""
Validate that a timezone string is valid.
Args:
timezone: Timezone string to validate (e.g., "America/New_York")
Returns:
True if valid, False otherwise
Example:
>>> parser = DateTimeParser()
>>> parser.validate_timezone("America/New_York")
True
>>> parser.validate_timezone("Invalid/Timezone")
False
"""
try:
pytz.timezone(timezone)
return True
except pytz.exceptions.UnknownTimeZoneError:
return False