3 from numbers import Number
4 from typing import Callable
9 class Converter(object):
10 """A converter has a canonical name and a category. The name defines
11 a unit of measurement in a category or class of measurements.
12 This framework will allow conversion between named units in the
13 same category. e.g. name may be "meter", "inch", "mile" and their
14 category may be "length".
16 The way that conversions work is that we convert the magnitude
17 first to a canonical unit and then (if needed) from the canonical
18 unit to the desired destination unit. e.g. if "meter" is the
19 canonical unit of measurement for the category "length", in order
20 to convert miles into inches we first convert miles into meters
21 and, from there, meters into inches. This is potentially
22 dangerous because it requires two floating point operations which
23 each have the potential to overflow, underflow, or introduce
24 floating point errors. Caveat emptor.
31 to_canonical: Callable, # convert to canonical unit
32 from_canonical: Callable, # convert from canonical unit
36 self.category = category
37 self.to_canonical_f = to_canonical
38 self.from_canonical_f = from_canonical
41 def to_canonical(self, n: Number) -> Number:
42 return self.to_canonical_f(n)
44 def from_canonical(self, n: Number) -> Number:
45 return self.from_canonical_f(n)
47 def unit_suffix(self) -> str:
51 # A catalog of converters.
52 conversion_catalog = {
53 "Second": Converter("Second", "time", lambda s: s, lambda s: s, "s"),
57 lambda m: (m * constants.SECONDS_PER_MINUTE),
58 lambda s: (s / constants.SECONDS_PER_MINUTE),
64 lambda h: (h * constants.SECONDS_PER_HOUR),
65 lambda s: (s / constants.SECONDS_PER_HOUR),
71 lambda d: (d * constants.SECONDS_PER_DAY),
72 lambda s: (s / constants.SECONDS_PER_DAY),
78 lambda w: (w * constants.SECONDS_PER_WEEK),
79 lambda s: (s / constants.SECONDS_PER_WEEK),
82 "Fahrenheit": Converter(
85 lambda f: (f - 32.0) * 0.55555555,
86 lambda c: c * 1.8 + 32.0,
89 "Celsius": Converter("Celsius", "temperature", lambda c: c, lambda c: c, "°C"),
100 def convert(magnitude: Number, from_thing: str, to_thing: str) -> float:
101 src = conversion_catalog.get(from_thing, None)
102 dst = conversion_catalog.get(to_thing, None)
103 if src is None or dst is None:
104 raise ValueError("No known conversion")
105 if src.category != dst.category:
106 raise ValueError("Incompatible conversion")
107 return _convert(magnitude, src, dst)
110 def _convert(magnitude: Number, from_unit: Converter, to_unit: Converter) -> float:
111 canonical = from_unit.to_canonical(magnitude)
112 converted = to_unit.from_canonical(canonical)
113 return float(converted)
116 def sec_to_min(s: float) -> float:
118 Convert seconds into minutes.
123 return convert(s, "Second", "Minute")
126 def sec_to_hour(s: float) -> float:
128 Convert seconds into hours.
130 >>> sec_to_hour(1800)
133 return convert(s, "Second", "Hour")
136 def sec_to_day(s: float) -> float:
138 Convert seconds into days.
143 return convert(s, "Second", "Day")
146 def sec_to_week(s: float) -> float:
148 Convert seconds into weeks.
150 >>> sec_to_week(1800)
153 return convert(s, "Second", "Week")
156 def min_to_sec(m: float) -> float:
158 Convert minutes into seconds.
163 return convert(m, "Minute", "Second")
166 def min_to_hour(m: float) -> float:
168 Convert minutes into hours.
173 return convert(m, "Minute", "Hour")
176 def min_to_day(m: float) -> float:
178 Convert minutes into days.
180 >>> min_to_day(60 * 12)
183 return convert(m, "Minute", "Day")
186 def min_to_week(m: float) -> float:
188 Convert minutes into weeks.
190 >>> min_to_week(60 * 24 * 3)
193 return convert(m, "Minute", "Week")
196 def hour_to_sec(h: float) -> float:
198 Convert hours into seconds.
203 return convert(h, "Hour", "Second")
206 def hour_to_min(h: float) -> float:
208 Convert hours into minutes.
213 return convert(h, "Hour", "Minute")
216 def hour_to_day(h: float) -> float:
218 Convert hours into days.
223 return convert(h, "Hour", "Day")
226 def hour_to_week(h: float) -> float:
228 Convert hours into weeks.
233 return convert(h, "Hour", "Week")
236 def day_to_sec(d: float) -> float:
238 Convert days into seconds.
243 return convert(d, "Day", "Second")
246 def day_to_min(d: float) -> float:
248 Convert days into minutes.
253 return convert(d, "Day", "Minute")
256 def day_to_hour(d: float) -> float:
258 Convert days into hours.
263 return convert(d, "Day", "Hour")
266 def day_to_week(d: float) -> float:
268 Convert days into weeks.
273 return convert(d, "Day", "Week")
276 def week_to_sec(w: float) -> float:
278 Convert weeks into seconds.
283 return convert(w, "Week", "Second")
286 def week_to_min(w: float) -> float:
288 Convert weeks into minutes.
293 return convert(w, "Week", "Minute")
296 def week_to_hour(w: float) -> float:
298 Convert weeks into hours.
303 return convert(w, "Week", "Hour")
306 def week_to_day(w: float) -> float:
308 Convert weeks into days.
313 return convert(w, "Week", "Day")
316 def f_to_c(temp_f: float) -> float:
318 Convert Fahrenheit into Celsius.
323 return convert(temp_f, "Fahrenheit", "Celsius")
326 def c_to_f(temp_c: float) -> float:
328 Convert Celsius to Fahrenheit.
333 return convert(temp_c, "Celsius", "Fahrenheit")
336 if __name__ == '__main__':