from .dates_add_days import parse_iso_date from .dates_day_of_week import day_name from .todo_item import Recurrence _MONTHS = ("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec") _FREQUENCIES = ("daily", "weekdays", "weekly", "monthly", "yearly") def _ordinal(n: int) -> str: """1st, 2nd, 3rd, 4th ... 11th, 12th, 13th ... 21st, 22nd, 23rd ... 31st.""" if 11 <= n % 100 <= 13: return f"{n}th" suffix = {1: "st", 2: "nd", 3: "rd"}.get(n % 10, "th") return f"{n}{suffix}" def _every(interval: int, unit: str) -> str: return f"Every {unit}" if interval == 1 else f"Every {interval} {unit}s" def recurrence_label(recurrence: Recurrence) -> str: """English words for a repeat rule; the weekday, day and date come from the anchor.""" frequency, interval, anchor = recurrence.frequency, recurrence.interval, recurrence.anchor if frequency not in _FREQUENCIES: raise ValueError(f'unknown repeat frequency "{frequency}": expected daily, weekdays, weekly, monthly or yearly') if isinstance(interval, bool) or not isinstance(interval, int) or interval < 1: raise ValueError(f"repeat interval must be 1 or more, received {interval}") if frequency == "weekdays" and interval != 1: raise ValueError(f"weekday repeats cannot skip weeks: interval must be 1, received {interval}") date = parse_iso_date(anchor) if frequency == "daily": return _every(interval, "day") if frequency == "weekdays": return "Every weekday" if frequency == "weekly": return f"{_every(interval, 'week')} on {day_name(anchor)}" if frequency == "monthly": return f"{_every(interval, 'month')} on the {_ordinal(date.day)}" return f"{_every(interval, 'year')} on {date.day} {_MONTHS[date.month - 1]}"