{"record":{"id":"f3ccdfdadc5313c5","repo":"mvanhorn/last30days-skill","slug":"invalid-as-of-date-as-of-date-expected-yyyy","errorCode":null,"errorMessage":"Invalid --as-of date: {as_of_date}. Expected YYYY-MM-DD.","messagePattern":"Invalid --as-of date: (.+?)\\. Expected YYYY-MM-DD\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"skills/last30days/scripts/lib/dates.py","lineNumber":28,"sourceCode":"    Args:\n        as_of_date: Date string in YYYY-MM-DD format.\n\n    Returns:\n        Normalized YYYY-MM-DD string, or None when no date was provided.\n\n    Raises:\n        ValueError: If the date is not in YYYY-MM-DD format.\n    \"\"\"\n    if as_of_date is None:\n        return None\n\n    if not as_of_date.strip():\n        raise ValueError(\"--as-of must be in YYYY-MM-DD format.\")\n\n    try:\n        parsed = datetime.strptime(as_of_date, \"%Y-%m-%d\").date()\n    except ValueError as exc:\n        raise ValueError(\n            f\"Invalid --as-of date: {as_of_date}. Expected YYYY-MM-DD.\"\n        ) from exc\n\n    return parsed.isoformat()\n\n\ndef get_date_range(days: int = 30, as_of_date: Optional[str] = None) -> Tuple[str, str]:\n    \"\"\"Get the date range for the last N days.\n\n    When as_of_date is provided, the range ends at that date instead of today.\n\n    Args:\n        days: Number of days to look back.\n        as_of_date: Optional end date in YYYY-MM-DD format.\n\n    Returns:\n        Tuple of (from_date, to_date) as YYYY-MM-DD strings.\n    \"\"\"","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/mvanhorn/last30days-skill/blob/c7460f6114449ddfe6ea3fc2f23c3d910c0e740c/skills/last30days/scripts/lib/dates.py#L10-L46","documentation":"`lib/dates.py:normalize_as_of_date` raises this ValueError when `--as-of` cannot be parsed by `datetime.strptime(value, \"%Y-%m-%d\")`. The message echoes the offending value and the expected format, and chains the original strptime exception. Any deviation — slashes, month names, 2-digit years, or a semantically invalid date like 2026-02-30 — fails.","triggerScenarios":"`--as-of 07/14/2026`, `--as-of 2026-7-4` (non-zero-padded), `--as-of July 14 2026`, `--as-of 2026-02-30`, or any locale-formatted date string. strptime's %Y-%m-%d requires zero-padded, exactly-formatted ISO dates.","commonSituations":"Users copying dates from regional formats (US MM/DD/YYYY); scripts producing `date +%m/%d/%Y`; pandas/dateutil-style outputs ('2026-07-14 00:00:00') passed untrimmed; LLM hosts generating a 'natural' date.","solutions":["Pass an ISO date: `--as-of 2026-07-14` (zero-padded, dashes).","In scripts, generate it with `date +%F` (or `date -I`), not a locale-dependent format.","Pre-validate in Python: `datetime.strptime(value, '%Y-%m-%d')` in a try/except before invoking the CLI."],"exampleFix":"# before\n--as-of 07/14/2026\n# after\n--as-of $(date -d '14 days ago' +%F)","handlingStrategy":"validation","validationCode":"from datetime import datetime\n\ndef valid_as_of(value: str) -> bool:\n    try:\n        datetime.strptime(value, \"%Y-%m-%d\")\n        return True\n    except ValueError:\n        return False\n\nif not valid_as_of(as_of):\n    raise SystemExit(f\"bad --as-of {as_of!r}; expected YYYY-MM-DD\")","typeGuard":null,"tryCatchPattern":"from lib.dates import normalize_as_of_date\ntry:\n    as_of = normalize_as_of_date(user_input)\nexcept ValueError as exc:\n    print(f\"date rejected: {exc}\", file=sys.stderr)\n    sys.exit(2)","preventionTips":["Always generate dates with `date +%F` in shell or date.isoformat() in Python.","Pass through ISO strings end-to-end; never reformat to locale styles between systems."],"tags":["cli","dates","validation","strptime"],"backgroundTag":null,"analyzedSha":"c7460f6114449ddfe6ea3fc2f23c3d910c0e740c","analyzedAt":"2026-08-15T03:34:49.540Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}