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"