Spaces:
Running
Running
File size: 4,308 Bytes
a55ea57 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | import calendar
from datetime import datetime, timedelta, timezone
from typing import Optional
import re
_DATE_INPUT_FORMATS = (
"%d/%m/%Y",
"%Y-%m-%d",
"%Y/%m/%d",
"%d-%m-%Y",
"%B %d, %Y",
"%b %d, %Y",
"%B %d %Y",
"%b %d %Y",
"%d %B %Y",
"%d %b %Y",
"%B-%d-%Y",
"%b-%d-%Y",
"%Y-%m-%dT%H:%M:%S",
"%Y-%m-%dT%H:%M:%S%z",
"%Y-%m-%dT%H:%M:%S.%f",
"%Y-%m-%dT%H:%M:%S.%f%z",
"%Y-%m-%d %H:%M:%S",
"%Y-%m-%d %H:%M:%S%z",
"%Y-%m-%d %H:%M:%S.%f",
"%Y-%m-%d %H:%M:%S.%f%z",
"%I:%M %p on %d %B, %Y",
"%I:%M %p on %d %b, %Y",
)
_RELATIVE_PATTERNS = (
(re.compile(r"^yesterday$", re.I), lambda ref: ref - timedelta(days=1)),
(re.compile(r"^tomorrow$", re.I), lambda ref: ref + timedelta(days=1)),
(re.compile(r"^today$", re.I), lambda ref: ref),
(re.compile(r"^last\s+week$", re.I), lambda ref: ref - timedelta(days=7)),
(re.compile(r"^next\s+week$", re.I), lambda ref: ref + timedelta(days=7)),
(re.compile(r"^last\s+month$", re.I), lambda ref: _shift_months(ref, -1)),
(re.compile(r"^next\s+month$", re.I), lambda ref: _shift_months(ref, 1)),
(
re.compile(r"^(\d+)\s+days?\s+ago$", re.I),
lambda ref, m: ref - timedelta(days=int(m.group(1))),
),
(
re.compile(r"^in\s+(\d+)\s+days?$", re.I),
lambda ref, m: ref + timedelta(days=int(m.group(1))),
),
(
re.compile(r"^(\d+)\s+weeks?\s+ago$", re.I),
lambda ref, m: ref - timedelta(weeks=int(m.group(1))),
),
)
_WEEKDAY_NAMES = {
"monday": 0,
"tuesday": 1,
"wednesday": 2,
"thursday": 3,
"friday": 4,
"saturday": 5,
"sunday": 6,
}
def _shift_months(ref: datetime, delta: int) -> datetime:
month = ref.month - 1 + delta
year = ref.year + month // 12
month = month % 12 + 1
day = min(ref.day, [31, 29 if calendar.isleap(year) else 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1])
return ref.replace(year=year, month=month, day=day)
def to_naive_utc(value: datetime) -> datetime:
if value.tzinfo is None:
return value
return value.astimezone(timezone.utc).replace(tzinfo=None)
def parse_date_string(value: Optional[str]) -> Optional[datetime]:
if not value or not isinstance(value, str):
return None
cleaned = value.strip()
for fmt in _DATE_INPUT_FORMATS:
try:
parsed = datetime.strptime(cleaned, fmt)
except ValueError:
continue
return to_naive_utc(parsed)
return None
def normalize_date_string(value: Optional[str]) -> Optional[str]:
if not value or not isinstance(value, str):
return value
parsed = parse_date_string(value)
if parsed is not None:
return parsed.strftime("%d/%m/%Y")
return value.strip()
def resolve_relative_date(
value: Optional[str],
reference_time: Optional[str | datetime] = None,
) -> Optional[str]:
if not value or not isinstance(value, str):
return value
cleaned = value.strip()
absolute = normalize_date_string(cleaned)
if absolute != cleaned or parse_date_string(cleaned) is not None:
return absolute
if reference_time is None:
return cleaned
if isinstance(reference_time, datetime):
ref = reference_time
else:
ref = parse_date_string(str(reference_time))
if ref is None:
return cleaned
for pattern, resolver in _RELATIVE_PATTERNS:
match = pattern.match(cleaned)
if not match:
continue
if match.lastindex:
resolved = resolver(ref, match)
else:
resolved = resolver(ref)
return resolved.strftime("%d/%m/%Y")
weekday_match = re.match(r"^(last|this|next)\s+(monday|tuesday|wednesday|thursday|friday|saturday|sunday)$", cleaned, re.I)
if weekday_match:
mode = weekday_match.group(1).lower()
target = _WEEKDAY_NAMES[weekday_match.group(2).lower()]
current = ref.weekday()
if mode == "this":
delta = (target - current) % 7
elif mode == "next":
delta = (target - current) % 7 or 7
else:
delta = -((current - target) % 7 or 7)
return (ref + timedelta(days=delta)).strftime("%d/%m/%Y")
return cleaned
|