blob: 19e63121f97e5d2cc918de9ee53a97686a1231cc [file] [log] [blame]
package unit
import (
"fmt"
"math"
"regexp"
"strconv"
"strings"
)
// metersInFoot denotes the number of meters in one foot, which is *exactly*
// 0.3048 (i.e., there is no rounding).
const (
metersInFoot = 0.3048
// MaxDistance represents the largest expressable distance.
MaxDistance Distance = math.MaxFloat64
)
// Distance represents a physical distance with floating-point precision.
// It can be expressed in both metric and imperial units.
//
// Distance is internally stored in millimeter units.
type Distance float64
// Common distances. To convert an integer number of units into a Distance,
// multiply:
// fmt.Print(5*unit.Meter) // prints "5m0mm"
//
// To count a number of units in a Distance, divide:
// d := 5*unit.Meter
// fmt.Print(int64(d/unit.Centimeter)) // prints 500
//
// Distances can be added or subtracted:
// fmt.Print(5*unit.Meter + 3*unit.Centimeter) // prints "5m30mm"
// fmt.Print(3*unit.Kilometer - 2*unit.Kilometer) // prints "1km0m0mm"
const (
Millimeter Distance = 1
Centimeter = 10 * Millimeter
Meter = 1000 * Millimeter
Kilometer = 1000 * Meter
Foot = metersInFoot * Meter
Inch = Foot / 12
Yard = 3 * Foot
Mile = 1760 * Yard
)
var parseRegexp = regexp.MustCompile("^([0-9\\+\\-\\.eE]*)([mM][iI]|[kK][mM]|[mM][mM]|[cC][mM]|[fF][tT]|[iI][nN]|[fF][tT]|[yY][dD]|[mM])")
// Parse parses the provided string and returns a new Distance. The provided
// string must be of the following format:
//
// [+-]*(<positive_float_num>("mi"|"km"|"m"|"yd"|"ft"|"in"|"cm"|"mm"))+
//
// For example, the following are legitimate distance strings:
//
// "10.2mi8km0.3m12yd100ft18in12.82cm22mm"
// "10km12m88cm"
// "12yd8ft3in"
// "-12yd3.2ft"
//
// ParseDistance will return an error if the string doesn't conform to the
// above format.
func ParseDistance(value string) (Distance, error) {
var total Distance
s := value
// See if the string starts with a +- sign and if so consume it.
neg := false
if len(s) > 0 && s[0] == '+' {
s = s[1:]
} else if len(s) > 0 && s[0] == '-' {
s = s[1:]
neg = true
}
match := parseRegexp.FindStringSubmatch(s)
if match == nil {
return 0, fmt.Errorf("invalid distance string: %q", value)
}
for match != nil && len(match) == 3 {
full, value, unit := match[0], match[1], match[2]
// Parse value.
f, err := strconv.ParseFloat(value, 64)
if err != nil {
return 0, fmt.Errorf("error parsing float in distance string %q: %s", value, err)
}
if f < 0 {
return 0, fmt.Errorf("can't use negative float unit count in distance string %q: %f", value, f)
}
// Parse unit.
var mult Distance
switch strings.ToLower(unit) {
case "mi":
mult = Mile
case "km":
mult = Kilometer
case "m":
mult = Meter
case "yd":
mult = Yard
case "ft":
mult = Foot
case "in":
mult = Inch
case "cm":
mult = Centimeter
case "mm":
mult = Millimeter
default: // should never happen
return 0, fmt.Errorf("illegal unit in distance string %q: %q", value, unit)
}
// Add the newly parsed sub-distance to the total.
total += Distance(f) * mult
// Advance to the next matched sub-distance.
s = s[len(full):]
match = parseRegexp.FindStringSubmatch(s)
}
if len(s) > 0 {
return 0, fmt.Errorf("invalid distance string: %q", value)
}
if neg {
total *= -1
}
return total, nil
}
func (d Distance) String() string {
var sign string
if d < 0 {
sign = "-"
}
v := math.Abs(float64(d))
kms := math.Floor(v / float64(Kilometer))
v = v - kms*float64(Kilometer)
ms := math.Floor(v / float64(Meter))
v = v - ms*float64(Meter)
mms := v
if kms > 0 {
return fmt.Sprintf("%s%gkm%gm%gmm", sign, kms, ms, mms)
}
if ms > 0 {
return fmt.Sprintf("%s%gm%gmm", sign, ms, mms)
}
return fmt.Sprintf("%s%gmm", sign, mms)
}
// Miles returns a distance value in mile units.
func (d Distance) Miles() float64 {
return float64(d / Mile)
}
// Kilometers returns a distance value in kilometer units.
func (d Distance) Kilometers() float64 {
return float64(d / Kilometer)
}
// Meters returns a distance value in meter units.
func (d Distance) Meters() float64 {
return float64(d / Meter)
}
// Yards returns a distance value in yard units.
func (d Distance) Yards() float64 {
return float64(d / Yard)
}
// Feet returns a distance value in foot units.
func (d Distance) Feet() float64 {
return float64(d / Foot)
}
// Inches returns a distance value in inch units.
func (d Distance) Inches() float64 {
return float64(d / Inch)
}
// Centimeters returns a distance value in centimeter units.
func (d Distance) Centimeters() float64 {
return float64(d / Centimeter)
}
// Millimeters returns a distance value in millimeter units.
func (d Distance) Millimeters() float64 {
return float64(d)
}