"""NormalizedDate module.""" from datetime import date, datetime from typing import Any from sqlalchemy.engine import Dialect from sqlalchemy.types import Date as SADate, TypeDecorator _FORMATS = [ '%Y/%m/%d', '%m/%d/%Y', '%d-%m-%Y', ] class NormalizedDate(TypeDecorator): """Normalize inputs to a date.""" impl = SADate cache_ok = True def process_bind_param(self, value: Any, dialect: Dialect) -> date | None: """Process params.""" return to_date(value) def process_result_value(self, value: Any, dialect: Dialect) -> date | None: """Process results.""" return to_date(value) def to_date(value: Any) -> date | None: """Normalize inputs to a date.""" # None / '' -> None if value is None or value == '': return None # datetime if isinstance(value, datetime): return value.date() # date if isinstance(value, date): return value # Epoch seconds if isinstance(value, (int, float)): return datetime.fromtimestamp(value).date() # bytes -> decode (utf-8) if isinstance(value, (bytes, bytearray)): value = value.decode('utf-8', errors='strict') # Convert to string s = str(value).strip() if not s: return None # Handle trailing 'Z' (UTC) if s.endswith(('Z', 'z')): s = s[:-1] + '+00:00' # ISO date-only ('YYYY-MM-DD') try: return date.fromisoformat(s) except ValueError: pass # ISO 8601 try: return datetime.fromisoformat(s).date() except ValueError: pass # Try fallback formats for fmt in _FORMATS: try: return datetime.strptime(s, fmt).date() except ValueError: continue raise TypeError(f'Unsupported date value: {value!r}')