Human-friendly formatting helpers for Elixir.
A small, flat set of pure functions that turn raw values into the kind of strings you show to people:
bytes/2—2_456_789→"2.5 MB"duration/2—3725→"1 hour, 2 minutes"relative_time/3— aDateTime→"2 days ago"number/2—1_234_567→"1.2M"delimit/2—1_234_567→"1,234,567"ordinal/1—23→"23rd"truncate/3—"the quick brown fox", 9→"the quic…"list_join/2—["Alice", "Bob", "Charlie"]→"Alice, Bob and Charlie"
Design
- English only. No localization in v0.1. For real i18n use
ex_cldr. - No global config. Every option is a keyword passed to the function.
- One rounding rule. Decimal output uses round-half-away-from-zero with a
single fractional digit by default (
:precisionto override). - No scientific notation, ever. Numbers are formatted from integers, so
values up to
10 ** 15never come out as"1.0e15".
Summary
Functions
Formats a byte count as a human-readable size.
Inserts a thousands separator into a number.
Formats a duration given in seconds as words.
Joins a list of strings into a human-readable enumeration.
Formats a number with a magnitude suffix (K, M, B, T).
Returns the integer with its English ordinal suffix.
Formats a DateTime relative to now (or to a given reference time).
Truncates a string to at most length characters, appending an omission marker.
Functions
@spec bytes( non_neg_integer(), keyword() ) :: String.t()
Formats a byte count as a human-readable size.
Uses decimal (SI, base 1000) units by default. Pass system: :binary for
IEC (base 1024) units. Precision defaults to one fractional digit.
Options
:system—:decimal(default) or:binary:precision— number of fractional digits (default1)
Examples
iex> Humanizer.bytes(2_456_789)
"2.5 MB"
iex> Humanizer.bytes(2_456_789, system: :binary)
"2.3 MiB"
iex> Humanizer.bytes(2_456_789, precision: 2)
"2.46 MB"
iex> Humanizer.bytes(0)
"0 B"
Inserts a thousands separator into a number.
Groups the integer part in threes. Integers render with no fractional part;
floats keep their own decimals. Pass :precision to force a fixed number of
fractional digits (handy for currency). Output is never in scientific notation.
Options
:separator— the grouping separator (default","):precision— fixed number of fractional digits (default: keep the value's natural decimals; integers show none)
Examples
iex> Humanizer.delimit(1_234_567)
"1,234,567"
iex> Humanizer.delimit(1_234_567, separator: " ")
"1 234 567"
iex> Humanizer.delimit(1234.5, precision: 2)
"1,234.50"
iex> Humanizer.delimit(-1000)
"-1,000"
Formats a duration given in seconds as words.
Breaks the duration down into days, hours, minutes and seconds (months and years are out of scope in v0.1). By default the two most significant non-zero units are shown.
Options
:units— number of units to show, or:all(default2):format—:long(default,"1 hour") or:short("1h")
Raises ArgumentError for negative input.
Examples
iex> Humanizer.duration(3725)
"1 hour, 2 minutes"
iex> Humanizer.duration(3725, units: 1)
"1 hour"
iex> Humanizer.duration(3725, units: :all)
"1 hour, 2 minutes, 5 seconds"
iex> Humanizer.duration(45, format: :short)
"45s"
Joins a list of strings into a human-readable enumeration.
Options
:conjunction— word before the last item (default"and"):oxford— add a serial comma before the conjunction (defaultfalse). Has no effect on two-item lists.:max— show at most this many items, collapsing the rest into a count ("Alice, Bob and 3 others"). Only applies when the list is longer.:other/:others— singular/plural noun for the collapsed remainder (defaults"other"/"others")
Examples
iex> Humanizer.list_join(["Alice", "Bob", "Charlie"])
"Alice, Bob and Charlie"
iex> Humanizer.list_join(["Alice", "Bob"])
"Alice and Bob"
iex> Humanizer.list_join(["Alice", "Bob", "Charlie"], oxford: true)
"Alice, Bob, and Charlie"
iex> Humanizer.list_join(["Alice", "Bob", "Charlie"], conjunction: "or")
"Alice, Bob or Charlie"
iex> Humanizer.list_join(["Alice", "Bob", "Charlie", "Dave", "Eve"], max: 2)
"Alice, Bob and 3 others"
Formats a number with a magnitude suffix (K, M, B, T).
Accepts integers and floats, positive or negative. Values below 1000 are
rendered without a suffix or decimals. The ceiling in v0.1 is trillions (T).
Options
:precision— number of fractional digits for suffixed values (default1)
Examples
iex> Humanizer.number(1_234)
"1.2K"
iex> Humanizer.number(1_234_567)
"1.2M"
iex> Humanizer.number(999)
"999"
iex> Humanizer.number(-1_234, precision: 2)
"-1.23K"
Returns the integer with its English ordinal suffix.
Correctly handles the 11/12/13 exceptions and negative numbers.
Examples
iex> Humanizer.ordinal(1)
"1st"
iex> Humanizer.ordinal(11)
"11th"
iex> Humanizer.ordinal(23)
"23rd"
iex> Humanizer.ordinal(-1)
"-1st"
@spec relative_time( DateTime.t(), keyword() ) :: String.t()
Formats a DateTime relative to now (or to a given reference time).
Past times read as "X ago", future times as "in X", and anything within a
minute as "just now". Both arguments are compared as absolute instants, so
time zones are handled correctly. Units range from minutes up to years; weeks,
months and years are coarse fixed-width approximations (7 / 30 / 365 days) for
display only — for exact calendar math use Date/DateTime directly.
Options
:format—:long(default,"2 days ago") or:short("2d ago","in 3h","now"). Short usesmfor minutes andmofor months.
Examples
iex> Humanizer.relative_time(~U[2026-05-13 10:00:00Z], ~U[2026-05-15 10:00:00Z])
"2 days ago"
iex> Humanizer.relative_time(~U[2026-05-15 13:00:00Z], ~U[2026-05-15 10:00:00Z])
"in 3 hours"
iex> Humanizer.relative_time(~U[2026-05-15 10:00:00Z], ~U[2026-05-15 10:00:00Z])
"just now"
iex> Humanizer.relative_time(~U[2026-04-01 10:00:00Z], ~U[2026-05-15 10:00:00Z])
"1 month ago"
iex> Humanizer.relative_time(~U[2026-05-13 10:00:00Z], ~U[2026-05-15 10:00:00Z], format: :short)
"2d ago"
@spec relative_time(DateTime.t(), DateTime.t(), keyword()) :: String.t()
@spec truncate(String.t(), non_neg_integer(), keyword()) :: String.t()
Truncates a string to at most length characters, appending an omission marker.
Counts graphemes, so it is Unicode-safe. If the text already fits within
length, it is returned unchanged. With break: :word the cut is moved back
to the previous whitespace boundary so words are not split mid-way. The
omission marker counts toward length.
Options
:omission— marker appended when text is cut (default"…"):break—:char(default) cuts anywhere,:wordcuts on whitespace
Examples
iex> Humanizer.truncate("the quick brown fox", 9)
"the quic…"
iex> Humanizer.truncate("the quick brown fox", 9, break: :word)
"the…"
iex> Humanizer.truncate("hi there", 20)
"hi there"
iex> Humanizer.truncate("the quick brown fox", 12, omission: "...")
"the quick..."