humanizex

package
v0.13.0 Latest Latest
Warning

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

Go to latest
Published: Jun 29, 2022 License: MIT, MIT-0, MIT Imports: 9 Imported by: 0

README

humanizex - locale-aware natural number formatting

go get -u "tawesoft.co.uk/go"
import "tawesoft.co.uk/go/humanizex"

##FROZEN - PLEASE MIGRATE

These packages are moving to https://github.com/tawesoft/golib.

This is to increase security against possible supply chain attacks such as our domain name expiring in the future and being registered by someone else.

Please migrate to https://github.com/tawesoft/golib (when available) instead.

Most programs relying on a package in this monorepo, such as the dialog or lxstrconv packages, will continue to work for the foreseeable future.

Rarely used packages have been hidden for now - they are in the git commit history at https://github.com/tawesoft/go if you need to resurrect one.

Links License Stable?
homedocssrc MIT ✔ yes

About

Package humanizex is an elegant, general-purpose, extensible, modular, locale-aware way to format and parse numbers and quantities - like distances, bytes, and time - in a human-readable way ideal for config files and as a building-block for fully translated ergonomic user interfaces.

If golang.org/x/text is ever promoted to core then there will be a new version of this package named humanize (dropping the 'x').

What about dustin's go-humanize?

dustin's go-humanize (https://github.com/dustin/go-humanize) is 3.9 to 4.5 times faster formatting and 2 times faster parsing, if this is a bottleneck for you. It's also quite mature, so is probably very well tested by now. If you're only targeting the English language it also has more handy "out of the box" features.

On the other hand, tawesoft's humanizex is more general purpose and has better localisation support. Even with those extra features, tawesoft's humanizex codebase is also smaller and simpler.

Examples

Example formatting and parsing Byte quantities in various locales

package main

import (
    "fmt"

    "golang.org/x/text/language"
    "tawesoft.co.uk/go/humanizex"
)

func mustInt64(v int64, err error) int64 {
    if err != nil { panic(err) }
    return v
}

func main() {
    hEnglish := humanizex.NewHumanizer(language.English)
    hDanish  := humanizex.NewHumanizer(language.Danish)
    hBengali := humanizex.NewHumanizer(language.Bengali)

    // prints 1.5 KiB
    fmt.Println(hEnglish.FormatBytesIEC(1024 + 512))

    // prints 1,5 KiB
    fmt.Println(hDanish.FormatBytesIEC(1024 + 512))

    // prints ১.৫ KiB
    fmt.Println(hBengali.FormatBytesIEC(1024 + 512))

    // prints 1536
    fmt.Println(mustInt64(hEnglish.ParseBytesIEC("1.5 KiB")))
}

Example leveraging the raw parts of FormatParts to handle durations in a custom even nicer way for the english language.

package main

import (
    "fmt"
    "time"

    "golang.org/x/text/language"
    "tawesoft.co.uk/go/humanizex"
)

func plural(x float64) string {
    if x > 0.99 && x < 1.01 { return "" }
    return "s"
}

func main() {

    duration := (2 * time.Hour) + (20 * time.Second)

    // prints "2 h 20 s"
    fmt.Printf("Basic time: %s\n", humanizex.NewHumanizer(language.English).FormatDuration(duration))

    // Get the raw format parts
    parts := humanizex.FormatParts(
        duration.Seconds(),
        humanizex.CommonUnits.Second,
        humanizex.CommonFactors.Time,
    )

    // prints Nice time: 2 hours and 20 seconds ago
    fmt.Printf("Nice time: ")
    if (len(parts) == 1) && (parts[0].Unit.Utf8 == "s") {
        fmt.Printf("just now\n")
    } else {
        for i, part := range parts {
            fmt.Printf("%d", int(part.Magnitude + 0.5))

            if part.Unit.Utf8 == "y" {
                fmt.Printf(" year%s", plural(part.Magnitude))
            } else if part.Unit.Utf8 == "d" {
                fmt.Printf(" day%s", plural(part.Magnitude))
            } else if part.Unit.Utf8 == "h" {
                fmt.Printf(" hour%s", plural(part.Magnitude))
            } else if part.Unit.Utf8 == "min" {
                fmt.Printf(" minute%s", plural(part.Magnitude))
            } else if part.Unit.Utf8 == "s" {
                fmt.Printf(" second%s", plural(part.Magnitude))
            }

            if i + 1 < len(parts) {
                fmt.Printf(" and ")
            } else {
                fmt.Printf(" ago\n")
            }
        }
    }

}

Example using custom time factors from the Battlestar Galactica 1978 TV series.

package main

import (
    "fmt"

    "golang.org/x/text/language"
    "tawesoft.co.uk/go/humanizex"
)

func main() {
    factors := humanizex.Factors{
        Factors:    []humanizex.Factor{
            {1,                         humanizex.Unit{"millicenton", "millicenton"}, humanizex.FactorModeReplace},
            {60,                        humanizex.Unit{"centon", "centon"}, humanizex.FactorModeReplace},
            {60 * 60,                   humanizex.Unit{"centar", "centar"}, humanizex.FactorModeReplace},
            {24 * 60 * 60,              humanizex.Unit{"cycle", "cycle"}, humanizex.FactorModeReplace},
            {7 * 24 * 60 * 60,          humanizex.Unit{"secton", "secton"}, humanizex.FactorModeReplace},
            {28 * 24 * 60 * 60,         humanizex.Unit{"sectar", "sectar"}, humanizex.FactorModeReplace},
            {365 * 24 * 60 * 60,        humanizex.Unit{"yahren", "yahren"}, humanizex.FactorModeReplace},
            {100 * 365 * 24 * 60 * 60,  humanizex.Unit{"centauron", "centauron"}, humanizex.FactorModeReplace},
        },
        Components: 2,
    }

    h := humanizex.NewHumanizer(language.English)

    est := float64((2 * 365 * 24 * 60 * 60) + 1)

    fmt.Printf("Hey, I'll be with you in %s. Watch out for toasters!\n",
        h.Format(est, humanizex.Unit{"millicenton", "millicenton"}, factors).Utf8)

    // prints "Hey, I'll be with you in 2 yahren 1 millicenton. Watch out for toasters!"
}

Getting Help

This package is part of tawesoft.co.uk/go, a monorepo for small Go modules maintained by Tawesoft®. Check out that URL for more information about other Go modules from Tawesoft plus community and commercial support options.

Documentation

Overview

Package humanizex is an elegant, general-purpose, extensible, modular, locale-aware way to format and parse numbers and quantities - like distances, bytes, and time - in a human-readable way ideal for config files and as a building-block for fully translated ergonomic user interfaces.

If golang.org/x/text is ever promoted to core then there will be a new version of this package named `humanize` (dropping the 'x').

What about dustin's go-humanize?

dustin's go-humanize (https://github.com/dustin/go-humanize) is 3.9 to 4.5 times faster formatting and 2 times faster parsing, if this is a bottleneck for you. It's also quite mature, so is probably very well tested by now. If you're only targeting the English language it also has more handy "out of the box" features.

On the other hand, tawesoft's humanizex is more general purpose and has better localisation support. Even with those extra features, tawesoft's humanizex codebase is also smaller and simpler.

Examples

Example formatting and parsing Byte quantities in various locales

https://www.tawesoft.co.uk/go/doc/humanizex/examples/simple/

Example leveraging the raw parts of FormatParts to handle durations in a custom even nicer way for the english language.

https://www.tawesoft.co.uk/go/doc/humanizex/examples/custom-durations/

Example using custom time factors from the Battlestar Galactica 1978 TV series.

https://www.tawesoft.co.uk/go/doc/humanizex/examples/custom-factors/

FROZEN - PLEASE MIGRATE

These packages are moving to https://github.com/tawesoft/golib.

This is to increase security against possible supply chain attacks such as our domain name expiring in the future and being registered by someone else.

Please migrate to https://github.com/tawesoft/golib (when available) instead.

Most programs relying on a package in this monorepo, such as the dialog or lxstrconv packages, will continue to work for the foreseeable future.

Rarely used packages have been hidden for now - they are in the git commit history at https://github.com/tawesoft/go if you need to resurrect one.

Package Information

License: MIT (see LICENSE.txt)

Stable: yes

For more information, documentation, source code, examples, support, links, etc. please see https://www.tawesoft.co.uk/go and https://www.tawesoft.co.uk/go/humanizex

Index

Constants

View Source
const (
	// FactorModeIdentity indicates that the given factor label represents the
	// unit with no changes.
	FactorModeIdentity = FactorMode(0)

	// FactorModeUnitPrefix indicates the given factor label is a unit prefix
	// e.g. "Ki" is a byte prefix giving "KiB".
	FactorModeUnitPrefix = FactorMode(1)

	// FactorModeReplace indicates the given factor label replaces the current
	// unit e.g. the duration of time 100 s becomes 1 min 40 s, not 1 hs
	// (hectosecond)!
	FactorModeReplace = FactorMode(2)

	// FactorModeInputCompat indicates that the given factor label should only
	// be considered on input. This may be combined with any other FactorMode
	// by a bitwise OR operation.
	FactorModeInputCompat = FactorMode(4)
)

Variables

View Source
var CommonFactors = struct {
	// Time is time units in seconds, minutes, hours, days and years as min, h,
	// d, and y. These are non-SI units but generally accepted in context.
	// For times smaller than a second (e.g. nanoseconds), use SI instead.
	// The expected unit is a second (Unit{"s", "s"} or CommonUnits.Second)
	Time Factors

	// Distance are SI units that stop at kilo (because nobody uses
	// megametres or gigametres!) but includes centi. The expected unit is the
	// SI unit for distance, the metre (Unit{"m", "m"} or CommonUnits.Meter)
	Distance Factors

	// IEC are the "ibi" unit prefixes for bytes e.g. Ki, Mi, Gi with a
	// factor of 1024.
	IEC Factors

	// JEDEC are the old unit prefixes for bytes: K, M, G (only) with a factor
	// of 1024.
	JEDEC Factors

	// SIBytes are the SI unit prefixes for bytes e.g. k, M, G with a
	// factor of 1000. Unlike the normal SI Factors, it is assumed based on
	// context that when a "K" is input this is intended to mean the "k" SI
	// unit prefix instead of Kelvin - I've never heard of a Kelvin-Byte!
	SIBytes Factors

	// SIUncommon are the SI unit prefixes including deci, deca, and hecto
	SIUncommon Factors

	// SI are the SI unit prefixes except centi, deci, deca, and hecto
	SI Factors
}{
	Time: Factors{
		Factors: []Factor{
			{1, Unit{"s", "s"}, FactorModeReplace},
			{60, Unit{"min", "min"}, FactorModeReplace},
			{60 * 60, Unit{"h", "h"}, FactorModeReplace},
			{24 * 60 * 60, Unit{"d", "d"}, FactorModeReplace},
			{365.2422 * 24 * 60 * 60, Unit{"y", "y"}, FactorModeReplace},
		},
		Components: 2,
	},
	Distance: Factors{
		Factors: []Factor{
			{1E-9, Unit{"n", "n"}, FactorModeUnitPrefix},
			{1E-6, Unit{"μ", "u"}, FactorModeUnitPrefix},
			{1E-3, Unit{"m", "m"}, FactorModeUnitPrefix},
			{1E-2, Unit{"c", "c"}, FactorModeUnitPrefix},
			{1, Unit{"", ""}, FactorModeIdentity},
			{1000, Unit{"k", "k"}, FactorModeUnitPrefix},
		},
	},
	IEC: Factors{
		Factors: []Factor{
			{1, Unit{"", ""}, FactorModeUnitPrefix},
			{1024, Unit{"Ki", "Ki"}, FactorModeUnitPrefix},
			{1024 * 1024, Unit{"Mi", "Mi"}, FactorModeUnitPrefix},
			{1024 * 1024 * 1024, Unit{"Gi", "Gi"}, FactorModeUnitPrefix},
			{1024 * 1024 * 1024 * 1024, Unit{"Ti", "Ti"}, FactorModeUnitPrefix},
		},
	},
	JEDEC: Factors{
		Factors: []Factor{
			{1, Unit{"", ""}, FactorModeIdentity},
			{1024, Unit{"K", "K"}, FactorModeUnitPrefix},
			{1024 * 1024, Unit{"M", "M"}, FactorModeUnitPrefix},
			{1024 * 1024 * 1024, Unit{"G", "G"}, FactorModeUnitPrefix},
		},
	},
	SIBytes: Factors{
		Factors: []Factor{
			{1, Unit{"", ""}, FactorModeIdentity},
			{1E3, Unit{"k", "k"}, FactorModeUnitPrefix},
			{1E3, Unit{"K", "K"}, FactorModeUnitPrefix | FactorModeInputCompat},
			{1E6, Unit{"M", "M"}, FactorModeUnitPrefix},
			{1E9, Unit{"G", "G"}, FactorModeUnitPrefix},
			{1E12, Unit{"T", "T"}, FactorModeUnitPrefix},
		},
	},
	SIUncommon: Factors{
		Factors: []Factor{
			{1E-9, Unit{"n", "n"}, FactorModeUnitPrefix},
			{1E-6, Unit{"μ", "u"}, FactorModeUnitPrefix},
			{1E-3, Unit{"m", "m"}, FactorModeUnitPrefix},
			{1E-2, Unit{"c", "c"}, FactorModeUnitPrefix},
			{1E-1, Unit{"d", "d"}, FactorModeUnitPrefix},
			{1, Unit{"", ""}, FactorModeIdentity},
			{1E1, Unit{"da", "da"}, FactorModeUnitPrefix},
			{1E2, Unit{"h", "h"}, FactorModeUnitPrefix},
			{1E3, Unit{"k", "k"}, FactorModeUnitPrefix},
			{1E6, Unit{"M", "M"}, FactorModeUnitPrefix},
			{1E9, Unit{"G", "G"}, FactorModeUnitPrefix},
			{1E12, Unit{"T", "T"}, FactorModeUnitPrefix},
		},
	},
	SI: Factors{
		Factors: []Factor{
			{1E-9, Unit{"n", "n"}, FactorModeUnitPrefix},
			{1E-6, Unit{"μ", "u"}, FactorModeUnitPrefix},
			{1E-3, Unit{"m", "m"}, FactorModeUnitPrefix},
			{1, Unit{"", ""}, FactorModeIdentity},
			{1E3, Unit{"k", "k"}, FactorModeUnitPrefix},
			{1E6, Unit{"M", "M"}, FactorModeUnitPrefix},
			{1E9, Unit{"G", "G"}, FactorModeUnitPrefix},
			{1E12, Unit{"T", "T"}, FactorModeUnitPrefix},
		},
	},
}
View Source
var CommonUnits = struct {
	None          Unit
	Second        Unit
	Meter         Unit
	Byte          Unit
	Bit           Unit
	BitsPerSecond Unit
}{
	None:          Unit{"", ""},
	Second:        Unit{"s", "s"},
	Meter:         Unit{"m", "m"},
	Byte:          Unit{"B", "B"},
	Bit:           Unit{"b", "b"},
	BitsPerSecond: Unit{"bps", "bps"},
}

Functions

This section is empty.

Types

type Factor

type Factor struct {

	// Magnitude defines the absolute size of the factor e.g. 1000000 for the
	// SI unit prefix "M".
	Magnitude float64

	// Label describes the magnitude, usually as a unit prefix (like SI "M")
	// or as a replacement (like "min"), controlled by Mode.
	Unit Unit

	// Mode controls the formatting of this factor
	Mode FactorMode
}

Factor defines one entry in an ordered list of Factors.

type FactorMode

type FactorMode int

FactorMode controls the formatting of a factor.

type Factors

type Factors struct {
	// Factors is a list of Factor entries in ascending order of size.
	Factors []Factor

	// Components controls how the formatting is broken up -
	//
	// - if zero (default) or 1 (interchangeable), formatting has a single
	// component e.g. "1.5 M".
	//
	// - if 2 or more, formatting is broken up into previous factors e.g.
	// "1 h 50 min" (2 components) or "1 h 50 min 25 s" (3 components)
	Components int
}

Factors describes a way to format a quantity with units.

type Humanizer

type Humanizer interface {
	// Format is a general purpose locale-aware way to format any quantity
	// with a defined set of factors. The unit argument is the base unit
	// e.g. s for seconds, m for meters, B for bytes.
	Format(value float64, unit Unit, factors Factors) String

	FormatNumber(number float64) String           // e.g. 12 k
	FormatDistance(meters float64) String         // e.g. 10 µm, 10 km
	FormatDuration(duration time.Duration) string // e.g. 1 h 50 min
	FormatSeconds(seconds float64) string         // e.g. 1 h 50 min
	FormatBytesJEDEC(bytes int64) string          // e.g. 12 KB, 5 MB
	FormatBytesIEC(bytes int64) string            // e.g. 12 kB, 5 MB
	FormatBytesSI(bytes int64) string             // e.g. 12 KiB, 5 MiB

	// Accept is a general purpose locale-aware way to parse any quantity
	// with a defined set of factors from the start of the string str. The
	// provided unit is optional and is accepted if it appears in str.
	//
	// Accept returns the value, the number of bytes successfully parsed (which
	// may be zero), or an error.
	Accept(str string, unit Unit, factors Factors) (float64, int, error)

	// Parse is a general purpose locale-aware way to parse any quantity
	// with a defined set of factors.  The provided unit is optional and is
	// accepted if it appears in str.
	Parse(str string, unit Unit, factors Factors) (float64, error)

	ParseDuration(str string) (time.Duration, error)
	ParseBytesJEDEC(str string) (int64, error)
	ParseBytesIEC(str string) (int64, error)
	ParseBytesSI(str string) (int64, error)
}

Humanizer implements a locale-aware way to parse and format humanized quantities.

func NewHumanizer

func NewHumanizer(tag language.Tag, options ...interface{}) Humanizer

NewHumanizer initialises a human language number encoder/decoder for the given tag (representing a specific language or locale).

The language.Tag is usually a named language from golang.org/x/text/language e.g. language.English and controls how numbers are written e.g. comma placement, decimal point, digits.

type Part

type Part struct {
	Magnitude float64
	Unit      Unit
}

Part describes some component of a formatting result e.g. 1.5 km or 1 hour

func FormatParts

func FormatParts(n float64, unit Unit, factors Factors) []Part

FormatParts is a general purpose locale-aware way to format any quantity with a defined set of factors into a list of parts. The unit argument is the base unit e.g. s for seconds, m for meters, B for bytes. In the simple case a list of only one Part is returned, e.g. a Part representing 1.5 km. In other cases, there may be multiple parts e.g. the two parts "1 h" and "30 min" making up the time "1 h 30 min". The number of parts returned is never more than that defined by the factors argument's Components field (but may be fewer).

type String

type String struct {
	// Utf8 is the native Utf8-encoded Unicode representation
	Utf8 string

	// Ascii is an alternative version accepted for non-Unicode inputs (such
	// as when a user does not know how to enter µ on their keyboard (on mine,
	// it's Right-Alt+m)) or for non-Unicode output (such as legacy systems).
	Ascii string
}

String holds an Utf8-encoded and an Ascii-compatible encoding of a string.

type Unit

type Unit String

Unit describes some quantity e.g. "m" for length in metres, "k" for the SI unit prefix kilo, "km" for a kilometre.

func (Unit) Cat

func (u Unit) Cat(v Unit) Unit

Cat concatenates two units (u + v) and returns the result.

Directories

Path Synopsis
examples
custom-durations
Example leveraging the raw parts of FormatParts to handle durations in a custom even nicer way for the english language.
Example leveraging the raw parts of FormatParts to handle durations in a custom even nicer way for the english language.
custom-factors
Example using custom time factors from the Battlestar Galactica 1978 TV series.
Example using custom time factors from the Battlestar Galactica 1978 TV series.
simple
Example formatting and parsing Byte quantities in various locales
Example formatting and parsing Byte quantities in various locales

Jump to

Keyboard shortcuts

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