{
  "id": "todo",
  "title": "Todo and task lists",
  "summary": "A to-do list's logic as pure functions: the item type, a list reducer, quick-add parsing, due dates, repeats, tags, filters, sorting and CSV import/export.",
  "synonyms": [
    "todo",
    "to do",
    "to-do list",
    "tasks",
    "task list",
    "task manager",
    "checklist",
    "reminders",
    "productivity",
    "getting things done"
  ],
  "apps": [
    "todo app",
    "task manager",
    "to-do list app",
    "productivity app",
    "personal task tracker",
    "reminders app"
  ],
  "language": "python",
  "includes": [
    "dates-basics"
  ],
  "includedBy": [],
  "caveats": [],
  "sections": [
    {
      "key": "items",
      "title": "Items and the list",
      "purpose": "The todo type and every change to a list as a pure reducer.",
      "from": "todo",
      "members": [
        {
          "id": "todo.item",
          "tier": "core",
          "why": "The Todo, TodoDraft and Recurrence types, with validators giving a message per field.",
          "section": "items",
          "from": "todo",
          "version": "1.0.0",
          "summary": "The to-do item type (Todo, TodoDraft, Recurrence) and its validators, with a message per field.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": true,
          "functions": [
            "validateDraft",
            "validateTodo"
          ],
          "signatures": [
            "def validate_draft(draft: TodoDraft) -> TodoValidation",
            "def validate_todo(todo: Todo) -> TodoValidation"
          ],
          "reference": "from fune.todo.item import validate_draft, validate_todo  # todo.item@^1",
          "url": "http://www.functionalweave.com/functions/todo.item"
        },
        {
          "id": "todo.list",
          "tier": "core",
          "why": "Adds, edits, completes, removes, clears and reorders todos, each returning a new list.",
          "section": "items",
          "from": "todo",
          "version": "1.1.0",
          "summary": "The to-do list reducer: add, edit, complete, remove, clear and reorder todos, each returning a new list.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": true,
          "functions": [
            "addTodo",
            "updateTodo",
            "toggleTodo",
            "removeTodo",
            "clearCompleted",
            "toggleAll",
            "moveTodo",
            "toggleAllMode"
          ],
          "signatures": [
            "def add_todo(todos: Sequence[Todo], draft: TodoDraft, id: str, now: str) -> List[Todo]",
            "def update_todo(todos: Sequence[Todo], id: str, draft: TodoDraft) -> List[Todo]",
            "def toggle_todo(todos: Sequence[Todo], id: str, now: str, today: str) -> List[Todo]",
            "def remove_todo(todos: Sequence[Todo], id: str) -> List[Todo]",
            "def clear_completed(todos: Sequence[Todo]) -> List[Todo]",
            "def toggle_all(todos: Sequence[Todo], now: str, today: str) -> List[Todo]",
            "def move_todo(todos: Sequence[Todo], id: str, to_index: int) -> List[Todo]",
            "def toggle_all_mode(todos: Sequence[Todo]) -> ToggleAllMode"
          ],
          "reference": "from fune.todo.list import add_todo, update_todo, toggle_todo, remove_todo, clear_completed, toggle_all, move_todo, toggle_all_mode  # todo.list@^1",
          "url": "http://www.functionalweave.com/functions/todo.list"
        }
      ],
      "gaps": []
    },
    {
      "key": "entry",
      "title": "Quick entry",
      "purpose": "Turn what people type into todos.",
      "from": "todo",
      "members": [
        {
          "id": "todo.parse-quick-add",
          "tier": "core",
          "why": "Turns \"Pay rent tomorrow !high",
          "section": "entry",
          "from": "todo",
          "version": "1.0.0",
          "summary": "Turn one typed line like \"Pay rent tomorrow !high #home every month\" into a todo draft.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "parseQuickAdd"
          ],
          "signatures": [
            "def parse_quick_add(text: str, today: str) -> TodoDraft"
          ],
          "reference": "from fune.todo.parse_quick_add import parse_quick_add  # todo.parse-quick-add@^1",
          "url": "http://www.functionalweave.com/functions/todo.parse-quick-add"
        },
        {
          "id": "todo.parse-date-phrase",
          "tier": "optional",
          "why": "Reads \"tomorrow\", \"next friday\", \"in 2 weeks\" as a date relative to today.",
          "section": "entry",
          "from": "todo",
          "version": "1.0.0",
          "summary": "Read a typed due date like \"tomorrow\", \"next friday\", \"in 2 weeks\" or \"2026-10-01\" as a date, relative to today.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "parseDatePhrase"
          ],
          "signatures": [
            "def parse_date_phrase(text: str, today: str) -> Optional[str]"
          ],
          "reference": "from fune.todo.parse_date_phrase import parse_date_phrase  # todo.parse-date-phrase@^1",
          "url": "http://www.functionalweave.com/functions/todo.parse-date-phrase"
        },
        {
          "id": "todo.parse-tags",
          "tier": "optional",
          "why": "Reads a tags box (\"#Home, work\") into normalised tags.",
          "section": "entry",
          "from": "todo",
          "version": "1.0.0",
          "summary": "Read a tags box as typed, \"#Home, work  errands\", into normalised tags: split on spaces and commas.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "parseTags"
          ],
          "signatures": [
            "def parse_tags(text: str) -> List[str]"
          ],
          "reference": "from fune.todo.parse_tags import parse_tags  # todo.parse-tags@^1",
          "url": "http://www.functionalweave.com/functions/todo.parse-tags"
        },
        {
          "id": "todo.normalise-tags",
          "tier": "optional",
          "why": "Cleans tags: trims, drops",
          "section": "entry",
          "from": "todo",
          "version": "1.0.0",
          "summary": "Clean up tags as typed: trim, drop the #, lower-case slug form, drop empties and duplicates, keep the order.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "normaliseTags"
          ],
          "signatures": [
            "def normalise_tags(tags: Sequence[str]) -> List[str]"
          ],
          "reference": "from fune.todo.normalise_tags import normalise_tags  # todo.normalise-tags@^1",
          "url": "http://www.functionalweave.com/functions/todo.normalise-tags"
        },
        {
          "id": "todo.parse-repeat",
          "tier": "optional",
          "why": "Reads a Repeat box such as \"every 2 weeks\" as a recurrence.",
          "section": "entry",
          "from": "todo",
          "version": "1.0.0",
          "summary": "Read a Repeat box, \"every 2 weeks\" or \"every monday on 2026-10-05\", as a todo's recurrence, or null.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "parseRepeat"
          ],
          "signatures": [
            "def parse_repeat(text: str, due: Optional[str], today: str) -> Optional[Recurrence]"
          ],
          "reference": "from fune.todo.parse_repeat import parse_repeat  # todo.parse-repeat@^1",
          "url": "http://www.functionalweave.com/functions/todo.parse-repeat"
        }
      ],
      "gaps": []
    },
    {
      "key": "due",
      "title": "Due dates and repeats",
      "purpose": "Say where each todo stands against its date, and roll repeating ones forward.",
      "from": "todo",
      "members": [
        {
          "id": "todo.due-status",
          "tier": "core",
          "why": "Done, overdue, today, tomorrow, this week, later or no date, for a todo today.",
          "section": "due",
          "from": "todo",
          "version": "1.0.0",
          "summary": "Where a todo stands against its due date today: done, overdue, today, tomorrow, this week, later or no date.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "dueStatus"
          ],
          "signatures": [
            "def due_status(todo: Todo, today: str) -> DueStatus"
          ],
          "reference": "from fune.todo.due_status import due_status  # todo.due-status@^1",
          "url": "http://www.functionalweave.com/functions/todo.due-status"
        },
        {
          "id": "todo.due-label",
          "tier": "core",
          "why": "The short label beside a todo: \"2 days overdue\", \"Due tomorrow\".",
          "section": "due",
          "from": "todo",
          "version": "1.0.0",
          "summary": "The short English label shown next to a todo: \"2 days overdue\", \"Due today\", \"Due tomorrow\", \"Due Fri 2 Oct\".",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "dueLabel"
          ],
          "signatures": [
            "def due_label(todo: Todo, today: str) -> str"
          ],
          "reference": "from fune.todo.due_label import due_label  # todo.due-label@^1",
          "url": "http://www.functionalweave.com/functions/todo.due-label"
        },
        {
          "id": "todo.next-occurrence",
          "tier": "optional",
          "why": "The next due date of a repeating todo once done, month-end safe.",
          "section": "due",
          "from": "todo",
          "version": "1.0.0",
          "summary": "The next due date of a repeating todo once it is done: daily, weekdays, weekly, monthly or yearly, month-end safe.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "nextOccurrence"
          ],
          "signatures": [
            "def next_occurrence(recurrence: Recurrence, due: str, today: str) -> str"
          ],
          "reference": "from fune.todo.next_occurrence import next_occurrence  # todo.next-occurrence@^1",
          "url": "http://www.functionalweave.com/functions/todo.next-occurrence"
        },
        {
          "id": "todo.reschedule-recurrence",
          "tier": "optional",
          "why": "Moves a repeating todo's series when its due date is changed by hand.",
          "section": "due",
          "from": "todo",
          "version": "1.0.0",
          "summary": "When a repeating todo's due date is moved by hand, move its series with it: the new date becomes the anchor.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "rescheduleRecurrence"
          ],
          "signatures": [
            "def reschedule_recurrence(recurrence: Optional[Recurrence], from_due: Optional[str], to_due: Optional[str]) -> Optional[Recurrence]"
          ],
          "reference": "from fune.todo.reschedule_recurrence import reschedule_recurrence  # todo.reschedule-recurrence@^1",
          "url": "http://www.functionalweave.com/functions/todo.reschedule-recurrence"
        },
        {
          "id": "todo.recurrence-label",
          "tier": "optional",
          "why": "Text for a repeat chip: \"Every weekday\", \"Every month on the 31st\".",
          "section": "due",
          "from": "todo",
          "version": "1.0.0",
          "summary": "English text for a repeating todo's chip: \"Every weekday\", \"Every 2 weeks on Monday\", \"Every month on the 31st\".",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "recurrenceLabel"
          ],
          "signatures": [
            "def recurrence_label(recurrence: Recurrence) -> str"
          ],
          "reference": "from fune.todo.recurrence_label import recurrence_label  # todo.recurrence-label@^1",
          "url": "http://www.functionalweave.com/functions/todo.recurrence-label"
        }
      ],
      "gaps": []
    },
    {
      "key": "views",
      "title": "Views",
      "purpose": "The list as people see it: filtered, sorted, grouped and counted.",
      "from": "todo",
      "members": [
        {
          "id": "todo.filter",
          "tier": "core",
          "why": "All, active or completed, one tag and a search box.",
          "section": "views",
          "from": "todo",
          "version": "1.0.0",
          "summary": "The todos a list view shows: all, active or completed, optionally one tag, and a search box, in input order.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "filterTodos"
          ],
          "signatures": [
            "def filter_todos(todos: Sequence[Todo], filter: TodoFilter) -> List[Todo]"
          ],
          "reference": "from fune.todo.filter import filter_todos  # todo.filter@^1",
          "url": "http://www.functionalweave.com/functions/todo.filter"
        },
        {
          "id": "todo.sort",
          "tier": "core",
          "why": "Sorts by manual order, due date, priority, creation time or title, stably.",
          "section": "views",
          "from": "todo",
          "version": "1.0.0",
          "summary": "Sort todos by manual order, due date, priority, creation time or title, stably, with sensible tie-breakers.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "sortTodos"
          ],
          "signatures": [
            "def sort_todos(todos: Sequence[Todo], by: TodoSortKey, direction: SortDirection) -> List[Todo]"
          ],
          "reference": "from fune.todo.sort import sort_todos  # todo.sort@^1",
          "url": "http://www.functionalweave.com/functions/todo.sort"
        },
        {
          "id": "todo.group-by-due",
          "tier": "optional",
          "why": "Overdue, Today, Tomorrow, This week, Later, No date and Done sections.",
          "section": "views",
          "from": "todo",
          "version": "1.1.0",
          "summary": "Split todos into Overdue, Today, Tomorrow, This week, Later, No date and Done sections, in that order.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": true,
          "functions": [
            "groupByDue",
            "groupByDueWithOrder"
          ],
          "signatures": [
            "def group_by_due(todos: Sequence[Todo], today: str) -> List[DueGroup]",
            "def group_by_due_with_order(todos: Sequence[Todo], today: str, order: SectionOrder) -> List[DueGroup]"
          ],
          "reference": "from fune.todo.group_by_due import group_by_due, group_by_due_with_order  # todo.group-by-due@^1",
          "url": "http://www.functionalweave.com/functions/todo.group-by-due"
        },
        {
          "id": "todo.summary",
          "tier": "optional",
          "why": "Counts for the header or footer: \"3 items left\", percent done, overdue.",
          "section": "views",
          "from": "todo",
          "version": "1.0.0",
          "summary": "Counts for a todo list's header or footer: total, active, done, overdue, due today, percent done and \"3 items left\".",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "summariseTodos"
          ],
          "signatures": [
            "def summarise_todos(todos: Sequence[Todo], today: str) -> TodoSummary"
          ],
          "reference": "from fune.todo.summary import summarise_todos  # todo.summary@^1",
          "url": "http://www.functionalweave.com/functions/todo.summary"
        },
        {
          "id": "todo.tag-counts",
          "tier": "optional",
          "why": "How many todos carry each tag, for a tag sidebar.",
          "section": "views",
          "from": "todo",
          "version": "1.0.0",
          "summary": "How many todos carry each tag, for a tag sidebar: most used first, open todos only unless asked.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "tagCounts"
          ],
          "signatures": [
            "def tag_counts(todos: Sequence[Todo], include_done: bool) -> List[TagCount]"
          ],
          "reference": "from fune.todo.tag_counts import tag_counts  # todo.tag-counts@^1",
          "url": "http://www.functionalweave.com/functions/todo.tag-counts"
        }
      ],
      "gaps": []
    },
    {
      "key": "import-export",
      "title": "Import and export",
      "purpose": "Move todos in and out as CSV.",
      "from": "todo",
      "members": [
        {
          "id": "todo.export-csv",
          "tier": "optional",
          "why": "Writes todos as an RFC 4180 CSV file for backups and spreadsheets.",
          "section": "import-export",
          "from": "todo",
          "version": "1.0.0",
          "summary": "Write todos as a CSV file (RFC 4180) with one column per field, for backups, spreadsheets and other apps.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "exportCsv"
          ],
          "signatures": [
            "def export_csv(todos: Sequence[Todo]) -> str"
          ],
          "reference": "from fune.todo.export_csv import export_csv  # todo.export-csv@^1",
          "url": "http://www.functionalweave.com/functions/todo.export-csv"
        },
        {
          "id": "todo.import-csv",
          "tier": "optional",
          "why": "Reads that CSV back, checking every row with row-numbered errors.",
          "section": "import-export",
          "from": "todo",
          "version": "1.1.0",
          "summary": "Read todos from a CSV file (RFC 4180) as todo.export-csv writes it, checking every row, with row-numbered errors.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": true,
          "functions": [
            "importCsv",
            "validateTodoCsv"
          ],
          "signatures": [
            "def import_csv(csv: str) -> List[Todo]",
            "def validate_todo_csv(csv: str) -> CsvValidation"
          ],
          "reference": "from fune.todo.import_csv import import_csv, validate_todo_csv  # todo.import-csv@^1",
          "url": "http://www.functionalweave.com/functions/todo.import-csv"
        },
        {
          "id": "todo.merge",
          "tier": "optional",
          "why": "Merges imported todos into a list by id.",
          "section": "import-export",
          "from": "todo",
          "version": "1.0.0",
          "summary": "Merge imported todos into a list by id: an imported todo replaces the one with its id, new ids are appended.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "mergeTodos"
          ],
          "signatures": [
            "def merge_todos(current: Sequence[Todo], incoming: Sequence[Todo]) -> List[Todo]"
          ],
          "reference": "from fune.todo.merge import merge_todos  # todo.merge@^1",
          "url": "http://www.functionalweave.com/functions/todo.merge"
        }
      ],
      "gaps": []
    },
    {
      "key": "date-arithmetic",
      "title": "Calendar arithmetic",
      "purpose": "Move and measure ISO dates exactly, with no date library and no clock reads.",
      "from": "dates-basics",
      "members": [
        {
          "id": "dates.add-days",
          "tier": "core",
          "why": "Shifts a date by whole days, for due dates and windows.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Shift an ISO date by a whole number of days, forwards or backwards, with exact calendar arithmetic.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "addDays"
          ],
          "signatures": [
            "def add_days(iso: str, days: int) -> str"
          ],
          "reference": "from fune.dates.add_days import add_days  # dates.add-days@^1",
          "url": "http://www.functionalweave.com/functions/dates.add-days"
        },
        {
          "id": "dates.add-months",
          "tier": "core",
          "why": "Adds months clamping to month end, so 31 January plus a month is the end of February.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Add calendar months to an ISO date, clamping to the month end: 31 Jan + 1 month is 28 or 29 Feb.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "addMonths"
          ],
          "signatures": [
            "def add_months(iso: str, months: int) -> str"
          ],
          "reference": "from fune.dates.add_months import add_months  # dates.add-months@^1",
          "url": "http://www.functionalweave.com/functions/dates.add-months"
        },
        {
          "id": "dates.days-between",
          "tier": "core",
          "why": "Signed calendar days between two dates, for ages of invoices and stays.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Calendar days from one ISO date to another, signed: negative when the second date is earlier.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "daysBetween"
          ],
          "signatures": [
            "def days_between(start_iso: str, end_iso: str) -> int"
          ],
          "reference": "from fune.dates.days_between import days_between  # dates.days-between@^1",
          "url": "http://www.functionalweave.com/functions/dates.days-between"
        },
        {
          "id": "dates.month-boundaries",
          "tier": "core",
          "why": "First and last day of a date's month, for monthly periods and statements.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "The first day, last day and length of the calendar month containing a date.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "monthBoundaries"
          ],
          "signatures": [
            "def month_boundaries(iso: str) -> MonthBoundaries"
          ],
          "reference": "from fune.dates.month_boundaries import month_boundaries  # dates.month-boundaries@^1",
          "url": "http://www.functionalweave.com/functions/dates.month-boundaries"
        },
        {
          "id": "dates.day-of-week",
          "tier": "core",
          "why": "ISO weekday of a date, for weekend rules and opening hours.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "The ISO 8601 day of the week for a date: 1 is Monday through 7 is Sunday.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "dayOfWeek"
          ],
          "signatures": [
            "def day_of_week(iso: str) -> int"
          ],
          "reference": "from fune.dates.day_of_week import day_of_week  # dates.day-of-week@^1",
          "url": "http://www.functionalweave.com/functions/dates.day-of-week"
        },
        {
          "id": "dates.is-leap-year",
          "tier": "optional",
          "why": "Whether a year is a leap year, for 365/366-day calculations.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Whether a year is a Gregorian leap year: every 4th, except centuries, except every 400th.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "isLeapYear"
          ],
          "signatures": [
            "def is_leap_year(year: int) -> bool"
          ],
          "reference": "from fune.dates.is_leap_year import is_leap_year  # dates.is-leap-year@^1",
          "url": "http://www.functionalweave.com/functions/dates.is-leap-year"
        },
        {
          "id": "dates.overlap",
          "tier": "optional",
          "why": "Days two date ranges share, for prorating and clash checks.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Days two date ranges have in common, with the end of each range stated as inclusive or exclusive.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "overlapDays"
          ],
          "signatures": [
            "def overlap_days(a_start: str, a_end: str, b_start: str, b_end: str, end_inclusive: bool) -> int"
          ],
          "reference": "from fune.dates.overlap import overlap_days  # dates.overlap@^1",
          "url": "http://www.functionalweave.com/functions/dates.overlap"
        },
        {
          "id": "dates.age",
          "tier": "optional",
          "why": "Age in whole years on a date, handling 29 February birthdays.",
          "section": "date-arithmetic",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Age in whole years on a given date, with 29 February birthdays ageing on 1 March in other years.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "age"
          ],
          "signatures": [
            "def age(birth_date: str, on_date: str) -> int"
          ],
          "reference": "from fune.dates.age import age  # dates.age@^1",
          "url": "http://www.functionalweave.com/functions/dates.age"
        }
      ],
      "gaps": []
    },
    {
      "key": "reporting-periods",
      "title": "Periods and reporting dates",
      "purpose": "Put dates into the periods reports are grouped by.",
      "from": "dates-basics",
      "members": [
        {
          "id": "dates.quarter",
          "tier": "core",
          "why": "Calendar and fiscal quarter of a date for any year-start month.",
          "section": "reporting-periods",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Calendar quarter and fiscal quarter of a date, with the fiscal year and quarter date ranges, for any start month.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "quarter"
          ],
          "signatures": [
            "def quarter(iso: str, fiscal_year_start_month: int) -> Quarter"
          ],
          "reference": "from fune.dates.quarter import quarter  # dates.quarter@^1",
          "url": "http://www.functionalweave.com/functions/dates.quarter"
        },
        {
          "id": "dates.week-number",
          "tier": "optional",
          "why": "ISO week number and week-year, for weekly reports.",
          "section": "reporting-periods",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "ISO 8601 week number and week-numbering year of a date: weeks start Monday, week 1 holds the first Thursday.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "weekNumber"
          ],
          "signatures": [
            "def week_number(iso: str) -> IsoWeek"
          ],
          "reference": "from fune.dates.week_number import week_number  # dates.week-number@^1",
          "url": "http://www.functionalweave.com/functions/dates.week-number"
        },
        {
          "id": "dates.day-count-fraction",
          "tier": "optional",
          "why": "Year fraction by ACT/365F, ACT/360, 30/360 or ACT/ACT, for interest periods.",
          "section": "reporting-periods",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Year fraction between two dates by ACT/365F, ACT/360, 30/360, 30E/360 or ACT/ACT ISDA, as an exact fraction.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "dayCountFraction"
          ],
          "signatures": [
            "def day_count_fraction(start_iso: str, end_iso: str, convention: DayCountConvention) -> YearFraction"
          ],
          "reference": "from fune.dates.day_count_fraction import day_count_fraction  # dates.day-count-fraction@^1",
          "url": "http://www.functionalweave.com/functions/dates.day-count-fraction"
        }
      ],
      "gaps": []
    },
    {
      "key": "working-days",
      "title": "Working days and holidays",
      "purpose": "Skip weekends and public holidays when counting or adding days.",
      "from": "dates-basics",
      "members": [
        {
          "id": "dates.bank-holidays",
          "tier": "core",
          "why": "UK bank holidays for a region and year, as gov.uk publishes them.",
          "section": "working-days",
          "from": "dates-basics",
          "version": "1.0.1",
          "summary": "UK bank and public holidays for a region and year, as published by gov.uk, including one-off holidays.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "bankHolidays"
          ],
          "signatures": [
            "def bank_holidays(region: UkRegion, year: int) -> List[BankHoliday]"
          ],
          "reference": "from fune.dates.bank_holidays import bank_holidays  # dates.bank-holidays@^1",
          "url": "http://www.functionalweave.com/functions/dates.bank-holidays"
        },
        {
          "id": "dates.add-business-days",
          "tier": "core",
          "why": "Moves a date by working days, skipping weekends and a holiday list.",
          "section": "working-days",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Move a date forward or back by working days, skipping weekends and a caller-supplied holiday list.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "addBusinessDays"
          ],
          "signatures": [
            "def add_business_days(iso: str, days: int, holidays: Sequence[str]) -> str"
          ],
          "reference": "from fune.dates.add_business_days import add_business_days  # dates.add-business-days@^1",
          "url": "http://www.functionalweave.com/functions/dates.add-business-days"
        },
        {
          "id": "dates.business-days-between",
          "tier": "core",
          "why": "Working days between two dates, for SLAs and notice periods.",
          "section": "working-days",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Working days between two dates, excluding weekends and a caller-supplied holiday list.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "businessDaysBetween"
          ],
          "signatures": [
            "def business_days_between(start_iso: str, end_iso: str, holidays: Sequence[str]) -> int"
          ],
          "reference": "from fune.dates.business_days_between import business_days_between  # dates.business-days-between@^1",
          "url": "http://www.functionalweave.com/functions/dates.business-days-between"
        }
      ],
      "gaps": []
    },
    {
      "key": "repeating-dates",
      "title": "Repeating schedules",
      "purpose": "Generate the dates of something that repeats.",
      "from": "dates-basics",
      "members": [
        {
          "id": "dates.recurrence",
          "tier": "optional",
          "why": "The next n dates of a rule: monthly on the 31st, last Friday, every two weeks.",
          "section": "repeating-dates",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "The next n dates of a repeating rule: monthly on a day, last weekday of the month, every k days or weeks, yearly.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "recurrence"
          ],
          "signatures": [
            "def recurrence(rule: RecurrenceRule, start_iso: str, count: int) -> List[str]"
          ],
          "reference": "from fune.dates.recurrence import recurrence  # dates.recurrence@^1",
          "url": "http://www.functionalweave.com/functions/dates.recurrence"
        }
      ],
      "gaps": []
    },
    {
      "key": "times",
      "title": "Times and durations",
      "purpose": "Work with times of day, durations and timestamps.",
      "from": "dates-basics",
      "members": [
        {
          "id": "time.duration",
          "tier": "optional",
          "why": "Parses and adds durations like 1h30m in whole minutes, and formats the total.",
          "section": "times",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Parse durations written as 1h30m, 90m or 01:30, add them up in whole minutes, and format the total.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "duration"
          ],
          "signatures": [
            "def duration(parts: Sequence[str]) -> Duration"
          ],
          "reference": "from fune.time.duration import duration  # time.duration@^1",
          "url": "http://www.functionalweave.com/functions/time.duration"
        },
        {
          "id": "time.minutes-between",
          "tier": "optional",
          "why": "Minutes between two local date-times, across midnight.",
          "section": "times",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Minutes from one local date and time to another, correct across midnight; wall-clock time, no time zones.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "minutesBetween"
          ],
          "signatures": [
            "def minutes_between(start_date: str, start_time: str, end_date: str, end_time: str) -> int"
          ],
          "reference": "from fune.time.minutes_between import minutes_between  # time.minutes-between@^1",
          "url": "http://www.functionalweave.com/functions/time.minutes-between"
        },
        {
          "id": "time.round-to-increment",
          "tier": "optional",
          "why": "Rounds minutes to a billing increment such as 6 or 15.",
          "section": "times",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Round a duration in minutes to a billing increment such as 6 or 15 minutes, up, down or to nearest.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "roundToIncrement"
          ],
          "signatures": [
            "def round_to_increment(minutes: int, increment: int, mode: RoundingMode) -> int"
          ],
          "reference": "from fune.time.round_to_increment import round_to_increment  # time.round-to-increment@^1",
          "url": "http://www.functionalweave.com/functions/time.round-to-increment"
        },
        {
          "id": "time.iso-to-unix",
          "tier": "optional",
          "why": "RFC 3339 timestamps to Unix seconds.",
          "section": "times",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "An RFC 3339 timestamp with a Z or offset, such as 2026-09-26T13:00:00+01:00, to Unix time in whole seconds.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "isoToUnix"
          ],
          "signatures": [
            "def iso_to_unix(timestamp: str) -> int"
          ],
          "reference": "from fune.time.iso_to_unix import iso_to_unix  # time.iso-to-unix@^1",
          "url": "http://www.functionalweave.com/functions/time.iso-to-unix"
        },
        {
          "id": "time.unix-to-iso",
          "tier": "optional",
          "why": "Unix seconds to an ISO 8601 UTC timestamp.",
          "section": "times",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Unix time in seconds to an ISO 8601 UTC timestamp such as 2026-09-26T12:00:00Z, for years 0001 to 9999.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "unixToIso"
          ],
          "signatures": [
            "def unix_to_iso(seconds: int) -> str"
          ],
          "reference": "from fune.time.unix_to_iso import unix_to_iso  # time.unix-to-iso@^1",
          "url": "http://www.functionalweave.com/functions/time.unix-to-iso"
        },
        {
          "id": "time.countdown",
          "tier": "optional",
          "why": "Seconds left until a moment and a 4:05 style timer text.",
          "section": "times",
          "from": "dates-basics",
          "version": "1.0.0",
          "summary": "Seconds left until a Unix time, whether it has passed, and a timer text like 4:05, for tokens or retry waits.",
          "status": "stable",
          "license": "NOASSERTION",
          "languages": [
            "typescript",
            "python",
            "rust"
          ],
          "available": true,
          "group": false,
          "functions": [
            "countdown"
          ],
          "signatures": [
            "def countdown(until: int, now: int) -> Countdown"
          ],
          "reference": "from fune.time.countdown import countdown  # time.countdown@^1",
          "url": "http://www.functionalweave.com/functions/time.countdown"
        }
      ],
      "gaps": []
    }
  ],
  "gaps": [
    {
      "key": "persistence",
      "text": "Storing todos (localStorage, a database) and loading them: caps works on lists in memory.",
      "section": null,
      "from": "todo"
    },
    {
      "key": "sync",
      "text": "Syncing between devices and resolving edits made offline on two of them.",
      "section": null,
      "from": "todo"
    },
    {
      "key": "notifications",
      "text": "Reminders that fire at a time: push, email or OS notifications and their scheduler.",
      "section": null,
      "from": "todo"
    },
    {
      "key": "sharing",
      "text": "Shared lists, assignees and permissions between users.",
      "section": null,
      "from": "todo"
    },
    {
      "key": "ui",
      "text": "The list UI itself: Functional Weave has no todo components, only the logic and React form fields.",
      "section": null,
      "from": "todo"
    },
    {
      "key": "time-zones",
      "text": "Time zones and daylight saving: caps works in ISO dates, UTC and wall-clock times; converting with the IANA tz rules is yours.",
      "section": null,
      "from": "dates-basics"
    },
    {
      "key": "locale-formatting",
      "text": "Showing dates in a user's language and format (\"3 octobre 2026\"): caps returns ISO dates, not localised text.",
      "section": null,
      "from": "dates-basics"
    },
    {
      "key": "non-uk-holidays",
      "text": "Public holidays outside the UK: dates.bank-holidays is UK only; pass another country's list to the working-day functions yourself.",
      "section": null,
      "from": "dates-basics"
    }
  ],
  "counts": {
    "members": 41,
    "core": 16,
    "optional": 25,
    "unreviewed": 0,
    "deprecated": [],
    "unavailable": [],
    "missing": [],
    "gaps": 8
  },
  "warnings": [],
  "url": "http://www.functionalweave.com/suites/todo",
  "owner": null
}
