Spaces:
Sleeping
Sleeping
Download core/date_engine.py from yania-n/ProjectCraft: direct link, hf CLI and curl.
- Browser
- Download file 3.14 kB
-
https://huggingface.co/spaces/yania-n/ProjectCraft/resolve/main/core/date_engine.py
- Command line
-
hf download hf://spaces/yania-n/ProjectCraft/core/date_engine.py
-
curl -L -o date_engine.py https://huggingface.co/spaces/yania-n/ProjectCraft/resolve/main/core/date_engine.py
3.14 kB
| """Three-mode date intelligence engine for ProjectCraft.""" | |
| import re | |
| from datetime import date, timedelta | |
| def detect_date_mode(start: str, end: str, start_ai: bool, end_ai: bool) -> str: | |
| """ | |
| Detect which date mode to use. | |
| has_start = bool(start.strip()) and not start_ai | |
| has_end = bool(end.strip()) and not end_ai | |
| Mode A: both provided -> return "A" | |
| Mode B: start only -> return "B" | |
| Mode C: end only -> return "C" | |
| Neither: raise ValueError("At least one date must be provided.") | |
| """ | |
| has_start = bool(start.strip()) and not start_ai | |
| has_end = bool(end.strip()) and not end_ai | |
| if has_start and has_end: | |
| return "A" | |
| elif has_start: | |
| return "B" | |
| elif has_end: | |
| return "C" | |
| else: | |
| raise ValueError("At least one date must be provided.") | |
| def validate_date_order(start: str, end: str) -> bool: | |
| """Return True if start <= end (both as YYYY-MM-DD strings). Return True if either is empty.""" | |
| if not start or not end: | |
| return True | |
| parsed_start = parse_iso_date(start) | |
| parsed_end = parse_iso_date(end) | |
| if parsed_start is None or parsed_end is None: | |
| return True | |
| return parsed_start <= parsed_end | |
| def format_date_range(start: str, end: str) -> str: | |
| """Format two YYYY-MM-DD strings into a readable range like 'Jan 15, 2025 -> Mar 20, 2025'. Handle None/empty gracefully.""" | |
| def fmt(d: str) -> str: | |
| if not d: | |
| return "" | |
| parsed = parse_iso_date(d) | |
| if parsed is None: | |
| return "" | |
| # strftime('%d') zero-pads; lstrip('0') gives non-padded day cross-platform | |
| day = parsed.strftime("%d").lstrip("0") or "0" | |
| return parsed.strftime(f"%b {day}, %Y") | |
| start_fmt = fmt(start) if start else "" | |
| end_fmt = fmt(end) if end else "" | |
| if start_fmt and end_fmt: | |
| return f"{start_fmt} \u2192 {end_fmt}" | |
| elif start_fmt: | |
| return start_fmt | |
| elif end_fmt: | |
| return end_fmt | |
| return "" | |
| def parse_iso_date(date_str: str): | |
| """Parse a YYYY-MM-DD string to datetime.date. Return None if invalid.""" | |
| if not date_str or not isinstance(date_str, str): | |
| return None | |
| if not re.fullmatch(r"\d{4}-\d{2}-\d{2}", date_str.strip()): | |
| return None | |
| try: | |
| parts = date_str.strip().split("-") | |
| return date(int(parts[0]), int(parts[1]), int(parts[2])) | |
| except (ValueError, IndexError): | |
| return None | |
| def days_between(start: str, end: str) -> int: | |
| """Return number of days between two YYYY-MM-DD strings. Return 0 if either is invalid.""" | |
| parsed_start = parse_iso_date(start) | |
| parsed_end = parse_iso_date(end) | |
| if parsed_start is None or parsed_end is None: | |
| return 0 | |
| return abs((parsed_end - parsed_start).days) | |
| def offset_date(base_date: str, offset_days: int) -> str: | |
| """Add offset_days to a YYYY-MM-DD string. Return the result as YYYY-MM-DD.""" | |
| parsed = parse_iso_date(base_date) | |
| if parsed is None: | |
| raise ValueError(f"Invalid base date: {base_date!r}") | |
| result = parsed + timedelta(days=offset_days) | |
| return result.isoformat() | |