Python Humanize: Format File Sizes, Numbers, and Times
python humanize format file sizes numbers and times: Learn to use the humanize library to turn raw bytes, large numbers, and timestamps into readable strings like 1.5...
python humanize format file sizes numbers and times requires a clear understanding of the core syntax, runtime behavior, and practical implementation patterns demonstrated in the examples below.
When a UI shows 1536000 as a file size, 1623456789 as a timestamp, or 1200000 as a download count, the values are accurate but practically unreadable. The humanize package converts raw numbers into the strings users expect: 1.5 MB, 2 hours ago, and 1.2 million. This article covers how to use python humanize to format file sizes, numbers, and times, and where the defaults of each function change the output.
Installing humanize and Importing the Functions
humanize is a small library with no required dependencies for its core formatting functions. Install it with pip:
pip install humanize
The package exposes one function per formatting concern. Import only the functions you need:
from humanize import naturalsize, intword, intcomma, naturaltime, precisedelta
Importing the whole module with import humanize also works and is convenient when you use many functions in one module. The functions are pure: they take a numeric or datetime value and return a string, so they are safe to call from anywhere in the application.
Formatting File Sizes with naturalsize
naturalsize converts a byte count into a unit-labeled string:
humanize.naturalsize(1536000) # '1.5 MB' humanize.naturalsize(1000) # '1.0 kB' humanize.naturalsize(0) # '0 bytes'
The default behavior uses base 1000, matching the SI convention where 1 kB is 1000 bytes. Storage vendors and network tools commonly use this convention. If your application measures memory or disk in binary units, pass binary=True:
humanize.naturalsize(1536000, binary=True) # '1.5 MiB' humanize.naturalsize(1024, binary=True) # '1.0 KiB'
The gnu=True option produces the short binary form used by GNU tools such as ls -lh:
humanize.naturalsize(1536000, gnu=True) # '1.5M'
You can control the number of decimals with the format argument:
humanize.naturalsize(1536000, format="%.2f") # '1.54 MB'
Negative values keep their sign: naturalsize(-2048) returns -2.0 kB. The singular form 1 byte is used for exactly one byte, and zero is reported as 0 bytes.
Formatting Large Numbers with intword and intcomma
For counts, totals, and metrics, intword turns large integers into words:
humanize.intword(1200000) # '1.2 million' humanize.intword(1500000000) # '1.5 billion' humanize.intword(-2500000) # '-2.5 million'
intcomma inserts thousands separators without changing the value:
humanize.intcomma(1234567) # '1,234,567' humanize.intcomma(9876543.21) # '9,876,543.21'
Use intcomma when the exact value matters and the number is small enough to read with separators. Use intword when the magnitude is the point of the display, such as "1.2 million downloads". For ordinal positions, ordinal appends the correct suffix:
humanize.ordinal(1) # '1st' humanize.ordinal(23) # '23rd' humanize.ordinal(112) # '112th'
Formatting Times and Dates with naturaltime
naturaltime converts a datetime or timedelta into a relative phrase:
from datetime import datetime, timedelta past = datetime.now() - timedelta(hours=2) future = datetime.now() + timedelta(days=3) humanize.naturaltime(past) # '2 hours ago' humanize.naturaltime(future) # '3 days from now'
Recent values collapse to now. The threshold depends on the installed version; older releases used a fixed future parameter, while newer ones detect the direction from the value itself. If you rely on this behavior, pin the version you test against.
naturalday narrows the display to the current week:
humanize.naturalday(datetime.now()) # 'today' humanize.naturalday(datetime.now() - timedelta(days=1)) # 'yesterday'
naturaldate falls back to a formatted date when the value is not within the current week:
humanize.naturaldate(datetime(2023, 5, 4)) # 'May 04'
Timezone handling matters here. When you pass a naive datetime, naturaltime compares it against the local system time. When you pass a timezone-aware datetime, it compares against UTC. Mixing naive and aware values in the same code path produces offsets that look like bugs. If your application stores timestamps in UTC, convert them to aware datetimes before calling naturaltime, or normalize everything to local time first.
Using precisedelta for Exact Durations
naturaltime is for relative moments. For exact durations, precisedelta breaks a timedelta into components:
humanize.precisedelta(timedelta(hours=1, minutes=2, seconds=3)) # '1 hour, 2 minutes and 3 seconds'
The minimum_unit argument controls the smallest unit shown:
humanize.precisedelta(timedelta(minutes=5, seconds=45), minimum_unit="minutes") # '5 minutes'
This is useful for logs, job runtimes, and ETL reporting where a rounded relative phrase is not specific enough.
Combining Formatting Functions in an Output Layer
The functions compose cleanly. A file listing view can format size, count, and modification time in one place:
def describe_file(path): stat = path.stat() return { "size": humanize.naturalsize(stat.st_size, binary=True), "modified": humanize.naturaltime(datetime.fromtimestamp(stat.st_mtime)), } def describe_directory(path): entries = list(path.iterdir()) total = sum(e.stat().st_size for e in entries) return { "items": humanize.intword(len(entries)), "total_size": humanize.naturalsize(total, binary=True), }
Keeping display formatting in one module means the same unit conventions and timezone rules apply everywhere. If the product later switches from binary to decimal file sizes, the change happens in one function instead of across every template or API response.
Performance and Maintainability Considerations
The formatting functions are pure string operations; each call does a small amount of arithmetic and formatting. For a list of a few thousand rows, the cost is negligible compared to the I/O that produced the data. If you are formatting millions of values, precompute the formatted strings once and reuse them instead of calling the functions repeatedly inside a hot loop.
Localization is the main operational consideration. humanize ships locale files, and activating a locale has a one-time loading cost. If your application serves multiple languages, load the locale once at startup and keep the formatted output in the presentation layer, not in persisted data. Storing formatted strings in a database makes later re-formatting or locale changes impossible without a migration.
The largest compatibility risk is version drift. The behavior of naturaltime for future dates changed across releases, and the exact wording of unit labels can differ between versions. Add a test that asserts the output for a fixed input, such as naturalsize(1024, binary=True) == "1.0 KiB", so an upgrade that changes wording fails loudly instead of silently altering every page.