1use anyhow::{anyhow, Result};
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, PartialEq)]
6enum UnitCategory {
7 Length,
8 Mass,
9 Temperature,
10 Volume,
11 Time,
12 Area,
13 Speed,
14 Pressure,
15 Energy,
16 FuelEfficiency,
17}
18
19#[derive(Debug, Clone)]
21struct Unit {
22 category: UnitCategory,
23 to_si_factor: f64, to_si_offset: f64, from_si_factor: f64, from_si_offset: f64, }
28
29impl Unit {
30 fn simple(category: UnitCategory, to_si_factor: f64) -> Self {
32 Unit {
33 category,
34 to_si_factor,
35 to_si_offset: 0.0,
36 from_si_factor: 1.0 / to_si_factor,
37 from_si_offset: 0.0,
38 }
39 }
40
41 fn temperature(
43 to_si_factor: f64,
44 to_si_offset: f64,
45 from_si_factor: f64,
46 from_si_offset: f64,
47 ) -> Self {
48 Unit {
49 category: UnitCategory::Temperature,
50 to_si_factor,
51 to_si_offset,
52 from_si_factor,
53 from_si_offset,
54 }
55 }
56}
57
58pub struct UnitConverter {
60 units: HashMap<String, Unit>,
61}
62
63impl Default for UnitConverter {
64 fn default() -> Self {
65 Self::new()
66 }
67}
68
69impl UnitConverter {
70 #[must_use]
71 pub fn new() -> Self {
72 let mut units = HashMap::new();
73
74 units.insert("m".to_string(), Unit::simple(UnitCategory::Length, 1.0));
76 units.insert("meter".to_string(), Unit::simple(UnitCategory::Length, 1.0));
77 units.insert(
78 "meters".to_string(),
79 Unit::simple(UnitCategory::Length, 1.0),
80 );
81 units.insert("metre".to_string(), Unit::simple(UnitCategory::Length, 1.0));
82 units.insert(
83 "metres".to_string(),
84 Unit::simple(UnitCategory::Length, 1.0),
85 );
86
87 units.insert("km".to_string(), Unit::simple(UnitCategory::Length, 1000.0));
89 units.insert(
90 "kilometer".to_string(),
91 Unit::simple(UnitCategory::Length, 1000.0),
92 );
93 units.insert(
94 "kilometers".to_string(),
95 Unit::simple(UnitCategory::Length, 1000.0),
96 );
97 units.insert("cm".to_string(), Unit::simple(UnitCategory::Length, 0.01));
98 units.insert(
99 "centimeter".to_string(),
100 Unit::simple(UnitCategory::Length, 0.01),
101 );
102 units.insert(
103 "centimeters".to_string(),
104 Unit::simple(UnitCategory::Length, 0.01),
105 );
106 units.insert("mm".to_string(), Unit::simple(UnitCategory::Length, 0.001));
107 units.insert(
108 "millimeter".to_string(),
109 Unit::simple(UnitCategory::Length, 0.001),
110 );
111 units.insert(
112 "millimeters".to_string(),
113 Unit::simple(UnitCategory::Length, 0.001),
114 );
115 units.insert("nm".to_string(), Unit::simple(UnitCategory::Length, 1e-9));
116 units.insert(
117 "nanometer".to_string(),
118 Unit::simple(UnitCategory::Length, 1e-9),
119 );
120 units.insert("um".to_string(), Unit::simple(UnitCategory::Length, 1e-6));
121 units.insert(
122 "micrometer".to_string(),
123 Unit::simple(UnitCategory::Length, 1e-6),
124 );
125
126 units.insert(
128 "mi".to_string(),
129 Unit::simple(UnitCategory::Length, 1609.344),
130 );
131 units.insert(
132 "mile".to_string(),
133 Unit::simple(UnitCategory::Length, 1609.344),
134 );
135 units.insert(
136 "miles".to_string(),
137 Unit::simple(UnitCategory::Length, 1609.344),
138 );
139 units.insert("yd".to_string(), Unit::simple(UnitCategory::Length, 0.9144));
140 units.insert(
141 "yard".to_string(),
142 Unit::simple(UnitCategory::Length, 0.9144),
143 );
144 units.insert(
145 "yards".to_string(),
146 Unit::simple(UnitCategory::Length, 0.9144),
147 );
148 units.insert("ft".to_string(), Unit::simple(UnitCategory::Length, 0.3048));
149 units.insert(
150 "foot".to_string(),
151 Unit::simple(UnitCategory::Length, 0.3048),
152 );
153 units.insert(
154 "feet".to_string(),
155 Unit::simple(UnitCategory::Length, 0.3048),
156 );
157 units.insert("in".to_string(), Unit::simple(UnitCategory::Length, 0.0254));
158 units.insert(
159 "inch".to_string(),
160 Unit::simple(UnitCategory::Length, 0.0254),
161 );
162 units.insert(
163 "inches".to_string(),
164 Unit::simple(UnitCategory::Length, 0.0254),
165 );
166
167 units.insert(
169 "nmi".to_string(),
170 Unit::simple(UnitCategory::Length, 1852.0),
171 );
172 units.insert(
173 "nautical_mile".to_string(),
174 Unit::simple(UnitCategory::Length, 1852.0),
175 );
176
177 units.insert(
179 "au".to_string(),
180 Unit::simple(UnitCategory::Length, 1.495978707e11), );
182 units.insert(
183 "astronomical_unit".to_string(),
184 Unit::simple(UnitCategory::Length, 1.495978707e11),
185 );
186 units.insert(
187 "lightyear".to_string(),
188 Unit::simple(UnitCategory::Length, 9.4607304725808e15), );
190 units.insert(
191 "light_year".to_string(),
192 Unit::simple(UnitCategory::Length, 9.4607304725808e15),
193 );
194 units.insert(
195 "ly".to_string(),
196 Unit::simple(UnitCategory::Length, 9.4607304725808e15),
197 );
198 units.insert(
199 "parsec".to_string(),
200 Unit::simple(UnitCategory::Length, 3.0857e16), );
202 units.insert(
203 "pc".to_string(),
204 Unit::simple(UnitCategory::Length, 3.0857e16),
205 );
206
207 units.insert("kg".to_string(), Unit::simple(UnitCategory::Mass, 1.0));
209 units.insert(
210 "kilogram".to_string(),
211 Unit::simple(UnitCategory::Mass, 1.0),
212 );
213 units.insert(
214 "kilograms".to_string(),
215 Unit::simple(UnitCategory::Mass, 1.0),
216 );
217
218 units.insert("g".to_string(), Unit::simple(UnitCategory::Mass, 0.001));
220 units.insert("gram".to_string(), Unit::simple(UnitCategory::Mass, 0.001));
221 units.insert("grams".to_string(), Unit::simple(UnitCategory::Mass, 0.001));
222 units.insert("mg".to_string(), Unit::simple(UnitCategory::Mass, 0.000001));
223 units.insert(
224 "milligram".to_string(),
225 Unit::simple(UnitCategory::Mass, 0.000001),
226 );
227 units.insert(
228 "milligrams".to_string(),
229 Unit::simple(UnitCategory::Mass, 0.000001),
230 );
231 units.insert("ug".to_string(), Unit::simple(UnitCategory::Mass, 1e-9));
232 units.insert(
233 "microgram".to_string(),
234 Unit::simple(UnitCategory::Mass, 1e-9),
235 );
236 units.insert("t".to_string(), Unit::simple(UnitCategory::Mass, 1000.0));
237 units.insert(
238 "tonne".to_string(),
239 Unit::simple(UnitCategory::Mass, 1000.0),
240 );
241 units.insert(
242 "metric_ton".to_string(),
243 Unit::simple(UnitCategory::Mass, 1000.0),
244 );
245
246 units.insert(
248 "lb".to_string(),
249 Unit::simple(UnitCategory::Mass, 0.45359237),
250 );
251 units.insert(
252 "lbs".to_string(),
253 Unit::simple(UnitCategory::Mass, 0.45359237),
254 );
255 units.insert(
256 "pound".to_string(),
257 Unit::simple(UnitCategory::Mass, 0.45359237),
258 );
259 units.insert(
260 "pounds".to_string(),
261 Unit::simple(UnitCategory::Mass, 0.45359237),
262 );
263 units.insert(
264 "oz".to_string(),
265 Unit::simple(UnitCategory::Mass, 0.028349523125),
266 );
267 units.insert(
268 "ounce".to_string(),
269 Unit::simple(UnitCategory::Mass, 0.028349523125),
270 );
271 units.insert(
272 "ounces".to_string(),
273 Unit::simple(UnitCategory::Mass, 0.028349523125),
274 );
275 units.insert(
276 "ton".to_string(),
277 Unit::simple(UnitCategory::Mass, 907.18474),
278 );
279 units.insert(
280 "short_ton".to_string(),
281 Unit::simple(UnitCategory::Mass, 907.18474),
282 );
283 units.insert(
284 "long_ton".to_string(),
285 Unit::simple(UnitCategory::Mass, 1016.0469088),
286 );
287 units.insert(
288 "stone".to_string(),
289 Unit::simple(UnitCategory::Mass, 6.35029318),
290 );
291
292 units.insert(
294 "k".to_string(),
295 Unit::simple(UnitCategory::Temperature, 1.0),
296 );
297 units.insert(
298 "kelvin".to_string(),
299 Unit::simple(UnitCategory::Temperature, 1.0),
300 );
301
302 units.insert(
304 "c".to_string(),
305 Unit::temperature(1.0, 273.15, 1.0, -273.15),
306 );
307 units.insert(
308 "celsius".to_string(),
309 Unit::temperature(1.0, 273.15, 1.0, -273.15),
310 );
311 units.insert(
312 "centigrade".to_string(),
313 Unit::temperature(1.0, 273.15, 1.0, -273.15),
314 );
315
316 units.insert(
318 "f".to_string(),
319 Unit::temperature(5.0 / 9.0, 459.67 * 5.0 / 9.0, 9.0 / 5.0, -459.67),
320 );
321 units.insert(
322 "fahrenheit".to_string(),
323 Unit::temperature(5.0 / 9.0, 459.67 * 5.0 / 9.0, 9.0 / 5.0, -459.67),
324 );
325
326 units.insert("l".to_string(), Unit::simple(UnitCategory::Volume, 1.0));
328 units.insert("liter".to_string(), Unit::simple(UnitCategory::Volume, 1.0));
329 units.insert(
330 "liters".to_string(),
331 Unit::simple(UnitCategory::Volume, 1.0),
332 );
333 units.insert("litre".to_string(), Unit::simple(UnitCategory::Volume, 1.0));
334 units.insert(
335 "litres".to_string(),
336 Unit::simple(UnitCategory::Volume, 1.0),
337 );
338
339 units.insert("ml".to_string(), Unit::simple(UnitCategory::Volume, 0.001));
341 units.insert(
342 "milliliter".to_string(),
343 Unit::simple(UnitCategory::Volume, 0.001),
344 );
345 units.insert(
346 "milliliters".to_string(),
347 Unit::simple(UnitCategory::Volume, 0.001),
348 );
349 units.insert("m3".to_string(), Unit::simple(UnitCategory::Volume, 1000.0));
350 units.insert(
351 "cubic_meter".to_string(),
352 Unit::simple(UnitCategory::Volume, 1000.0),
353 );
354 units.insert("cm3".to_string(), Unit::simple(UnitCategory::Volume, 0.001));
355 units.insert(
356 "cubic_centimeter".to_string(),
357 Unit::simple(UnitCategory::Volume, 0.001),
358 );
359 units.insert("cc".to_string(), Unit::simple(UnitCategory::Volume, 0.001));
360
361 units.insert(
363 "gal".to_string(),
364 Unit::simple(UnitCategory::Volume, 3.785411784),
365 );
366 units.insert(
367 "gallon".to_string(),
368 Unit::simple(UnitCategory::Volume, 3.785411784),
369 );
370 units.insert(
371 "gallons".to_string(),
372 Unit::simple(UnitCategory::Volume, 3.785411784),
373 );
374 units.insert(
375 "qt".to_string(),
376 Unit::simple(UnitCategory::Volume, 0.946352946),
377 );
378 units.insert(
379 "quart".to_string(),
380 Unit::simple(UnitCategory::Volume, 0.946352946),
381 );
382 units.insert(
383 "quarts".to_string(),
384 Unit::simple(UnitCategory::Volume, 0.946352946),
385 );
386 units.insert(
387 "pt".to_string(),
388 Unit::simple(UnitCategory::Volume, 0.473176473),
389 );
390 units.insert(
391 "pint".to_string(),
392 Unit::simple(UnitCategory::Volume, 0.473176473),
393 );
394 units.insert(
395 "pints".to_string(),
396 Unit::simple(UnitCategory::Volume, 0.473176473),
397 );
398 units.insert(
399 "cup".to_string(),
400 Unit::simple(UnitCategory::Volume, 0.2365882365),
401 );
402 units.insert(
403 "cups".to_string(),
404 Unit::simple(UnitCategory::Volume, 0.2365882365),
405 );
406 units.insert(
407 "fl_oz".to_string(),
408 Unit::simple(UnitCategory::Volume, 0.0295735295625),
409 );
410 units.insert(
411 "fluid_ounce".to_string(),
412 Unit::simple(UnitCategory::Volume, 0.0295735295625),
413 );
414 units.insert(
415 "tbsp".to_string(),
416 Unit::simple(UnitCategory::Volume, 0.01478676478125),
417 );
418 units.insert(
419 "tablespoon".to_string(),
420 Unit::simple(UnitCategory::Volume, 0.01478676478125),
421 );
422 units.insert(
423 "tsp".to_string(),
424 Unit::simple(UnitCategory::Volume, 0.00492892159375),
425 );
426 units.insert(
427 "teaspoon".to_string(),
428 Unit::simple(UnitCategory::Volume, 0.00492892159375),
429 );
430
431 units.insert(
433 "imperial_gal".to_string(),
434 Unit::simple(UnitCategory::Volume, 4.54609),
435 );
436 units.insert(
437 "imperial_gallon".to_string(),
438 Unit::simple(UnitCategory::Volume, 4.54609),
439 );
440 units.insert(
441 "imperial_qt".to_string(),
442 Unit::simple(UnitCategory::Volume, 1.1365225),
443 );
444 units.insert(
445 "imperial_pt".to_string(),
446 Unit::simple(UnitCategory::Volume, 0.56826125),
447 );
448
449 units.insert("s".to_string(), Unit::simple(UnitCategory::Time, 1.0));
451 units.insert("sec".to_string(), Unit::simple(UnitCategory::Time, 1.0));
452 units.insert("second".to_string(), Unit::simple(UnitCategory::Time, 1.0));
453 units.insert("seconds".to_string(), Unit::simple(UnitCategory::Time, 1.0));
454
455 units.insert("ms".to_string(), Unit::simple(UnitCategory::Time, 0.001));
456 units.insert(
457 "millisecond".to_string(),
458 Unit::simple(UnitCategory::Time, 0.001),
459 );
460 units.insert(
461 "milliseconds".to_string(),
462 Unit::simple(UnitCategory::Time, 0.001),
463 );
464 units.insert("us".to_string(), Unit::simple(UnitCategory::Time, 0.000001));
465 units.insert(
466 "microsecond".to_string(),
467 Unit::simple(UnitCategory::Time, 0.000001),
468 );
469 units.insert(
470 "microseconds".to_string(),
471 Unit::simple(UnitCategory::Time, 0.000001),
472 );
473 units.insert(
474 "ns".to_string(),
475 Unit::simple(UnitCategory::Time, 0.000000001),
476 );
477 units.insert(
478 "nanosecond".to_string(),
479 Unit::simple(UnitCategory::Time, 0.000000001),
480 );
481 units.insert(
482 "nanoseconds".to_string(),
483 Unit::simple(UnitCategory::Time, 0.000000001),
484 );
485
486 units.insert("min".to_string(), Unit::simple(UnitCategory::Time, 60.0));
487 units.insert("minute".to_string(), Unit::simple(UnitCategory::Time, 60.0));
488 units.insert(
489 "minutes".to_string(),
490 Unit::simple(UnitCategory::Time, 60.0),
491 );
492 units.insert("hr".to_string(), Unit::simple(UnitCategory::Time, 3600.0));
493 units.insert("hour".to_string(), Unit::simple(UnitCategory::Time, 3600.0));
494 units.insert(
495 "hours".to_string(),
496 Unit::simple(UnitCategory::Time, 3600.0),
497 );
498 units.insert("day".to_string(), Unit::simple(UnitCategory::Time, 86400.0));
499 units.insert(
500 "days".to_string(),
501 Unit::simple(UnitCategory::Time, 86400.0),
502 );
503 units.insert(
504 "week".to_string(),
505 Unit::simple(UnitCategory::Time, 604800.0),
506 );
507 units.insert(
508 "weeks".to_string(),
509 Unit::simple(UnitCategory::Time, 604800.0),
510 );
511 units.insert(
512 "month".to_string(),
513 Unit::simple(UnitCategory::Time, 2629746.0),
514 ); units.insert(
516 "months".to_string(),
517 Unit::simple(UnitCategory::Time, 2629746.0),
518 );
519 units.insert(
520 "year".to_string(),
521 Unit::simple(UnitCategory::Time, 31557600.0),
522 ); units.insert(
524 "years".to_string(),
525 Unit::simple(UnitCategory::Time, 31557600.0),
526 );
527
528 units.insert("m2".to_string(), Unit::simple(UnitCategory::Area, 1.0));
530 units.insert("sqm".to_string(), Unit::simple(UnitCategory::Area, 1.0));
531 units.insert(
532 "square_meter".to_string(),
533 Unit::simple(UnitCategory::Area, 1.0),
534 );
535 units.insert(
536 "square_meters".to_string(),
537 Unit::simple(UnitCategory::Area, 1.0),
538 );
539
540 units.insert(
541 "km2".to_string(),
542 Unit::simple(UnitCategory::Area, 1000000.0),
543 );
544 units.insert(
545 "sqkm".to_string(),
546 Unit::simple(UnitCategory::Area, 1000000.0),
547 );
548 units.insert(
549 "square_kilometer".to_string(),
550 Unit::simple(UnitCategory::Area, 1000000.0),
551 );
552 units.insert("cm2".to_string(), Unit::simple(UnitCategory::Area, 0.0001));
553 units.insert(
554 "square_centimeter".to_string(),
555 Unit::simple(UnitCategory::Area, 0.0001),
556 );
557
558 units.insert(
559 "sq_ft".to_string(),
560 Unit::simple(UnitCategory::Area, 0.09290304),
561 );
562 units.insert(
563 "sqft".to_string(),
564 Unit::simple(UnitCategory::Area, 0.09290304),
565 );
566 units.insert(
567 "square_foot".to_string(),
568 Unit::simple(UnitCategory::Area, 0.09290304),
569 );
570 units.insert(
571 "square_feet".to_string(),
572 Unit::simple(UnitCategory::Area, 0.09290304),
573 );
574 units.insert(
575 "sq_in".to_string(),
576 Unit::simple(UnitCategory::Area, 0.00064516),
577 );
578 units.insert(
579 "square_inch".to_string(),
580 Unit::simple(UnitCategory::Area, 0.00064516),
581 );
582 units.insert(
583 "sq_mi".to_string(),
584 Unit::simple(UnitCategory::Area, 2589988.110336),
585 );
586 units.insert(
587 "sqmiles".to_string(),
588 Unit::simple(UnitCategory::Area, 2589988.110336),
589 );
590 units.insert(
591 "square_mile".to_string(),
592 Unit::simple(UnitCategory::Area, 2589988.110336),
593 );
594 units.insert(
595 "acre".to_string(),
596 Unit::simple(UnitCategory::Area, 4046.8564224),
597 );
598 units.insert(
599 "acres".to_string(),
600 Unit::simple(UnitCategory::Area, 4046.8564224),
601 );
602 units.insert(
603 "hectare".to_string(),
604 Unit::simple(UnitCategory::Area, 10000.0),
605 );
606 units.insert(
607 "hectares".to_string(),
608 Unit::simple(UnitCategory::Area, 10000.0),
609 );
610
611 units.insert("mps".to_string(), Unit::simple(UnitCategory::Speed, 1.0));
613 units.insert("m/s".to_string(), Unit::simple(UnitCategory::Speed, 1.0));
614 units.insert(
615 "meters_per_second".to_string(),
616 Unit::simple(UnitCategory::Speed, 1.0),
617 );
618
619 units.insert(
620 "kph".to_string(),
621 Unit::simple(UnitCategory::Speed, 0.277777778),
622 );
623 units.insert(
624 "kmh".to_string(),
625 Unit::simple(UnitCategory::Speed, 0.277777778),
626 );
627 units.insert(
628 "km/h".to_string(),
629 Unit::simple(UnitCategory::Speed, 0.277777778),
630 );
631 units.insert(
632 "kilometers_per_hour".to_string(),
633 Unit::simple(UnitCategory::Speed, 0.277777778),
634 );
635
636 units.insert(
637 "mph".to_string(),
638 Unit::simple(UnitCategory::Speed, 0.44704),
639 );
640 units.insert(
641 "mi/h".to_string(),
642 Unit::simple(UnitCategory::Speed, 0.44704),
643 );
644 units.insert(
645 "miles_per_hour".to_string(),
646 Unit::simple(UnitCategory::Speed, 0.44704),
647 );
648
649 units.insert(
650 "knot".to_string(),
651 Unit::simple(UnitCategory::Speed, 0.514444444),
652 );
653 units.insert(
654 "knots".to_string(),
655 Unit::simple(UnitCategory::Speed, 0.514444444),
656 );
657 units.insert(
658 "kt".to_string(),
659 Unit::simple(UnitCategory::Speed, 0.514444444),
660 );
661
662 units.insert("fps".to_string(), Unit::simple(UnitCategory::Speed, 0.3048));
663 units.insert(
664 "ft/s".to_string(),
665 Unit::simple(UnitCategory::Speed, 0.3048),
666 );
667 units.insert(
668 "feet_per_second".to_string(),
669 Unit::simple(UnitCategory::Speed, 0.3048),
670 );
671
672 units.insert(
675 "light_speed".to_string(),
676 Unit::simple(UnitCategory::Speed, 299792458.0),
677 );
678
679 units.insert("pa".to_string(), Unit::simple(UnitCategory::Pressure, 1.0));
681 units.insert(
682 "pascal".to_string(),
683 Unit::simple(UnitCategory::Pressure, 1.0),
684 );
685 units.insert(
686 "pascals".to_string(),
687 Unit::simple(UnitCategory::Pressure, 1.0),
688 );
689
690 units.insert(
691 "kpa".to_string(),
692 Unit::simple(UnitCategory::Pressure, 1000.0),
693 );
694 units.insert(
695 "kilopascal".to_string(),
696 Unit::simple(UnitCategory::Pressure, 1000.0),
697 );
698 units.insert(
699 "mpa".to_string(),
700 Unit::simple(UnitCategory::Pressure, 1000000.0),
701 );
702 units.insert(
703 "megapascal".to_string(),
704 Unit::simple(UnitCategory::Pressure, 1000000.0),
705 );
706 units.insert(
707 "gpa".to_string(),
708 Unit::simple(UnitCategory::Pressure, 1000000000.0),
709 );
710 units.insert(
711 "gigapascal".to_string(),
712 Unit::simple(UnitCategory::Pressure, 1000000000.0),
713 );
714
715 units.insert(
716 "bar".to_string(),
717 Unit::simple(UnitCategory::Pressure, 100000.0),
718 );
719 units.insert(
720 "bars".to_string(),
721 Unit::simple(UnitCategory::Pressure, 100000.0),
722 );
723 units.insert(
724 "mbar".to_string(),
725 Unit::simple(UnitCategory::Pressure, 100.0),
726 );
727 units.insert(
728 "millibar".to_string(),
729 Unit::simple(UnitCategory::Pressure, 100.0),
730 );
731
732 units.insert(
733 "atm".to_string(),
734 Unit::simple(UnitCategory::Pressure, 101325.0),
735 );
736 units.insert(
737 "atmosphere".to_string(),
738 Unit::simple(UnitCategory::Pressure, 101325.0),
739 );
740 units.insert(
741 "atmospheres".to_string(),
742 Unit::simple(UnitCategory::Pressure, 101325.0),
743 );
744
745 units.insert(
746 "psi".to_string(),
747 Unit::simple(UnitCategory::Pressure, 6894.757293168),
748 );
749 units.insert(
750 "pounds_per_square_inch".to_string(),
751 Unit::simple(UnitCategory::Pressure, 6894.757293168),
752 );
753
754 units.insert(
755 "torr".to_string(),
756 Unit::simple(UnitCategory::Pressure, 133.322368421),
757 );
758 units.insert(
759 "mmhg".to_string(),
760 Unit::simple(UnitCategory::Pressure, 133.322368421),
761 );
762 units.insert(
763 "mm_hg".to_string(),
764 Unit::simple(UnitCategory::Pressure, 133.322368421),
765 );
766
767 units.insert("j".to_string(), Unit::simple(UnitCategory::Energy, 1.0));
769 units.insert("joule".to_string(), Unit::simple(UnitCategory::Energy, 1.0));
770 units.insert(
771 "joules".to_string(),
772 Unit::simple(UnitCategory::Energy, 1.0),
773 );
774
775 units.insert("kj".to_string(), Unit::simple(UnitCategory::Energy, 1000.0));
776 units.insert(
777 "kilojoule".to_string(),
778 Unit::simple(UnitCategory::Energy, 1000.0),
779 );
780 units.insert(
781 "kilojoules".to_string(),
782 Unit::simple(UnitCategory::Energy, 1000.0),
783 );
784
785 units.insert("mj".to_string(), Unit::simple(UnitCategory::Energy, 1e6));
786 units.insert(
787 "megajoule".to_string(),
788 Unit::simple(UnitCategory::Energy, 1e6),
789 );
790
791 units.insert("cal".to_string(), Unit::simple(UnitCategory::Energy, 4.184));
792 units.insert(
793 "calorie".to_string(),
794 Unit::simple(UnitCategory::Energy, 4.184),
795 );
796 units.insert(
797 "calories".to_string(),
798 Unit::simple(UnitCategory::Energy, 4.184),
799 );
800
801 units.insert(
802 "kcal".to_string(),
803 Unit::simple(UnitCategory::Energy, 4184.0),
804 );
805 units.insert(
806 "kilocalorie".to_string(),
807 Unit::simple(UnitCategory::Energy, 4184.0),
808 );
809 units.insert(
810 "kilocalories".to_string(),
811 Unit::simple(UnitCategory::Energy, 4184.0),
812 );
813
814 units.insert("kwh".to_string(), Unit::simple(UnitCategory::Energy, 3.6e6));
815 units.insert(
816 "kilowatt_hour".to_string(),
817 Unit::simple(UnitCategory::Energy, 3.6e6),
818 );
819 units.insert(
820 "kilowatt_hours".to_string(),
821 Unit::simple(UnitCategory::Energy, 3.6e6),
822 );
823
824 units.insert("wh".to_string(), Unit::simple(UnitCategory::Energy, 3600.0));
825 units.insert(
826 "watt_hour".to_string(),
827 Unit::simple(UnitCategory::Energy, 3600.0),
828 );
829
830 units.insert(
831 "btu".to_string(),
832 Unit::simple(UnitCategory::Energy, 1055.056),
833 );
834 units.insert(
835 "british_thermal_unit".to_string(),
836 Unit::simple(UnitCategory::Energy, 1055.056),
837 );
838
839 units.insert(
840 "ev".to_string(),
841 Unit::simple(UnitCategory::Energy, 1.60218e-19),
842 );
843 units.insert(
844 "electron_volt".to_string(),
845 Unit::simple(UnitCategory::Energy, 1.60218e-19),
846 );
847
848 units.insert("erg".to_string(), Unit::simple(UnitCategory::Energy, 1e-7));
849 units.insert("ergs".to_string(), Unit::simple(UnitCategory::Energy, 1e-7));
850
851 units.insert(
853 "km/l".to_string(),
854 Unit::simple(UnitCategory::FuelEfficiency, 1.0),
855 );
856 units.insert(
857 "kml".to_string(),
858 Unit::simple(UnitCategory::FuelEfficiency, 1.0),
859 );
860
861 units.insert(
863 "mpg".to_string(),
864 Unit::simple(UnitCategory::FuelEfficiency, 0.425144), );
866 units.insert(
867 "miles/gallon".to_string(),
868 Unit::simple(UnitCategory::FuelEfficiency, 0.425144),
869 );
870
871 units.insert(
873 "mpg_uk".to_string(),
874 Unit::simple(UnitCategory::FuelEfficiency, 0.354006), );
876 units.insert(
877 "mpg_imp".to_string(),
878 Unit::simple(UnitCategory::FuelEfficiency, 0.354006),
879 );
880
881 units.insert(
884 "km/kwh".to_string(),
885 Unit::simple(UnitCategory::FuelEfficiency, 0.105263), );
887 units.insert(
888 "kmkwh".to_string(),
889 Unit::simple(UnitCategory::FuelEfficiency, 0.105263),
890 );
891
892 units.insert(
894 "mi/kwh".to_string(),
895 Unit::simple(UnitCategory::FuelEfficiency, 0.169349), );
897 units.insert(
898 "miles/kwh".to_string(),
899 Unit::simple(UnitCategory::FuelEfficiency, 0.169349),
900 );
901
902 units.insert(
905 "mpge".to_string(),
906 Unit::simple(UnitCategory::FuelEfficiency, 0.425144), );
908
909 units.insert(
912 "nmi/ton".to_string(),
913 Unit::simple(UnitCategory::FuelEfficiency, 0.001948), );
915 units.insert(
916 "nautical_miles/ton".to_string(),
917 Unit::simple(UnitCategory::FuelEfficiency, 0.001948),
918 );
919
920 units.insert(
922 "km/ton".to_string(),
923 Unit::simple(UnitCategory::FuelEfficiency, 0.001053), );
925
926 UnitConverter { units }
927 }
928
929 pub fn convert(&self, value: f64, from_unit: &str, to_unit: &str) -> Result<f64> {
931 let from_key = from_unit.to_lowercase();
933 let to_key = to_unit.to_lowercase();
934
935 let from = self
937 .units
938 .get(&from_key)
939 .ok_or_else(|| anyhow!("Unknown unit: {}", from_unit))?;
940 let to = self
941 .units
942 .get(&to_key)
943 .ok_or_else(|| anyhow!("Unknown unit: {}", to_unit))?;
944
945 if from.category != to.category {
947 return Err(anyhow!(
948 "Cannot convert between different unit types: {} ({:?}) to {} ({:?})",
949 from_unit,
950 from.category,
951 to_unit,
952 to.category
953 ));
954 }
955
956 let si_value = value * from.to_si_factor + from.to_si_offset;
958
959 let result = si_value * to.from_si_factor + to.from_si_offset;
961
962 Ok(result)
963 }
964
965 #[must_use]
967 pub fn get_units_for_category(&self, category: &str) -> Vec<String> {
968 let cat = match category.to_lowercase().as_str() {
969 "length" => Some(UnitCategory::Length),
970 "mass" | "weight" => Some(UnitCategory::Mass),
971 "temperature" | "temp" => Some(UnitCategory::Temperature),
972 "volume" => Some(UnitCategory::Volume),
973 "time" => Some(UnitCategory::Time),
974 "area" => Some(UnitCategory::Area),
975 "speed" | "velocity" => Some(UnitCategory::Speed),
976 "pressure" => Some(UnitCategory::Pressure),
977 "energy" => Some(UnitCategory::Energy),
978 "fuel_efficiency" | "efficiency" => Some(UnitCategory::FuelEfficiency),
979 _ => None,
980 };
981
982 if let Some(category) = cat {
983 self.units
984 .iter()
985 .filter(|(_, unit)| unit.category == category)
986 .map(|(name, _)| name.clone())
987 .collect()
988 } else {
989 Vec::new()
990 }
991 }
992}
993
994lazy_static::lazy_static! {
996 static ref CONVERTER: UnitConverter = UnitConverter::new();
997}
998
999pub fn convert_units(value: f64, from_unit: &str, to_unit: &str) -> Result<f64> {
1001 CONVERTER.convert(value, from_unit, to_unit)
1002}
1003
1004#[must_use]
1006pub fn list_units(category: &str) -> Vec<String> {
1007 CONVERTER.get_units_for_category(category)
1008}
1009
1010#[cfg(test)]
1011mod tests {
1012 use super::*;
1013
1014 #[test]
1015 fn test_length_conversions() {
1016 let converter = UnitConverter::new();
1017
1018 let result = converter.convert(100.0, "km", "miles").unwrap();
1020 assert!((result - 62.137).abs() < 0.01);
1021
1022 let result = converter.convert(100.0, "ft", "m").unwrap();
1024 assert!((result - 30.48).abs() < 0.01);
1025
1026 let result = converter.convert(12.0, "in", "cm").unwrap();
1028 assert!((result - 30.48).abs() < 0.01);
1029 }
1030
1031 #[test]
1032 fn test_mass_conversions() {
1033 let converter = UnitConverter::new();
1034
1035 let result = converter.convert(100.0, "lb", "kg").unwrap();
1037 assert!((result - 45.359).abs() < 0.01);
1038
1039 let result = converter.convert(16.0, "oz", "g").unwrap();
1041 assert!((result - 453.592).abs() < 0.01);
1042 }
1043
1044 #[test]
1045 fn test_temperature_conversions() {
1046 let converter = UnitConverter::new();
1047
1048 let result = converter.convert(32.0, "F", "C").unwrap();
1050 assert!((result - 0.0).abs() < 0.01);
1051
1052 let result = converter.convert(212.0, "F", "C").unwrap();
1053 assert!((result - 100.0).abs() < 0.01);
1054
1055 let result = converter.convert(0.0, "C", "K").unwrap();
1057 assert!((result - 273.15).abs() < 0.01);
1058 }
1059
1060 #[test]
1061 fn test_volume_conversions() {
1062 let converter = UnitConverter::new();
1063
1064 let result = converter.convert(1.0, "gal", "L").unwrap();
1066 assert!((result - 3.785).abs() < 0.01);
1067
1068 let result = converter.convert(1.0, "cup", "ml").unwrap();
1070 assert!((result - 236.588).abs() < 0.01);
1071 }
1072
1073 #[test]
1074 fn test_invalid_conversion() {
1075 let converter = UnitConverter::new();
1076
1077 let result = converter.convert(100.0, "km", "kg");
1079 assert!(result.is_err());
1080
1081 let result = converter.convert(100.0, "xyz", "m");
1083 assert!(result.is_err());
1084 }
1085
1086 #[test]
1087 fn test_case_insensitive() {
1088 let converter = UnitConverter::new();
1089
1090 let result1 = converter.convert(100.0, "KM", "MILES").unwrap();
1092 let result2 = converter.convert(100.0, "km", "miles").unwrap();
1093 let result3 = converter.convert(100.0, "Km", "Miles").unwrap();
1094
1095 assert!((result1 - result2).abs() < 0.001);
1096 assert!((result2 - result3).abs() < 0.001);
1097 }
1098}