go / format

I use small formatting functions to display bytes, numbers, and times in human-readable form.

Bytes

Format byte counts with appropriate units:

const (
	KB = 1000
	MB = KB * 1000
	GB = MB * 1000
	TB = GB * 1000
)

func HumanBytes(b int64) string {
	switch {
	case b >= TB:
		return fmt.Sprintf("%.1f TB", float64(b)/TB)
	case b >= GB:
		return fmt.Sprintf("%.1f GB", float64(b)/GB)
	case b >= MB:
		return fmt.Sprintf("%.1f MB", float64(b)/MB)
	case b >= KB:
		return fmt.Sprintf("%.1f KB", float64(b)/KB)
	default:
		return fmt.Sprintf("%d B", b)
	}
}
HumanBytes(500)        // "500 B"
HumanBytes(1500)       // "1.5 KB"
HumanBytes(1500000)    // "1.5 MB"
HumanBytes(1500000000) // "1.5 GB"

For binary units (KiB, MiB, GiB), use 1024 instead of 1000.

Numbers

Format large numbers with K/M/B suffixes:

const (
	Thousand = 1000
	Million  = Thousand * 1000
	Billion  = Million * 1000
)

func HumanNumber(n uint64) string {
	switch {
	case n >= Billion:
		return fmt.Sprintf("%.1fB", float64(n)/Billion)
	case n >= Million:
		return fmt.Sprintf("%.1fM", float64(n)/Million)
	case n >= Thousand:
		return fmt.Sprintf("%.0fK", float64(n)/Thousand)
	default:
		return strconv.FormatUint(n, 10)
	}
}
HumanNumber(500)        // "500"
HumanNumber(1500)       // "2K"
HumanNumber(1500000)    // "1.5M"
HumanNumber(1500000000) // "1.5B"

Relative time

Format timestamps as relative durations:

func HumanTime(t time.Time) string {
	if t.IsZero() {
		return "Never"
	}

	d := time.Since(t)
	if d < 0 {
		return humanDuration(-d) + " from now"
	}
	return humanDuration(d) + " ago"
}

func humanDuration(d time.Duration) string {
	switch {
	case d < time.Second:
		return "Less than a second"
	case d < time.Minute:
		return fmt.Sprintf("%d seconds", int(d.Seconds()))
	case d < time.Hour:
		return fmt.Sprintf("%d minutes", int(d.Minutes()))
	case d < 48*time.Hour:
		return fmt.Sprintf("%d hours", int(d.Hours()))
	case d < 14*24*time.Hour:
		return fmt.Sprintf("%d days", int(d.Hours())/24)
	case d < 60*24*time.Hour:
		return fmt.Sprintf("%d weeks", int(d.Hours())/24/7)
	case d < 365*24*time.Hour:
		return fmt.Sprintf("%d months", int(d.Hours())/24/30)
	default:
		return fmt.Sprintf("%d years", int(d.Hours())/24/365)
	}
}
HumanTime(time.Now().Add(-30 * time.Second)) // "30 seconds ago"
HumanTime(time.Now().Add(-3 * time.Hour))    // "3 hours ago"
HumanTime(time.Now().Add(-3 * 24 * time.Hour)) // "3 days ago"
HumanTime(time.Time{})                       // "Never"

Duration

Format durations for progress displays:

func HumanDuration(d time.Duration) string {
	switch {
	case d >= 100*time.Hour:
		return "99h+"
	case d >= time.Hour:
		return fmt.Sprintf("%dh%dm", int(d.Hours()), int(d.Minutes())%60)
	default:
		return d.Round(time.Second).String()
	}
}
HumanDuration(90 * time.Second)  // "1m30s"
HumanDuration(90 * time.Minute)  // "1h30m"
HumanDuration(200 * time.Hour)   // "99h+"

Usage

These functions are useful for CLI output, logs, and web UIs:

fmt.Printf("Downloaded %s in %s\n",
    HumanBytes(fileSize),
    HumanDuration(elapsed))
// "Downloaded 1.5 GB in 2m30s"

fmt.Printf("Last updated %s\n", HumanTime(updatedAt))
// "Last updated 3 hours ago"

fmt.Printf("%s parameters\n", HumanNumber(params))
// "7B parameters"

← All articles