repomatic.humanize module

Conversions between raw machine quantities and their human forms.

Byte counts, modification times and service timestamps reach the user through more than one surface (the image-optimization summary, the repomatic cache tables, every dependency report), and each surface should spell them the same way. One home for those conversions keeps the wording consistent and keeps the formatters out of the modules that merely happen to be the first consumer; parse_iso_datetime() is the machine-to-datetime half the renderers start from.

A leaf module with no project-internal imports, so any module can import it without risking a cycle.

repomatic.humanize.SECONDS_PER_DAY = 86400

Divisor turning an mtime delta into whole days.

repomatic.humanize.parse_iso_datetime(value)[source]

Parse an ISO 8601 / RFC 3339 timestamp into a timezone-aware datetime.

The package-wide parser for any timestamp an external service writes: repomatic.deps.dep_report reads PyPI upload times, repomatic.deps.uv lock timestamps, repomatic.cloudflare token expiries and repomatic.github.job_timings job clocks through it, so every consumer tolerates the same shapes.

Uses arrow, so a nanosecond fractional second and a Z suffix (both of which Python 3.10’s stdlib datetime.fromisoformat rejects) parse cleanly; sub-microsecond precision is truncated to fit datetime.

arrow also supplies the .humanize() relative-time phrasing that format_countdown() and format_elapsed() render.

Todo

Switch this parser back to whenever, the prior implementation, once it grows a humanizer: whenever#277.

Parameters:

value (str) – An ISO 8601 / RFC 3339 instant, or empty.

Return type:

datetime | None

Returns:

A timezone-aware datetime, or None when value is empty or not a valid instant.

repomatic.humanize.utc_midnight(day)[source]

The UTC instant day starts on.

What a date-granular datasource hands format_countdown() and format_elapsed(), which take instants so no caller can silently drop a precision it holds. A date holds none to drop: midnight is the whole of what it says.

Return type:

datetime

repomatic.humanize.format_countdown(deadline, reference)[source]

Render deadline as a date, with a countdown to it.

The countdown measures the two instants, not their calendar dates. A deadline later today then reads in 5 hours, where truncating to dates rendered just now: a reader takes that for something already elapsed, the opposite of what the line reports.

Both parameters are instants for that reason, and a date is rejected by the type checker. Every deadline this renders is computed at instant granularity (uv records a package upload-time to the second, and a cooldown cutoff is now - span), so a caller holding that precision must not drop it. A date-granular datasource passes the UTC midnight its date starts on through utc_midnight(), on both arguments: an instant measured against a midnight reference is off by up to a day.

Parameters:
  • deadline (datetime) – The instant counted down to.

  • reference (datetime) – The current instant, for the relative offset.

Return type:

str

Returns:

A string like 2026-06-25 (in 4 days) or 2026-06-25 (in 5 hours), or the bare date once the deadline has passed.

repomatic.humanize.format_elapsed(instant, reference)[source]

Render instant as a date, with how long ago it happened.

The mirror of format_countdown(), for a date a reader looks back at rather than waits for: a package upload, a release, a recorded reading. The same precision rules apply, since the two read the same gap.

Each drops the phrase in the direction it cannot speak for. A countdown says nothing about a deadline already passed, and this says nothing about an instant still ahead, where in 3 days would contradict the column it sits in. Both then render the bare date.

Parameters:
  • instant (datetime) – The instant being looked back at.

  • reference (datetime) – The current instant, for the relative offset.

Return type:

str

Returns:

A string like 2026-06-24 (2 days ago) or 2026-06-24 (6 hours ago), or the bare date while instant is still ahead.

repomatic.humanize.format_file_size(size_bytes)[source]

Format a byte count as a human-readable string.

A thin binding of click_extra.format_size() to the JEDEC unit style (binary powers with the customary KB/MB symbols), matching the format produced by calibreapp/image-actions.

Return type:

str

repomatic.humanize.format_age(mtime)[source]

Format a file mtime as a human-readable age string.

Rounds down to whole days, since the cache tables it feeds exist to answer “is this stale?”, not to time anything precisely.

Parameters:

mtime (float) – POSIX timestamp, as returned by Path.stat().st_mtime.

Return type:

str

Returns:

"today", "1 day", or "{n} days".