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,
90 "Celsius", "temperature", lambda c: c, lambda c: c, "°C"
102 def convert(magnitude: Number, from_thing: str, to_thing: str) -> float:
103 src = conversion_catalog.get(from_thing, None)
104 dst = conversion_catalog.get(to_thing, None)
105 if src is None or dst is None:
106 raise ValueError("No known conversion")
107 if src.category != dst.category:
108 raise ValueError("Incompatible conversion")
109 return _convert(magnitude, src, dst)
113 magnitude: Number, from_unit: Converter, to_unit: Converter
115 canonical = from_unit.to_canonical(magnitude)
116 converted = to_unit.from_canonical(canonical)
117 return float(converted)
120 def sec_to_min(s: float) -> float:
122 Convert seconds into minutes.
127 return convert(s, "Second", "Minute")
130 def sec_to_hour(s: float) -> float:
132 Convert seconds into hours.
134 >>> sec_to_hour(1800)
137 return convert(s, "Second", "Hour")
140 def sec_to_day(s: float) -> float:
142 Convert seconds into days.
147 return convert(s, "Second", "Day")
150 def sec_to_week(s: float) -> float:
152 Convert seconds into weeks.
154 >>> sec_to_week(1800)
157 return convert(s, "Second", "Week")
160 def min_to_sec(m: float) -> float:
162 Convert minutes into seconds.
167 return convert(m, "Minute", "Second")
170 def min_to_hour(m: float) -> float:
172 Convert minutes into hours.
177 return convert(m, "Minute", "Hour")
180 def min_to_day(m: float) -> float:
182 Convert minutes into days.
184 >>> min_to_day(60 * 12)
187 return convert(m, "Minute", "Day")
190 def min_to_week(m: float) -> float:
192 Convert minutes into weeks.
194 >>> min_to_week(60 * 24 * 3)
197 return convert(m, "Minute", "Week")
200 def hour_to_sec(h: float) -> float:
202 Convert hours into seconds.
207 return convert(h, "Hour", "Second")
210 def hour_to_min(h: float) -> float:
212 Convert hours into minutes.
217 return convert(h, "Hour", "Minute")
220 def hour_to_day(h: float) -> float:
222 Convert hours into days.
227 return convert(h, "Hour", "Day")
230 def hour_to_week(h: float) -> float:
232 Convert hours into weeks.
237 return convert(h, "Hour", "Week")
240 def day_to_sec(d: float) -> float:
242 Convert days into seconds.
247 return convert(d, "Day", "Second")
250 def day_to_min(d: float) -> float:
252 Convert days into minutes.
257 return convert(d, "Day", "Minute")
260 def day_to_hour(d: float) -> float:
262 Convert days into hours.
267 return convert(d, "Day", "Hour")
270 def day_to_week(d: float) -> float:
272 Convert days into weeks.
277 return convert(d, "Day", "Week")
280 def week_to_sec(w: float) -> float:
282 Convert weeks into seconds.
287 return convert(w, "Week", "Second")
290 def week_to_min(w: float) -> float:
292 Convert weeks into minutes.
297 return convert(w, "Week", "Minute")
300 def week_to_hour(w: float) -> float:
302 Convert weeks into hours.
307 return convert(w, "Week", "Hour")
310 def week_to_day(w: float) -> float:
312 Convert weeks into days.
317 return convert(w, "Week", "Day")
320 def f_to_c(temp_f: float) -> float:
322 Convert Fahrenheit into Celsius.
327 return convert(temp_f, "Fahrenheit", "Celsius")
330 def c_to_f(temp_c: float) -> float:
332 Convert Celsius to Fahrenheit.
337 return convert(temp_c, "Celsius", "Fahrenheit")
340 if __name__ == '__main__':