humanize formats values as human-readable text. pip install humanize. File size: import humanize; humanize.naturalsize(1_500_000) → “1.5 MB”. humanize.naturalsize(1_500_000, binary=True) → “1.4 MiB”. naturalsize(1234567890) → “1.2 GB”. Number: humanize.intcomma(1234567) → “1,234,567”. humanize.intword(1_200_000) → “1.2 million”. humanize.intword(12_000_000_000) → “12.0 billion”. humanize.apnumber(5) → “five”. humanize.apnumber(12) → “12”. humanize.ordinal(1) → “1st”. humanize.ordinal(21) → “21st”. humanize.fractional(0.333) → “1/3”. Metric: humanize.metric(1500) → “1.50 k”. humanize.metric(0.001) → “1.00 m”. Scientific: humanize.scientific(0.00123) → “1.23 x 10⁻³”. Time: humanize.naturaltime(datetime.now() - timedelta(seconds=30)) → “30 seconds ago”. humanize.naturaltime(future) → “in 2 hours”. humanize.naturalday(date.today() - timedelta(days=1)) → “yesterday”. humanize.naturaldate(date(2024,1,5)) → “January 5”. humanize.naturalday(date.today()) → “today”. Duration: humanize.precisedelta(timedelta(hours=2, minutes=15)) → “2 hours and 15 minutes”. humanize.precisedelta(timedelta(seconds=90), minimum_unit="seconds"). Locale: humanize.i18n.activate("de_DE"). humanize.intcomma(1234) → “1.234”. Deactivate: humanize.i18n.deactivate(). Claude Code generates humanize formatters, Jinja2 filters, and API response serializers.
CLAUDE.md for humanize
## humanize Stack
- Version: humanize >= 4.9 | pip install humanize
- Sizes: humanize.naturalsize(bytes) | naturalsize(bytes, binary=True) for MiB
- Numbers: intcomma(n) | intword(n) → "1.2 million" | ordinal(n) → "1st"
- Time: naturaltime(dt) → "3 minutes ago" | naturalday(d) → "yesterday"
- Duration: precisedelta(timedelta) → "2 hours and 15 minutes"
- Locale: humanize.i18n.activate("de_DE") — use with context manager
- Jinja2: register as custom filter: env.filters["naturalsize"] = humanize.naturalsize
humanize Formatting Pipeline
# app/format_utils.py — humanize formatters for API responses and templates
from __future__ import annotations
from datetime import date, datetime, timedelta, timezone
from typing import Any
import humanize
# ─────────────────────────────────────────────────────────────────────────────
# 1. File and data sizes
# ─────────────────────────────────────────────────────────────────────────────
def format_size(
bytes: int,
binary: bool = False,
gnu: bool = False,
minimum_unit: str = "Byte",
) -> str:
"""
naturalsize(1234567) → "1.2 MB" (SI prefixes, base-10)
naturalsize(1234567, binary=True) → "1.2 MiB" (IEC prefixes, base-2)
naturalsize(1234567, gnu=True) → "1.2M" (single-letter, like ls -lh)
"""
return humanize.naturalsize(bytes, binary=binary, gnu=gnu, minimum_unit=minimum_unit)
def demo_sizes() -> None:
sizes = [512, 1_024, 10_000, 1_048_576, 1_073_741_824, 1_099_511_627_776]
for b in sizes:
si = humanize.naturalsize(b)
iec = humanize.naturalsize(b, binary=True)
gnu = humanize.naturalsize(b, gnu=True)
print(f" {b:>15,} → {si:>12} {iec:>12} {gnu:>8}")
# ─────────────────────────────────────────────────────────────────────────────
# 2. Numbers
# ─────────────────────────────────────────────────────────────────────────────
def demo_numbers() -> None:
print("intcomma: ", humanize.intcomma(1_234_567)) # 1,234,567
print("intword: ", humanize.intword(1_200_000)) # 1.2 million
print("intword: ", humanize.intword(42_000_000_000)) # 42.0 billion
print("apnumber: ", humanize.apnumber(3)) # three
print("apnumber: ", humanize.apnumber(11)) # 11
print("ordinal 1: ", humanize.ordinal(1)) # 1st
print("ordinal 2: ", humanize.ordinal(2)) # 2nd
print("ordinal 21: ", humanize.ordinal(21)) # 21st
print("ordinal 11: ", humanize.ordinal(11)) # 11th
print("fractional: ", humanize.fractional(0.333)) # 1/3
print("fractional: ", humanize.fractional(0.5)) # 1/2
print("metric 1500:", humanize.metric(1500)) # 1.50 k
print("metric .001:", humanize.metric(0.001)) # 1.00 m
print("scientific: ", humanize.scientific(0.00123)) # 1.23 x 10⁻³
print("clamp: ", humanize.clamp(50, minimum=0, maximum=100)) # 50
# ─────────────────────────────────────────────────────────────────────────────
# 3. Relative time
# ─────────────────────────────────────────────────────────────────────────────
def format_relative_time(dt: datetime) -> str:
"""
naturaltime converts a datetime to "X ago" or "in X".
Passing a timezone-aware datetime works correctly.
"""
return humanize.naturaltime(dt)
def demo_times() -> None:
now = datetime.now()
cases = [
now - timedelta(seconds=5),
now - timedelta(minutes=2),
now - timedelta(hours=3),
now - timedelta(days=1),
now - timedelta(weeks=2),
now + timedelta(minutes=15),
now + timedelta(hours=2),
now + timedelta(days=3),
]
for dt in cases:
print(f" {str(dt - now):>20} → {humanize.naturaltime(dt)}")
# ─────────────────────────────────────────────────────────────────────────────
# 4. Calendar dates
# ─────────────────────────────────────────────────────────────────────────────
def demo_dates() -> None:
today = date.today()
cases = [
today,
today - timedelta(days=1),
today + timedelta(days=1),
today - timedelta(days=3),
date(2024, 1, 5),
date(2020, 3, 15),
]
for d in cases:
print(f" {d} → naturalday={humanize.naturalday(d):15} naturaldate={humanize.naturaldate(d)}")
# ─────────────────────────────────────────────────────────────────────────────
# 5. Duration formatting
# ─────────────────────────────────────────────────────────────────────────────
def format_duration(td: timedelta, minimum_unit: str = "seconds") -> str:
"""
precisedelta formats a timedelta verbosely: "2 hours, 15 minutes and 30 seconds".
Use suppress=["seconds"] to omit small units when precision isn't needed.
"""
return humanize.precisedelta(td, minimum_unit=minimum_unit)
def demo_durations() -> None:
deltas = [
timedelta(seconds=45),
timedelta(minutes=90),
timedelta(hours=2, minutes=15, seconds=30),
timedelta(days=3, hours=4),
timedelta(weeks=2, days=3),
]
for td in deltas:
short = humanize.precisedelta(td)
long_ = humanize.precisedelta(td, suppress=[], minimum_unit="seconds")
print(f" {str(td):>25} → {short}")
# ─────────────────────────────────────────────────────────────────────────────
# 6. API response serializer
# ─────────────────────────────────────────────────────────────────────────────
def humanize_record(record: dict) -> dict:
"""
Transform a raw database record into a display-ready dict with
human-readable values alongside the raw values.
"""
result = dict(record)
# Bytes → readable size
if "file_size" in record and isinstance(record["file_size"], int):
result["file_size_human"] = humanize.naturalsize(record["file_size"])
# Datetime → relative time
if "created_at" in record and isinstance(record["created_at"], datetime):
result["created_at_human"] = humanize.naturaltime(record["created_at"])
result["created_at_date"] = humanize.naturaldate(record["created_at"].date())
# Large counts
if "download_count" in record and isinstance(record["download_count"], int):
result["download_count_human"] = humanize.intcomma(record["download_count"])
if record["download_count"] >= 1_000_000:
result["download_count_word"] = humanize.intword(record["download_count"])
# Order number with ordinal
if "rank" in record and isinstance(record["rank"], int):
result["rank_ordinal"] = humanize.ordinal(record["rank"])
return result
# ─────────────────────────────────────────────────────────────────────────────
# 7. Jinja2 template filters
# ─────────────────────────────────────────────────────────────────────────────
def register_humanize_filters(env) -> None:
"""
Register humanize functions as Jinja2 template filters.
Usage in templates: {{ file.size | naturalsize }}
{{ post.created_at | naturaltime }}
{{ user.rank | ordinal }}
"""
env.filters["naturalsize"] = humanize.naturalsize
env.filters["naturaltime"] = humanize.naturaltime
env.filters["naturalday"] = humanize.naturalday
env.filters["naturaldate"] = humanize.naturaldate
env.filters["precisedelta"] = humanize.precisedelta
env.filters["intcomma"] = humanize.intcomma
env.filters["intword"] = humanize.intword
env.filters["ordinal"] = humanize.ordinal
env.filters["apnumber"] = humanize.apnumber
# ─────────────────────────────────────────────────────────────────────────────
# 8. Locale-aware formatting
# ─────────────────────────────────────────────────────────────────────────────
def demo_locales() -> None:
"""
humanize.i18n.activate changes the locale for number/date formatting.
Always deactivate after use (or use a context manager).
"""
from contextlib import contextmanager
@contextmanager
def locale(lang: str):
humanize.i18n.activate(lang)
try:
yield
finally:
humanize.i18n.deactivate()
for lang in ["en_US", "de_DE", "ru_RU", "fr_FR"]:
try:
with locale(lang):
print(f" [{lang}] intcomma(1234567)={humanize.intcomma(1_234_567)} "
f"naturalday(yesterday)={humanize.naturalday(date.today() - timedelta(days=1))}")
except Exception as e:
print(f" [{lang}] locale not available: {e}")
# ─────────────────────────────────────────────────────────────────────────────
# Demo
# ─────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
print("=== Sizes ===")
demo_sizes()
print("\n=== Numbers ===")
demo_numbers()
print("\n=== Relative times ===")
demo_times()
print("\n=== Calendar dates ===")
demo_dates()
print("\n=== Durations ===")
demo_durations()
print("\n=== Record serializer ===")
record = {
"name": "data.csv",
"file_size": 15_728_640,
"created_at": datetime.now() - timedelta(hours=3),
"download_count": 1_234_567,
"rank": 3,
}
human = humanize_record(record)
for k, v in sorted(human.items()):
print(f" {k:30}: {v}")
print("\n=== Locales ===")
demo_locales()
For the f"{value:,.0f}" format string alternative — Python’s f"{bytes:,}" adds commas to integers but doesn’t know whether 1_048_576 is “1 MB” or “1 MiB”, f"{dt:%Y-%m-%d %H:%M}" outputs absolute timestamps but not “3 minutes ago”, and format strings produce no ordinal suffix or AP-style number words, while humanize.naturalsize() picks the right SI or IEC unit automatically, humanize.naturaltime() returns “3 minutes ago” or “in 2 hours” relative to now, and humanize.ordinal(3) returns “3rd” — the presentation library that removes all the edge-case logic from your template or serializer code. For the arrow / pendulum alternative — arrow and pendulum are datetime manipulation libraries that create timezone-aware datetime objects and expose .humanize() as a chainable method, requiring the data to be stored as arrow/pendulum objects, while humanize works on standard datetime.datetime and datetime.timedelta objects and adds naturaltime() / precisedelta() as standalone formatting functions that accept any Python datetime. The Claude Skills 360 bundle includes humanize skill sets covering naturalsize with SI and binary modes, intcomma/intword/apnumber/ordinal/fractional, naturaltime for relative timestamps, naturalday/naturaldate for calendar display, precisedelta for verbose duration strings, metric and scientific notation, humanize_record for API response enrichment, Jinja2 filter registration, locale activation with i18n.activate, and Django template tag integration. Start with the free tier to try human-readable formatting code generation.