Spaces:
Running
Running
| """ | |
| 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 | |