HTML 77.2%
TypeScript 10.5%
Python 9.6%
JavaScript 2.5%
1"""Dates in the wild → aware datetimes (UTC). Partial dates ('March 2025', '2024') keep a precision marker."""2from __future__ import annotations34import re5from datetime import UTC, date, datetime67from dateutil import parser as duparser89_MONTHS = "january|february|march|april|may|june|july|august|september|october|november|december|jan|feb|mar|apr|jun|jul|aug|sep|sept|oct|nov|dec"10_MONTH_YEAR = re.compile(rf"\b({_MONTHS})\.?\s+(\d{{4}})\b", re.IGNORECASE)11_YEAR = re.compile(r"\b(19[89]\d|20[0-4]\d)\b")121314def parse_datetime(value: str | datetime | date | None, *, default_tz=UTC) -> datetime | None:15 if value is None:16 return None17 if isinstance(value, datetime):18 return value if value.tzinfo else value.replace(tzinfo=default_tz)19 if isinstance(value, date):20 return datetime(value.year, value.month, value.day, tzinfo=default_tz)21 s = str(value).strip()22 if not s:23 return None24 if re.fullmatch(r"\d{10}(\.\d+)?", s):25 return datetime.fromtimestamp(float(s), tz=UTC)26 if re.fullmatch(r"\d{13}", s):27 return datetime.fromtimestamp(int(s) / 1000, tz=UTC)28 try:29 dt = duparser.parse(s, fuzzy=False)30 return dt if dt.tzinfo else dt.replace(tzinfo=default_tz)31 except (ValueError, OverflowError, TypeError):32 pass33 return None343536def parse_date(value: str | None) -> tuple[date | None, str]:37 """Return (date, precision) where precision ∈ {'day','month','year','none'}. Fuzzy text allowed."""38 if not value:39 return None, "none"40 s = str(value).strip()41 dt = parse_datetime(s)42 if dt and re.search(r"\d{1,2}", s) and not _MONTH_YEAR.fullmatch(s) and not _YEAR.fullmatch(s):43 return dt.date(), "day"44 m = _MONTH_YEAR.search(s)45 if m:46 try:47 d = duparser.parse(f"1 {m.group(1)} {m.group(2)}")48 return d.date(), "month"49 except ValueError:50 pass51 m = _YEAR.search(s)52 if m:53 return date(int(m.group(1)), 1, 1), "year"54 try:55 d = duparser.parse(s, fuzzy=True, default=datetime(1900, 1, 1))56 if d.year > 1900:57 return d.date(), "day"58 except (ValueError, OverflowError):59 pass60 return None, "none"616263def iso(dt: datetime | date | None) -> str | None:64 if dt is None:65 return None66 if isinstance(dt, datetime):67 return dt.astimezone(UTC).isoformat(timespec="seconds")68 return dt.isoformat()697071__all__ = ["iso", "parse_date", "parse_datetime"]72