Skip to content

Consolidate duration formatting functions in report.py #243

@microsasa

Description

@microsasa

Problem

src/copilot_usage/report.py has three functions that all format a time duration into a human-readable string:

  1. format_duration(ms: int) (line 64) — Takes milliseconds, returns e.g. "6m 29s", "1h 1m 1s"
  2. _format_elapsed_since(start: datetime) (line 94) — Computes now - start, returns e.g. "2h 15m" or "3m 42s"
  3. _format_detail_duration(start, end) (line 271) — Computes end - start, returns e.g. "5m 30s" or "—" if either is None

All three contain the same hours/minutes/seconds decomposition and formatting logic. They differ only in input type and null handling.

Solution

  1. Create a single core helper:
def _format_timedelta(td: timedelta) -> str:
    """Format a timedelta to human-readable duration (e.g. '1h 5m 30s')."""
    total_seconds = max(int(td.total_seconds()), 0)
    hours, remainder = divmod(total_seconds, 3600)
    minutes, seconds = divmod(remainder, 60)
    parts: list[str] = []
    if hours:
        parts.append(f"{hours}h")
    if minutes:
        parts.append(f"{minutes}m")
    if seconds or not parts:
        parts.append(f"{seconds}s")
    return " ".join(parts)
  1. Rewrite the three functions to use it:
  • format_duration(ms: int)_format_timedelta(timedelta(milliseconds=ms))
  • _format_elapsed_since(start)_format_timedelta(datetime.now(tz=UTC) - ensure_aware(start))
  • _format_detail_duration(start, end) → return "—" if None, else _format_timedelta(end - start)
  1. format_duration is in __all__ (public API) — keep its signature unchanged.

Tests

Existing tests for format_duration must continue to pass. Add a test for _format_timedelta directly to verify the core logic. Verify _format_elapsed_since and _format_detail_duration still produce the same outputs.

Metadata

Metadata

Assignees

No one assigned

    Labels

    awCreated by agentic workflowaw-dispatchedIssue has been dispatched to implementer

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions