blob: 19e63121f97e5d2cc918de9ee53a97686a1231cc [file] [log] [blame]
Jiri Simsa5293dcb2014-05-10 09:56:38 -07001package unit
2
3import (
4 "fmt"
5 "math"
6 "regexp"
7 "strconv"
8 "strings"
9)
10
11// metersInFoot denotes the number of meters in one foot, which is *exactly*
12// 0.3048 (i.e., there is no rounding).
13const (
14 metersInFoot = 0.3048
15 // MaxDistance represents the largest expressable distance.
16 MaxDistance Distance = math.MaxFloat64
17)
18
19// Distance represents a physical distance with floating-point precision.
20// It can be expressed in both metric and imperial units.
21//
22// Distance is internally stored in millimeter units.
23type Distance float64
24
25// Common distances. To convert an integer number of units into a Distance,
26// multiply:
27// fmt.Print(5*unit.Meter) // prints "5m0mm"
28//
29// To count a number of units in a Distance, divide:
30// d := 5*unit.Meter
31// fmt.Print(int64(d/unit.Centimeter)) // prints 500
32//
33// Distances can be added or subtracted:
34// fmt.Print(5*unit.Meter + 3*unit.Centimeter) // prints "5m30mm"
35// fmt.Print(3*unit.Kilometer - 2*unit.Kilometer) // prints "1km0m0mm"
36const (
37 Millimeter Distance = 1
38 Centimeter = 10 * Millimeter
39 Meter = 1000 * Millimeter
40 Kilometer = 1000 * Meter
41 Foot = metersInFoot * Meter
42 Inch = Foot / 12
43 Yard = 3 * Foot
44 Mile = 1760 * Yard
45)
46
47var parseRegexp = regexp.MustCompile("^([0-9\\+\\-\\.eE]*)([mM][iI]|[kK][mM]|[mM][mM]|[cC][mM]|[fF][tT]|[iI][nN]|[fF][tT]|[yY][dD]|[mM])")
48
49// Parse parses the provided string and returns a new Distance. The provided
50// string must be of the following format:
51//
52// [+-]*(<positive_float_num>("mi"|"km"|"m"|"yd"|"ft"|"in"|"cm"|"mm"))+
53//
54// For example, the following are legitimate distance strings:
55//
56// "10.2mi8km0.3m12yd100ft18in12.82cm22mm"
57// "10km12m88cm"
58// "12yd8ft3in"
59// "-12yd3.2ft"
60//
61// ParseDistance will return an error if the string doesn't conform to the
62// above format.
63func ParseDistance(value string) (Distance, error) {
64 var total Distance
65 s := value
66
67 // See if the string starts with a +- sign and if so consume it.
68 neg := false
69 if len(s) > 0 && s[0] == '+' {
70 s = s[1:]
71 } else if len(s) > 0 && s[0] == '-' {
72 s = s[1:]
73 neg = true
74 }
75 match := parseRegexp.FindStringSubmatch(s)
76 if match == nil {
77 return 0, fmt.Errorf("invalid distance string: %q", value)
78 }
79 for match != nil && len(match) == 3 {
80 full, value, unit := match[0], match[1], match[2]
81 // Parse value.
82 f, err := strconv.ParseFloat(value, 64)
83 if err != nil {
84 return 0, fmt.Errorf("error parsing float in distance string %q: %s", value, err)
85 }
86 if f < 0 {
87 return 0, fmt.Errorf("can't use negative float unit count in distance string %q: %f", value, f)
88 }
89 // Parse unit.
90 var mult Distance
91 switch strings.ToLower(unit) {
92 case "mi":
93 mult = Mile
94 case "km":
95 mult = Kilometer
96 case "m":
97 mult = Meter
98 case "yd":
99 mult = Yard
100 case "ft":
101 mult = Foot
102 case "in":
103 mult = Inch
104 case "cm":
105 mult = Centimeter
106 case "mm":
107 mult = Millimeter
108 default: // should never happen
109 return 0, fmt.Errorf("illegal unit in distance string %q: %q", value, unit)
110 }
111 // Add the newly parsed sub-distance to the total.
112 total += Distance(f) * mult
113 // Advance to the next matched sub-distance.
114 s = s[len(full):]
115 match = parseRegexp.FindStringSubmatch(s)
116 }
117
118 if len(s) > 0 {
119 return 0, fmt.Errorf("invalid distance string: %q", value)
120 }
121
122 if neg {
123 total *= -1
124 }
125 return total, nil
126}
127
128func (d Distance) String() string {
129 var sign string
130 if d < 0 {
131 sign = "-"
132 }
133 v := math.Abs(float64(d))
134 kms := math.Floor(v / float64(Kilometer))
135 v = v - kms*float64(Kilometer)
136 ms := math.Floor(v / float64(Meter))
137 v = v - ms*float64(Meter)
138 mms := v
139 if kms > 0 {
140 return fmt.Sprintf("%s%gkm%gm%gmm", sign, kms, ms, mms)
141 }
142 if ms > 0 {
143 return fmt.Sprintf("%s%gm%gmm", sign, ms, mms)
144 }
145 return fmt.Sprintf("%s%gmm", sign, mms)
146}
147
148// Miles returns a distance value in mile units.
149func (d Distance) Miles() float64 {
150 return float64(d / Mile)
151}
152
153// Kilometers returns a distance value in kilometer units.
154func (d Distance) Kilometers() float64 {
155 return float64(d / Kilometer)
156}
157
158// Meters returns a distance value in meter units.
159func (d Distance) Meters() float64 {
160 return float64(d / Meter)
161}
162
163// Yards returns a distance value in yard units.
164func (d Distance) Yards() float64 {
165 return float64(d / Yard)
166}
167
168// Feet returns a distance value in foot units.
169func (d Distance) Feet() float64 {
170 return float64(d / Foot)
171}
172
173// Inches returns a distance value in inch units.
174func (d Distance) Inches() float64 {
175 return float64(d / Inch)
176}
177
178// Centimeters returns a distance value in centimeter units.
179func (d Distance) Centimeters() float64 {
180 return float64(d / Centimeter)
181}
182
183// Millimeters returns a distance value in millimeter units.
184func (d Distance) Millimeters() float64 {
185 return float64(d)
186}