SPB Git forge

spb/ai-atlas

Public
41commits 1branches 0releases
4.6 MBsize
maindefault branch
12 days agolast push
HTML 77.2% TypeScript 10.5% Python 9.6% JavaScript 2.5%
2.4 KB · 72 lines python
Raw Blame History
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