File size: 1,356 Bytes
7ff7119 | 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 | """Date parser and period helpers.
Multi-format support: YYYY-MM-DD, YYYY.MM.DD, YYYY/MM/DD, DD.MM.YYYY, DD/MM/YYYY.
"""
from __future__ import annotations
from datetime import datetime
from utils.numbers import is_null_alias
_FORMATS = (
"%Y-%m-%d",
"%Y.%m.%d",
"%Y.%m.%d.",
"%Y/%m/%d",
"%d.%m.%Y",
"%d.%m.%Y.",
"%d/%m/%Y",
)
def parse_date_safe(value) -> datetime | None:
"""Parse a date string in multiple formats. Returns None on failure."""
if value is None:
return None
if isinstance(value, datetime):
return value
if not isinstance(value, str):
return None
s = value.strip()
if not s or is_null_alias(s):
return None
# Take the first 10 chars (YYYY-MM-DD-like prefix, possibly trailing .)
s = s[:10] if len(s) >= 10 else s
for fmt in _FORMATS:
try:
return datetime.strptime(s, fmt)
except ValueError:
continue
return None
def is_expiring_soon(end_date_str: str | None, months: int = 12) -> bool:
"""Check whether the given date expires within the next ``months`` months."""
end = parse_date_safe(end_date_str)
if not end:
return False
now = datetime.now()
months_remaining = (end.year - now.year) * 12 + (end.month - now.month)
return 0 <= months_remaining <= months
|