2 # -*- coding: utf-8 -*-
4 """Utilities involving converting between different units."""
6 from typing import Callable, SupportsFloat
11 class Converter(object):
12 """A converter has a canonical name and a category. The name defines
13 a unit of measurement in a category or class of measurements.
14 This framework will allow conversion between named units in the
15 same category. e.g. name may be "meter", "inch", "mile" and their
16 category may be "length".
18 The way that conversions work is that we convert the magnitude
19 first to a canonical unit and then (if needed) from the canonical
20 unit to the desired destination unit. e.g. if "meter" is the
21 canonical unit of measurement for the category "length", in order
22 to convert miles into inches we first convert miles into meters
23 and, from there, meters into inches. This is potentially
24 dangerous because it requires two floating point operations which
25 each have the potential to overflow, underflow, or introduce
26 floating point errors. Caveat emptor.
33 to_canonical: Callable, # convert to canonical unit
34 from_canonical: Callable, # convert from canonical unit
38 self.category = category
39 self.to_canonical_f = to_canonical
40 self.from_canonical_f = from_canonical
43 def to_canonical(self, n: SupportsFloat) -> SupportsFloat:
44 return self.to_canonical_f(n)
46 def from_canonical(self, n: SupportsFloat) -> SupportsFloat:
47 return self.from_canonical_f(n)
49 def unit_suffix(self) -> str:
53 # A catalog of converters.
54 conversion_catalog = {
55 "Second": Converter("Second", "time", lambda s: s, lambda s: s, "s"),
59 lambda m: (m * constants.SECONDS_PER_MINUTE),
60 lambda s: (s / constants.SECONDS_PER_MINUTE),
66 lambda h: (h * constants.SECONDS_PER_HOUR),
67 lambda s: (s / constants.SECONDS_PER_HOUR),
73 lambda d: (d * constants.SECONDS_PER_DAY),
74 lambda s: (s / constants.SECONDS_PER_DAY),
80 lambda w: (w * constants.SECONDS_PER_WEEK),
81 lambda s: (s / constants.SECONDS_PER_WEEK),
84 "Fahrenheit": Converter(
87 lambda f: (f - 32.0) * 0.55555555,
88 lambda c: c * 1.8 + 32.0,
91 "Celsius": Converter("Celsius", "temperature", lambda c: c, lambda c: c, "°C"),
102 def convert(magnitude: SupportsFloat, 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)
112 def _convert(magnitude: SupportsFloat, from_unit: Converter, to_unit: Converter) -> float:
113 canonical = from_unit.to_canonical(magnitude)
114 converted = to_unit.from_canonical(canonical)
115 return float(converted)
118 def sec_to_min(s: float) -> float:
120 Convert seconds into minutes.
125 return convert(s, "Second", "Minute")
128 def sec_to_hour(s: float) -> float:
130 Convert seconds into hours.
132 >>> sec_to_hour(1800)
135 return convert(s, "Second", "Hour")
138 def sec_to_day(s: float) -> float:
140 Convert seconds into days.
145 return convert(s, "Second", "Day")
148 def sec_to_week(s: float) -> float:
150 Convert seconds into weeks.
152 >>> sec_to_week(1800)
155 return convert(s, "Second", "Week")
158 def min_to_sec(m: float) -> float:
160 Convert minutes into seconds.
165 return convert(m, "Minute", "Second")
168 def min_to_hour(m: float) -> float:
170 Convert minutes into hours.
175 return convert(m, "Minute", "Hour")
178 def min_to_day(m: float) -> float:
180 Convert minutes into days.
182 >>> min_to_day(60 * 12)
185 return convert(m, "Minute", "Day")
188 def min_to_week(m: float) -> float:
190 Convert minutes into weeks.
192 >>> min_to_week(60 * 24 * 3)
195 return convert(m, "Minute", "Week")
198 def hour_to_sec(h: float) -> float:
200 Convert hours into seconds.
205 return convert(h, "Hour", "Second")
208 def hour_to_min(h: float) -> float:
210 Convert hours into minutes.
215 return convert(h, "Hour", "Minute")
218 def hour_to_day(h: float) -> float:
220 Convert hours into days.
225 return convert(h, "Hour", "Day")
228 def hour_to_week(h: float) -> float:
230 Convert hours into weeks.
235 return convert(h, "Hour", "Week")
238 def day_to_sec(d: float) -> float:
240 Convert days into seconds.
245 return convert(d, "Day", "Second")
248 def day_to_min(d: float) -> float:
250 Convert days into minutes.
255 return convert(d, "Day", "Minute")
258 def day_to_hour(d: float) -> float:
260 Convert days into hours.
265 return convert(d, "Day", "Hour")
268 def day_to_week(d: float) -> float:
270 Convert days into weeks.
275 return convert(d, "Day", "Week")
278 def week_to_sec(w: float) -> float:
280 Convert weeks into seconds.
285 return convert(w, "Week", "Second")
288 def week_to_min(w: float) -> float:
290 Convert weeks into minutes.
295 return convert(w, "Week", "Minute")
298 def week_to_hour(w: float) -> float:
300 Convert weeks into hours.
305 return convert(w, "Week", "Hour")
308 def week_to_day(w: float) -> float:
310 Convert weeks into days.
315 return convert(w, "Week", "Day")
318 def f_to_c(temp_f: float) -> float:
320 Convert Fahrenheit into Celsius.
325 return convert(temp_f, "Fahrenheit", "Celsius")
328 def c_to_f(temp_c: float) -> float:
330 Convert Celsius to Fahrenheit.
335 return convert(temp_c, "Celsius", "Fahrenheit")
338 if __name__ == '__main__':