feat(cli): add natural language date parsing via dateparser

- Add _parse_natural() to utils.py using dateparser as a fallback when
  structured date formats (YYYY-MM-DD, MM-DD, DD-MM) don't match
- Supports expressions like 'today', 'yesterday', 'monday', '3 days ago'
- Change day argument to nargs='*' and join tokens so unquoted
  multi-word expressions like: uv run timesheets 3 days ago work correctly
- Pin dateparser to English to avoid locale-dependent behaviour
- Update tests to cover natural language cases and fix test_last_monday
  (dateparser does not support 'last monday'; use 'monday' instead)
This commit is contained in:
Jef Roosens 2026-05-22 10:57:16 +02:00
parent 29698b1241
commit 615bfe30e0
Signed by: Jef Roosens
GPG key ID: 119385BCAA005C21
6 changed files with 207 additions and 13 deletions

View file

@ -51,12 +51,12 @@ def build_parser() -> argparse.ArgumentParser:
)
parser.add_argument(
"day",
nargs="?",
nargs="*",
help=(
"Day to extract timesheets for. Accepts YYYY-MM-DD, MM-DD, or DD-MM "
"(- or / as separator). Defaults to today."
"Day to extract timesheets for. Accepts YYYY-MM-DD, MM-DD, DD-MM, "
"or a natural language expression like 'yesterday' or '3 days ago'. "
"Defaults to today."
),
default=None,
)
parser.add_argument(
"--map",
@ -93,11 +93,13 @@ def main() -> None:
config_path = args.config if args.config is not None else find_default_config()
config = load_config(config_path)
if args.day is None:
day_input = " ".join(args.day) if args.day else None
if day_input is None:
target_date = date.today()
else:
try:
target_date = parse_date_arg(args.day)
target_date = parse_date_arg(day_input)
except AmbiguousDateError as e:
print(f"Error: {e}", file=sys.stderr)
sys.exit(1)