""" 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=) >>> parser.parse("next Friday", user_timezone="America/New_York") datetime(2026, 2, 7, 5, 0, 0, tzinfo=) # 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