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)
109 def _convert(magnitude: SupportsFloat, from_unit: Converter, to_unit: Converter) -> float:
110 canonical = from_unit.to_canonical(magnitude)
111 converted = to_unit.from_canonical(canonical)
112 return float(converted)
115 def sec_to_min(s: float) -> float:
117 Convert seconds into minutes.
122 return convert(s, "Second", "Minute")
125 def sec_to_hour(s: float) -> float:
127 Convert seconds into hours.
129 >>> sec_to_hour(1800)
132 return convert(s, "Second", "Hour")
135 def sec_to_day(s: float) -> float:
137 Convert seconds into days.
142 return convert(s, "Second", "Day")
145 def sec_to_week(s: float) -> float:
147 Convert seconds into weeks.
149 >>> sec_to_week(1800)
152 return convert(s, "Second", "Week")
155 def min_to_sec(m: float) -> float:
157 Convert minutes into seconds.
162 return convert(m, "Minute", "Second")
165 def min_to_hour(m: float) -> float:
167 Convert minutes into hours.
172 return convert(m, "Minute", "Hour")
175 def min_to_day(m: float) -> float:
177 Convert minutes into days.
179 >>> min_to_day(60 * 12)
182 return convert(m, "Minute", "Day")
185 def min_to_week(m: float) -> float:
187 Convert minutes into weeks.
189 >>> min_to_week(60 * 24 * 3)
192 return convert(m, "Minute", "Week")
195 def hour_to_sec(h: float) -> float:
197 Convert hours into seconds.
202 return convert(h, "Hour", "Second")
205 def hour_to_min(h: float) -> float:
207 Convert hours into minutes.
212 return convert(h, "Hour", "Minute")
215 def hour_to_day(h: float) -> float:
217 Convert hours into days.
222 return convert(h, "Hour", "Day")
225 def hour_to_week(h: float) -> float:
227 Convert hours into weeks.
232 return convert(h, "Hour", "Week")
235 def day_to_sec(d: float) -> float:
237 Convert days into seconds.
242 return convert(d, "Day", "Second")
245 def day_to_min(d: float) -> float:
247 Convert days into minutes.
252 return convert(d, "Day", "Minute")
255 def day_to_hour(d: float) -> float:
257 Convert days into hours.
262 return convert(d, "Day", "Hour")
265 def day_to_week(d: float) -> float:
267 Convert days into weeks.
272 return convert(d, "Day", "Week")
275 def week_to_sec(w: float) -> float:
277 Convert weeks into seconds.
282 return convert(w, "Week", "Second")
285 def week_to_min(w: float) -> float:
287 Convert weeks into minutes.
292 return convert(w, "Week", "Minute")
295 def week_to_hour(w: float) -> float:
297 Convert weeks into hours.
302 return convert(w, "Week", "Hour")
305 def week_to_day(w: float) -> float:
307 Convert weeks into days.
312 return convert(w, "Week", "Day")
315 def f_to_c(temp_f: float) -> float:
317 Convert Fahrenheit into Celsius.
322 return convert(temp_f, "Fahrenheit", "Celsius")
325 def c_to_f(temp_c: float) -> float:
327 Convert Celsius to Fahrenheit.
332 return convert(temp_c, "Celsius", "Fahrenheit")
335 if __name__ == '__main__':