humanize

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 15, 2026 License: MIT Imports: 6 Imported by: 0

README

Go Humanize Library

go-humanize formats machine values into human-readable English.

It stays intentionally narrow: bytes, durations, relative time, numbers, ordinals, percentages, and count phrases. No builders, no option structs, no locale layer, and no runtime dependencies.

Humanized text is for display only. Never feed humanized text back into protocols, schedulers, billing, or audit. Use raw integers, time.Time, or time.Duration for those.

Installation

go get github.com/agentable/go-humanize

Requires Go 1.26.2.

Quick Taste

package main

import (
	"fmt"
	"time"

	"github.com/agentable/go-humanize"
)

func main() {
	ref := time.Date(2024, 2, 24, 12, 0, 0, 0, time.UTC)

	fmt.Println(humanize.Bytes(1500))                        // "1.5 KB"
	fmt.Println(humanize.BinaryBytes(1536))                  // "1.5 KiB"
	fmt.Println(humanize.Relative(ref.Add(-2*time.Hour), ref)) // "2 hours ago"
	fmt.Println(humanize.Number(1234567))                    // "1,234,567"
	fmt.Println(humanize.Percent(0.333))                     // "33.3%"
	fmt.Println(humanize.Count(1000, "item", "items"))       // "1,000 items"
}

API

  • Bytes(int64) string — decimal units (KB, MB, GB, ...)
  • BinaryBytes(int64) string — IEC binary units (KiB, MiB, GiB, ...)
  • Number(int64) string — comma-separated integers
  • Percent(float64) string — takes a fraction; 0.333"33.3%"
  • Duration(time.Duration) string — at most two descending units
  • Relative(target, ref time.Time) string — relative-time formatter
  • Ordinal(int64) string — English ordinal suffix
  • Count(int64, singular, plural string) string — count phrase
  • ParseBytes(string) (int64, error) — inverse of Bytes / BinaryBytes
  • ParseDuration(string) (time.Duration, error) — inverse of Duration
  • ErrInvalid — sentinel returned by parse failures

Behavior

Bytes
  • Bytes is decimal by default because Finder, AWS/GCP consoles, and most user-facing products use decimal byte units.
  • BinaryBytes is for the developer/sysadmin context (memory, block devices).
  • Output uses at most one decimal place.
  • ParseBytes accepts only text that round-trips through Bytes or BinaryBytes unchanged. The parsed value represents the nearest byte described by the display text, not the original source value before formatting.
Duration
  • Duration shows at most two descending units.
  • Days are treated as 24 hours.
  • Sub-microsecond durations collapse to "0s".
  • ParseDuration accepts only text that round-trips through Duration unchanged. Day units (d) are supported because time.ParseDuration cannot parse them.
Relative
  • Relative(target, ref) always requires an explicit reference time. There is no implicit time.Now() overload.
  • Cutovers: 60s → minute, 60m → hour, 24h → day, 7d → week, 30d → month, 345d → year. Months are 30d; years are 365d.
Percent
  • Percent takes a fraction: Percent(0.333)"33.3%", Percent(1)"100%". Values above 1 are not clamped.
  • NaN and infinities render as "NaN%", "+Inf%", "-Inf%".
Errors
  • Parse failures wrap ErrInvalid. Use errors.Is(err, humanize.ErrInvalid).

Non-Goals

  • Locale-aware formatting
  • Alternate separator APIs
  • Precision knobs like BytesN
  • Friendly calendar words like yesterday or tomorrow
  • Text utilities, truncation helpers, or string humanizers
  • Builders, config structs, or formatting options

Why It Stays Small

Other humanize libraries grow into broad utility bundles. This package does the opposite on purpose: one obvious entry point per need, a short list of exported symbols, and a behavior contract that can be explained on one screen.

Development

task test          # Run all tests with race detection
task lint          # Run golangci-lint v2.11.4 + go mod tidy check
task fmt           # Format code
task vet           # Run go vet
task verify        # Full verification: deps, fmt, vet, lint, test
task deps          # Download and tidy dependencies
task clean         # Clean build artifacts and caches

License

Released under the MIT License.

Documentation

Overview

Package humanize formats machine values into human-readable English.

It is English-only, stateless, display-side helpers. It does not do internationalization, calendar math, unit conversion, or protocol fields. Humanized text is for display only; never feed it back into protocols, schedulers, billing, or audit.

API

  • Bytes formats with decimal units (KB, MB, GB, ...).
  • BinaryBytes formats with IEC binary units (KiB, MiB, GiB, ...).
  • Number formats integers with comma separators.
  • Percent takes a fraction: Percent(0.333) returns "33.3%".
  • Duration shows at most two descending units: "1h 30m", "2d 5h".
  • Relative formats a target time relative to a reference time.
  • Ordinal adds an English ordinal suffix: "1st", "23rd".
  • Count joins a count and an English noun: "1,000 items".
  • ParseBytes and ParseDuration are the inverses of Bytes / BinaryBytes and Duration; they accept canonical output only.

Relative time cutovers

Relative uses approximate buckets, not calendar math. Months are 30 days, years are 365 days, days are 24 hours.

|diff| < 1s        just now
[1s,   60s)        N seconds ago / in N seconds
[60s,  60m)        N minutes ago / in N minutes
[60m,  24h)        N hours ago   / in N hours
[24h,   7d)        N days ago    / in N days
[7d,   30d)        N weeks ago   / in N weeks
[30d, 345d)        N months ago  / in N months
>= 345d            N years ago   / in N years

Errors

Format functions never return errors. Parse failures wrap ErrInvalid:

if _, err := humanize.ParseBytes(s); errors.Is(err, humanize.ErrInvalid) {
	// handle malformed input
}

Index

Examples

Constants

This section is empty.

Variables

View Source
var ErrInvalid = errors.New("invalid input")

ErrInvalid reports malformed ParseBytes or ParseDuration input.

Functions

func BinaryBytes

func BinaryBytes(b int64) string

BinaryBytes formats b with IEC binary byte units such as "1.5 KiB" and "2 MiB". It preserves the sign and saturates math.MinInt64 to math.MaxInt64 when taking the absolute value.

Example
package main

import (
	"fmt"

	"github.com/agentable/go-humanize"
)

func main() {
	const gibibyte = 1024 * 1024 * 1024

	fmt.Println(humanize.BinaryBytes(0))
	fmt.Println(humanize.BinaryBytes(1536))
	fmt.Println(humanize.BinaryBytes(1048576))
	fmt.Println(humanize.BinaryBytes(5 * gibibyte))
}
Output:
0 B
1.5 KiB
1 MiB
5 GiB

func Bytes

func Bytes(b int64) string

Bytes formats b with decimal byte units such as "1.5 KB" and "2 MB". It preserves the sign and saturates math.MinInt64 to math.MaxInt64 when taking the absolute value.

Example
package main

import (
	"fmt"

	"github.com/agentable/go-humanize"
)

func main() {
	const gigabyte = 1000 * 1000 * 1000

	fmt.Println(humanize.Bytes(0))
	fmt.Println(humanize.Bytes(1500))
	fmt.Println(humanize.Bytes(1000000))
	fmt.Println(humanize.Bytes(5 * gigabyte))
}
Output:
0 B
1.5 KB
1 MB
5 GB

func Count

func Count(count int64, singular, plural string) string

Count formats count with Number's comma separators and chooses singular when count is ±1.

Example
package main

import (
	"fmt"

	"github.com/agentable/go-humanize"
)

func main() {
	fmt.Println(humanize.Count(1, "item", "items"))
	fmt.Println(humanize.Count(0, "item", "items"))
	fmt.Println(humanize.Count(1000, "item", "items"))
	fmt.Println(humanize.Count(-1, "person", "people"))
}
Output:
1 item
0 items
1,000 items
-1 person

func Duration

func Duration(d time.Duration) string

Duration formats d with up to two significant units such as "1h 30m" and "2d 5h". It preserves the sign, treats a day as 24 hours, uses ms and µs below a second, and returns "0s" for zero.

Example
package main

import (
	"fmt"
	"time"

	"github.com/agentable/go-humanize"
)

func main() {
	fmt.Println(humanize.Duration(0))
	fmt.Println(humanize.Duration(500 * time.Millisecond))
	fmt.Println(humanize.Duration(90 * time.Minute))
	fmt.Println(humanize.Duration(29 * time.Hour))
}
Output:
0s
500ms
1h 30m
1d 5h

func Number

func Number(n int64) string

Number formats n as a human-readable English integer with comma separators every three digits, such as "1,234,567" and "-1,000,000".

Example
package main

import (
	"fmt"

	"github.com/agentable/go-humanize"
)

func main() {
	fmt.Println(humanize.Number(0))
	fmt.Println(humanize.Number(1234567))
	fmt.Println(humanize.Number(-1000000))
	fmt.Println(humanize.Number(123456789))
}
Output:
0
1,234,567
-1,000,000
123,456,789

func Ordinal

func Ordinal(n int64) string

Ordinal formats n with an English ordinal suffix such as "1st", "23rd", and "-3rd".

Example
package main

import (
	"fmt"

	"github.com/agentable/go-humanize"
)

func main() {
	fmt.Println(humanize.Ordinal(1))
	fmt.Println(humanize.Ordinal(2))
	fmt.Println(humanize.Ordinal(3))
	fmt.Println(humanize.Ordinal(11))
	fmt.Println(humanize.Ordinal(21))
	fmt.Println(humanize.Ordinal(100))
}
Output:
1st
2nd
3rd
11th
21st
100th

func ParseBytes

func ParseBytes(s string) (int64, error)

ParseBytes parses s when it is a canonical Bytes or BinaryBytes result. It accepts only the units B, KB, MB, GB, TB, PB, EB, KiB, MiB, GiB, TiB, PiB, and EiB, with a single space between the number and unit. It returns ErrInvalid for malformed or non-canonical input. The parsed value represents the nearest byte described by the display text, not the original source value before formatting.

Example
package main

import (
	"errors"
	"fmt"

	"github.com/agentable/go-humanize"
)

func main() {
	kb, _ := humanize.ParseBytes("42 KB")
	fmt.Println(kb)

	mib, _ := humanize.ParseBytes("1.5 MiB")
	fmt.Println(mib)

	gb, _ := humanize.ParseBytes("2.5 GB")
	fmt.Println(gb)

	if _, err := humanize.ParseBytes("garbage"); errors.Is(err, humanize.ErrInvalid) {
		fmt.Println("invalid")
	}
}
Output:
42000
1572864
2500000000
invalid

func ParseDuration

func ParseDuration(s string) (time.Duration, error)

ParseDuration parses s when it is a canonical Duration result. It accepts day units in addition to time.ParseDuration units and returns ErrInvalid for malformed or non-canonical input.

Example
package main

import (
	"fmt"

	"github.com/agentable/go-humanize"
)

func main() {
	d1, _ := humanize.ParseDuration("1h 30m")
	fmt.Println(d1)

	d2, _ := humanize.ParseDuration("2d 5h")
	fmt.Println(d2)

	d3, _ := humanize.ParseDuration("500ms")
	fmt.Println(d3)
}
Output:
1h30m0s
53h0m0s
500ms

func Percent

func Percent(fraction float64) string

Percent formats fraction as a percentage with at most one decimal place. The input is a fraction, so 0.333 renders as "33.3%" and 1 renders as "100%". Values above 1 are not clamped: 1.25 renders as "125%". NaN and infinities render as "NaN%", "+Inf%", and "-Inf%".

Example
package main

import (
	"fmt"

	"github.com/agentable/go-humanize"
)

func main() {
	fmt.Println(humanize.Percent(0.75))
	fmt.Println(humanize.Percent(0.333))
	fmt.Println(humanize.Percent(1.0 / 3))
	fmt.Println(humanize.Percent(0))
}
Output:
75%
33.3%
33.3%
0%

func Relative

func Relative(target, ref time.Time) string

Relative formats target relative to ref using approximate 30-day months and 365-day years. Durations under a second return "just now". The output describes target's position relative to ref: target before ref renders as "N units ago", and target after ref renders as "in N units".

Example
package main

import (
	"fmt"
	"time"

	"github.com/agentable/go-humanize"
)

func main() {
	ref := time.Date(2024, 2, 24, 12, 0, 0, 0, time.UTC)

	fmt.Println(humanize.Relative(ref.Add(-3*24*time.Hour), ref))
	fmt.Println(humanize.Relative(ref.Add(-7*24*time.Hour), ref))
	fmt.Println(humanize.Relative(ref.Add(-30*24*time.Hour), ref))
	fmt.Println(humanize.Relative(ref.Add(2*24*time.Hour), ref))
}
Output:
3 days ago
1 week ago
1 month ago
in 2 days

Types

This section is empty.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL