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.
29 to_canonical: Callable, # convert to canonical unit
30 from_canonical: Callable, # convert from canonical unit
33 self.category = category
34 self.to_canonical_f = to_canonical
35 self.from_canonical_f = from_canonical
38 def to_canonical(self, n: Number) -> Number:
39 return self.to_canonical_f(n)
41 def from_canonical(self, n: Number) -> Number:
42 return self.from_canonical_f(n)
44 def unit_suffix(self) -> str:
48 # A catalog of converters.
49 conversion_catalog = {
50 "Second": Converter("Second",
55 "Minute": Converter("Minute",
57 lambda m: (m * constants.SECONDS_PER_MINUTE),
58 lambda s: (s / constants.SECONDS_PER_MINUTE),
60 "Hour": Converter("Hour",
62 lambda h: (h * constants.SECONDS_PER_HOUR),
63 lambda s: (s / constants.SECONDS_PER_HOUR),
65 "Day": Converter("Day",
67 lambda d: (d * constants.SECONDS_PER_DAY),
68 lambda s: (s / constants.SECONDS_PER_DAY),
70 "Week": Converter("Week",
72 lambda w: (w * constants.SECONDS_PER_WEEK),
73 lambda s: (s / constants.SECONDS_PER_WEEK),
75 "Fahrenheit": Converter("Fahrenheit",
77 lambda f: (f - 32.0) * 0.55555555,
78 lambda c: c * 1.8 + 32.0,
80 "Celsius": Converter("Celsius",
85 "Kelvin": Converter("Kelvin",
93 def convert(magnitude: Number,
95 to_thing: str) -> float:
96 src = conversion_catalog.get(from_thing, None)
97 dst = conversion_catalog.get(to_thing, None)
98 if src is None or dst is None:
99 raise ValueError("No known conversion")
100 if src.category != dst.category:
101 raise ValueError("Incompatible conversion")
102 return _convert(magnitude, src, dst)
105 def _convert(magnitude: Number,
106 from_unit: Converter,
107 to_unit: Converter) -> float:
108 canonical = from_unit.to_canonical(magnitude)
109 converted = to_unit.from_canonical(canonical)
110 return float(converted)
113 def sec_to_min(s: float) -> float:
115 Convert seconds into minutes.
120 return convert(s, "Second", "Minute")
123 def sec_to_hour(s: float) -> float:
125 Convert seconds into hours.
127 >>> sec_to_hour(1800)
130 return convert(s, "Second", "Hour")
133 def sec_to_day(s: float) -> float:
135 Convert seconds into days.
140 return convert(s, "Second", "Day")
143 def sec_to_week(s: float) -> float:
145 Convert seconds into weeks.
147 >>> sec_to_week(1800)
150 return convert(s, "Second", "Week")
153 def min_to_sec(m: float) -> float:
155 Convert minutes into seconds.
160 return convert(m, "Minute", "Second")
163 def min_to_hour(m: float) -> float:
165 Convert minutes into hours.
170 return convert(m, "Minute", "Hour")
173 def min_to_day(m: float) -> float:
175 Convert minutes into days.
177 >>> min_to_day(60 * 12)
180 return convert(m, "Minute", "Day")
183 def min_to_week(m: float) -> float:
185 Convert minutes into weeks.
187 >>> min_to_week(60 * 24 * 3)
190 return convert(m, "Minute", "Week")
193 def hour_to_sec(h: float) -> float:
195 Convert hours into seconds.
200 return convert(h, "Hour", "Second")
203 def hour_to_min(h: float) -> float:
205 Convert hours into minutes.
210 return convert(h, "Hour", "Minute")
213 def hour_to_day(h: float) -> float:
215 Convert hours into days.
220 return convert(h, "Hour", "Day")
223 def hour_to_week(h: float) -> float:
225 Convert hours into weeks.
230 return convert(h, "Hour", "Week")
233 def day_to_sec(d: float) -> float:
235 Convert days into seconds.
240 return convert(d, "Day", "Second")
243 def day_to_min(d: float) -> float:
245 Convert days into minutes.
250 return convert(d, "Day", "Minute")
253 def day_to_hour(d: float) -> float:
255 Convert days into hours.
260 return convert(d, "Day", "Hour")
263 def day_to_week(d: float) -> float:
265 Convert days into weeks.
270 return convert(d, "Day", "Week")
273 def week_to_sec(w: float) -> float:
275 Convert weeks into seconds.
280 return convert(w, "Week", "Second")
283 def week_to_min(w: float) -> float:
285 Convert weeks into minutes.
290 return convert(w, "Week", "Minute")
293 def week_to_hour(w: float) -> float:
295 Convert weeks into hours.
300 return convert(w, "Week", "Hour")
303 def week_to_day(w: float) -> float:
305 Convert weeks into days.
310 return convert(w, "Week", "Day")
313 def f_to_c(temp_f: float) -> float:
315 Convert Fahrenheit into Celsius.
320 return convert(temp_f, "Fahrenheit", "Celsius")
323 def c_to_f(temp_c: float) -> float:
325 Convert Celsius to Fahrenheit.
330 return convert(temp_c, "Celsius", "Fahrenheit")
333 if __name__ == '__main__':