Spaces:
Running
Running
| """DateTime tool implementation.""" | |
| from __future__ import annotations | |
| from dataclasses import dataclass, field | |
| from datetime import datetime, timedelta | |
| from typing import Any | |
| from zoneinfo import ZoneInfo | |
| from src.tools.base import Tool, ToolParameter, ToolResult | |
| from src.utils.logging import get_logger | |
| logger = get_logger(__name__) | |
| class DateTimeTool(Tool): | |
| """Tool for date and time operations.""" | |
| name: str = "datetime" | |
| description: str = "Perform date/time operations: get current time, convert timezones, calculate date differences, add/subtract durations." | |
| parameters: list[ToolParameter] = field(default_factory=lambda: [ | |
| ToolParameter( | |
| name="operation", | |
| type="string", | |
| description="The operation to perform", | |
| required=True, | |
| enum=["current", "convert_timezone", "date_diff", "add_duration", "format", "parse"], | |
| ), | |
| ToolParameter( | |
| name="timezone", | |
| type="string", | |
| description="Timezone for the operation (e.g., 'America/New_York', 'UTC', 'Asia/Tokyo')", | |
| required=False, | |
| default="UTC", | |
| ), | |
| ToolParameter( | |
| name="date", | |
| type="string", | |
| description="Date string for operations (ISO format: YYYY-MM-DD or YYYY-MM-DDTHH:MM:SS)", | |
| required=False, | |
| ), | |
| ToolParameter( | |
| name="date2", | |
| type="string", | |
| description="Second date for date_diff operation", | |
| required=False, | |
| ), | |
| ToolParameter( | |
| name="days", | |
| type="integer", | |
| description="Number of days for add_duration operation", | |
| required=False, | |
| default=0, | |
| ), | |
| ToolParameter( | |
| name="hours", | |
| type="integer", | |
| description="Number of hours for add_duration operation", | |
| required=False, | |
| default=0, | |
| ), | |
| ToolParameter( | |
| name="target_timezone", | |
| type="string", | |
| description="Target timezone for convert_timezone operation", | |
| required=False, | |
| ), | |
| ToolParameter( | |
| name="format_string", | |
| type="string", | |
| description="Format string for format operation (e.g., '%Y-%m-%d %H:%M')", | |
| required=False, | |
| default="%Y-%m-%d %H:%M:%S %Z", | |
| ), | |
| ]) | |
| async def execute(self, **kwargs: Any) -> ToolResult: | |
| """Execute a datetime operation. | |
| Args: | |
| operation: Operation to perform | |
| **kwargs: Operation-specific parameters | |
| Returns: | |
| ToolResult with datetime result | |
| """ | |
| operation = kwargs.get("operation", "current") | |
| try: | |
| if operation == "current": | |
| return await self._get_current(kwargs) | |
| elif operation == "convert_timezone": | |
| return await self._convert_timezone(kwargs) | |
| elif operation == "date_diff": | |
| return await self._date_diff(kwargs) | |
| elif operation == "add_duration": | |
| return await self._add_duration(kwargs) | |
| elif operation == "format": | |
| return await self._format_date(kwargs) | |
| elif operation == "parse": | |
| return await self._parse_date(kwargs) | |
| else: | |
| return ToolResult.fail(f"Unknown operation: {operation}") | |
| except Exception as e: | |
| logger.error(f"DateTime operation failed: {e}") | |
| return ToolResult.fail(f"DateTime operation failed: {e}") | |
| async def _get_current(self, kwargs: dict[str, Any]) -> ToolResult: | |
| """Get current date/time.""" | |
| timezone_str = kwargs.get("timezone", "UTC") | |
| try: | |
| tz = ZoneInfo(timezone_str) | |
| except Exception: | |
| return ToolResult.fail(f"Invalid timezone: {timezone_str}") | |
| now = datetime.now(tz) | |
| return ToolResult.ok({ | |
| "operation": "current", | |
| "timezone": timezone_str, | |
| "datetime": now.isoformat(), | |
| "date": now.strftime("%Y-%m-%d"), | |
| "time": now.strftime("%H:%M:%S"), | |
| "day_of_week": now.strftime("%A"), | |
| "unix_timestamp": int(now.timestamp()), | |
| }) | |
| async def _convert_timezone(self, kwargs: dict[str, Any]) -> ToolResult: | |
| """Convert datetime between timezones.""" | |
| date_str = kwargs.get("date") | |
| source_tz = kwargs.get("timezone", "UTC") | |
| target_tz = kwargs.get("target_timezone") | |
| if not date_str: | |
| return ToolResult.fail("Date is required for timezone conversion") | |
| if not target_tz: | |
| return ToolResult.fail("Target timezone is required") | |
| try: | |
| source = ZoneInfo(source_tz) | |
| target = ZoneInfo(target_tz) | |
| except Exception as e: | |
| return ToolResult.fail(f"Invalid timezone: {e}") | |
| # Parse the date | |
| dt = self._parse_datetime(date_str) | |
| if dt.tzinfo is None: | |
| dt = dt.replace(tzinfo=source) | |
| # Convert | |
| converted = dt.astimezone(target) | |
| return ToolResult.ok({ | |
| "operation": "convert_timezone", | |
| "source_timezone": source_tz, | |
| "target_timezone": target_tz, | |
| "original": dt.isoformat(), | |
| "converted": converted.isoformat(), | |
| }) | |
| async def _date_diff(self, kwargs: dict[str, Any]) -> ToolResult: | |
| """Calculate difference between two dates.""" | |
| date1_str = kwargs.get("date") | |
| date2_str = kwargs.get("date2") | |
| if not date1_str or not date2_str: | |
| return ToolResult.fail("Both dates are required for date_diff") | |
| dt1 = self._parse_datetime(date1_str) | |
| dt2 = self._parse_datetime(date2_str) | |
| diff = dt2 - dt1 | |
| total_seconds = diff.total_seconds() | |
| return ToolResult.ok({ | |
| "operation": "date_diff", | |
| "date1": dt1.isoformat(), | |
| "date2": dt2.isoformat(), | |
| "difference": { | |
| "days": diff.days, | |
| "total_seconds": total_seconds, | |
| "total_hours": total_seconds / 3600, | |
| "total_minutes": total_seconds / 60, | |
| "weeks": diff.days / 7, | |
| }, | |
| }) | |
| async def _add_duration(self, kwargs: dict[str, Any]) -> ToolResult: | |
| """Add a duration to a date.""" | |
| date_str = kwargs.get("date") | |
| days = kwargs.get("days", 0) | |
| hours = kwargs.get("hours", 0) | |
| timezone_str = kwargs.get("timezone", "UTC") | |
| if date_str: | |
| dt = self._parse_datetime(date_str) | |
| else: | |
| tz = ZoneInfo(timezone_str) | |
| dt = datetime.now(tz) | |
| duration = timedelta(days=days, hours=hours) | |
| result = dt + duration | |
| return ToolResult.ok({ | |
| "operation": "add_duration", | |
| "original": dt.isoformat(), | |
| "duration": {"days": days, "hours": hours}, | |
| "result": result.isoformat(), | |
| "result_date": result.strftime("%Y-%m-%d"), | |
| "result_day": result.strftime("%A"), | |
| }) | |
| async def _format_date(self, kwargs: dict[str, Any]) -> ToolResult: | |
| """Format a date with a custom format string.""" | |
| date_str = kwargs.get("date") | |
| format_string = kwargs.get("format_string", "%Y-%m-%d %H:%M:%S") | |
| timezone_str = kwargs.get("timezone", "UTC") | |
| if date_str: | |
| dt = self._parse_datetime(date_str) | |
| else: | |
| tz = ZoneInfo(timezone_str) | |
| dt = datetime.now(tz) | |
| formatted = dt.strftime(format_string) | |
| return ToolResult.ok({ | |
| "operation": "format", | |
| "datetime": dt.isoformat(), | |
| "format_string": format_string, | |
| "formatted": formatted, | |
| }) | |
| async def _parse_date(self, kwargs: dict[str, Any]) -> ToolResult: | |
| """Parse a date string into components.""" | |
| date_str = kwargs.get("date") | |
| if not date_str: | |
| return ToolResult.fail("Date string is required for parse") | |
| dt = self._parse_datetime(date_str) | |
| return ToolResult.ok({ | |
| "operation": "parse", | |
| "input": date_str, | |
| "parsed": { | |
| "year": dt.year, | |
| "month": dt.month, | |
| "day": dt.day, | |
| "hour": dt.hour, | |
| "minute": dt.minute, | |
| "second": dt.second, | |
| "day_of_week": dt.strftime("%A"), | |
| "iso_format": dt.isoformat(), | |
| }, | |
| }) | |
| def _parse_datetime(self, date_str: str) -> datetime: | |
| """Parse a datetime string in various formats. | |
| Args: | |
| date_str: Date string to parse | |
| Returns: | |
| Parsed datetime object | |
| """ | |
| formats = [ | |
| "%Y-%m-%dT%H:%M:%S%z", | |
| "%Y-%m-%dT%H:%M:%S", | |
| "%Y-%m-%d %H:%M:%S", | |
| "%Y-%m-%d", | |
| "%m/%d/%Y", | |
| "%d/%m/%Y", | |
| "%B %d, %Y", | |
| ] | |
| for fmt in formats: | |
| try: | |
| return datetime.strptime(date_str, fmt) | |
| except ValueError: | |
| continue | |
| # Try ISO format parsing | |
| try: | |
| return datetime.fromisoformat(date_str) | |
| except ValueError: | |
| pass | |
| raise ValueError(f"Could not parse date: {date_str}") | |