3 from typing import Callable, SupportsFloat
8 class Converter(object):
9 """A converter has a canonical name and a category. The name defines
10 a unit of measurement in a category or class of measurements.
11 This framework will allow conversion between named units in the
12 same category. e.g. name may be "meter", "inch", "mile" and their
13 category may be "length".
15 The way that conversions work is that we convert the magnitude
16 first to a canonical unit and then (if needed) from the canonical
17 unit to the desired destination unit. e.g. if "meter" is the
18 canonical unit of measurement for the category "length", in order
19 to convert miles into inches we first convert miles into meters
20 and, from there, meters into inches. This is potentially
21 dangerous because it requires two floating point operations which
22 each have the potential to overflow, underflow, or introduce
23 floating point errors. Caveat emptor.
30 to_canonical: Callable, # convert to canonical unit
31 from_canonical: Callable, # convert from canonical unit
35 self.category = category
36 self.to_canonical_f = to_canonical
37 self.from_canonical_f = from_canonical
40 def to_canonical(self, n: SupportsFloat) -> SupportsFloat:
41 return self.to_canonical_f(n)
43 def from_canonical(self, n: SupportsFloat) -> SupportsFloat:
44 return self.from_canonical_f(n)
46 def unit_suffix(self) -> str:
50 # A catalog of converters.
51 conversion_catalog = {
52 "Second": Converter("Second", "time", lambda s: s, lambda s: s, "s"),
56 lambda m: (m * constants.SECONDS_PER_MINUTE),
57 lambda s: (s / constants.SECONDS_PER_MINUTE),
63 lambda h: (h * constants.SECONDS_PER_HOUR),
64 lambda s: (s / constants.SECONDS_PER_HOUR),
70 lambda d: (d * constants.SECONDS_PER_DAY),
71 lambda s: (s / constants.SECONDS_PER_DAY),
77 lambda w: (w * constants.SECONDS_PER_WEEK),
78 lambda s: (s / constants.SECONDS_PER_WEEK),
81 "Fahrenheit": Converter(
84 lambda f: (f - 32.0) * 0.55555555,
85 lambda c: c * 1.8 + 32.0,
88 "Celsius": Converter("Celsius", "temperature", lambda c: c, lambda c: c, "°C"),
99 def convert(magnitude: SupportsFloat, from_thing: str, to_thing: str) -> float:
100 src = conversion_catalog.get(from_thing, None)
101 dst = conversion_catalog.get(to_thing, None)
102 if src is None or dst is None:
103 raise ValueError("No known conversion")
104 if src.category != dst.category:
105 raise ValueError("Incompatible conversion")
106 return _convert(magnitude, src, dst)
110 magnitude: SupportsFloat, from_unit: Converter, to_unit: Converter
112 canonical = from_unit.to_canonical(magnitude)
113 converted = to_unit.from_canonical(canonical)
114 return float(converted)
117 def sec_to_min(s: float) -> float:
119 Convert seconds into minutes.
124 return convert(s, "Second", "Minute")
127 def sec_to_hour(s: float) -> float:
129 Convert seconds into hours.
131 >>> sec_to_hour(1800)
134 return convert(s, "Second", "Hour")
137 def sec_to_day(s: float) -> float:
139 Convert seconds into days.
144 return convert(s, "Second", "Day")
147 def sec_to_week(s: float) -> float:
149 Convert seconds into weeks.
151 >>> sec_to_week(1800)
154 return convert(s, "Second", "Week")
157 def min_to_sec(m: float) -> float:
159 Convert minutes into seconds.
164 return convert(m, "Minute", "Second")
167 def min_to_hour(m: float) -> float:
169 Convert minutes into hours.
174 return convert(m, "Minute", "Hour")
177 def min_to_day(m: float) -> float:
179 Convert minutes into days.
181 >>> min_to_day(60 * 12)
184 return convert(m, "Minute", "Day")
187 def min_to_week(m: float) -> float:
189 Convert minutes into weeks.
191 >>> min_to_week(60 * 24 * 3)
194 return convert(m, "Minute", "Week")
197 def hour_to_sec(h: float) -> float:
199 Convert hours into seconds.
204 return convert(h, "Hour", "Second")
207 def hour_to_min(h: float) -> float:
209 Convert hours into minutes.
214 return convert(h, "Hour", "Minute")
217 def hour_to_day(h: float) -> float:
219 Convert hours into days.
224 return convert(h, "Hour", "Day")
227 def hour_to_week(h: float) -> float:
229 Convert hours into weeks.
234 return convert(h, "Hour", "Week")
237 def day_to_sec(d: float) -> float:
239 Convert days into seconds.
244 return convert(d, "Day", "Second")
247 def day_to_min(d: float) -> float:
249 Convert days into minutes.
254 return convert(d, "Day", "Minute")
257 def day_to_hour(d: float) -> float:
259 Convert days into hours.
264 return convert(d, "Day", "Hour")
267 def day_to_week(d: float) -> float:
269 Convert days into weeks.
274 return convert(d, "Day", "Week")
277 def week_to_sec(w: float) -> float:
279 Convert weeks into seconds.
284 return convert(w, "Week", "Second")
287 def week_to_min(w: float) -> float:
289 Convert weeks into minutes.
294 return convert(w, "Week", "Minute")
297 def week_to_hour(w: float) -> float:
299 Convert weeks into hours.
304 return convert(w, "Week", "Hour")
307 def week_to_day(w: float) -> float:
309 Convert weeks into days.
314 return convert(w, "Week", "Day")
317 def f_to_c(temp_f: float) -> float:
319 Convert Fahrenheit into Celsius.
324 return convert(temp_f, "Fahrenheit", "Celsius")
327 def c_to_f(temp_c: float) -> float:
329 Convert Celsius to Fahrenheit.
334 return convert(temp_c, "Celsius", "Fahrenheit")
337 if __name__ == '__main__':