Humanizer (Humanizer v0.2.0)

Copy Markdown View Source

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:

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 (:precision to override).
  • No scientific notation, ever. Numbers are formatted from integers, so values up to 10 ** 15 never 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

bytes(n, opts \\ [])

@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 (default 1)

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"

delimit(n, opts \\ [])

@spec delimit(
  number(),
  keyword()
) :: String.t()

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"

duration(seconds, opts \\ [])

@spec duration(
  number(),
  keyword()
) :: String.t()

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 (default 2)
  • :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"

list_join(items, opts \\ [])

@spec list_join(
  [String.t()],
  keyword()
) :: String.t()

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 (default false). 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"

number(n, opts \\ [])

@spec number(
  number(),
  keyword()
) :: String.t()

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 (default 1)

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"

ordinal(n)

@spec ordinal(integer()) :: String.t()

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"

relative_time(datetime, now_or_opts \\ [])

@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 uses m for minutes and mo for 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"

relative_time(datetime, now, opts)

@spec relative_time(DateTime.t(), DateTime.t(), keyword()) :: String.t()

truncate(text, length, opts \\ [])

@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, :word cuts 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..."