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}
16
17#[derive(Debug, Clone)]
19struct Unit {
20 category: UnitCategory,
21 to_si_factor: f64, to_si_offset: f64, from_si_factor: f64, from_si_offset: f64, }
26
27impl Unit {
28 fn simple(category: UnitCategory, to_si_factor: f64) -> Self {
30 Unit {
31 category,
32 to_si_factor,
33 to_si_offset: 0.0,
34 from_si_factor: 1.0 / to_si_factor,
35 from_si_offset: 0.0,
36 }
37 }
38
39 fn temperature(
41 to_si_factor: f64,
42 to_si_offset: f64,
43 from_si_factor: f64,
44 from_si_offset: f64,
45 ) -> Self {
46 Unit {
47 category: UnitCategory::Temperature,
48 to_si_factor,
49 to_si_offset,
50 from_si_factor,
51 from_si_offset,
52 }
53 }
54}
55
56pub struct UnitConverter {
58 units: HashMap<String, Unit>,
59}
60
61impl Default for UnitConverter {
62 fn default() -> Self {
63 Self::new()
64 }
65}
66
67impl UnitConverter {
68 #[must_use]
69 pub fn new() -> Self {
70 let mut units = HashMap::new();
71
72 units.insert("m".to_string(), Unit::simple(UnitCategory::Length, 1.0));
74 units.insert("meter".to_string(), Unit::simple(UnitCategory::Length, 1.0));
75 units.insert(
76 "meters".to_string(),
77 Unit::simple(UnitCategory::Length, 1.0),
78 );
79 units.insert("metre".to_string(), Unit::simple(UnitCategory::Length, 1.0));
80 units.insert(
81 "metres".to_string(),
82 Unit::simple(UnitCategory::Length, 1.0),
83 );
84
85 units.insert("km".to_string(), Unit::simple(UnitCategory::Length, 1000.0));
87 units.insert(
88 "kilometer".to_string(),
89 Unit::simple(UnitCategory::Length, 1000.0),
90 );
91 units.insert(
92 "kilometers".to_string(),
93 Unit::simple(UnitCategory::Length, 1000.0),
94 );
95 units.insert("cm".to_string(), Unit::simple(UnitCategory::Length, 0.01));
96 units.insert(
97 "centimeter".to_string(),
98 Unit::simple(UnitCategory::Length, 0.01),
99 );
100 units.insert(
101 "centimeters".to_string(),
102 Unit::simple(UnitCategory::Length, 0.01),
103 );
104 units.insert("mm".to_string(), Unit::simple(UnitCategory::Length, 0.001));
105 units.insert(
106 "millimeter".to_string(),
107 Unit::simple(UnitCategory::Length, 0.001),
108 );
109 units.insert(
110 "millimeters".to_string(),
111 Unit::simple(UnitCategory::Length, 0.001),
112 );
113 units.insert("nm".to_string(), Unit::simple(UnitCategory::Length, 1e-9));
114 units.insert(
115 "nanometer".to_string(),
116 Unit::simple(UnitCategory::Length, 1e-9),
117 );
118 units.insert("um".to_string(), Unit::simple(UnitCategory::Length, 1e-6));
119 units.insert(
120 "micrometer".to_string(),
121 Unit::simple(UnitCategory::Length, 1e-6),
122 );
123
124 units.insert(
126 "mi".to_string(),
127 Unit::simple(UnitCategory::Length, 1609.344),
128 );
129 units.insert(
130 "mile".to_string(),
131 Unit::simple(UnitCategory::Length, 1609.344),
132 );
133 units.insert(
134 "miles".to_string(),
135 Unit::simple(UnitCategory::Length, 1609.344),
136 );
137 units.insert("yd".to_string(), Unit::simple(UnitCategory::Length, 0.9144));
138 units.insert(
139 "yard".to_string(),
140 Unit::simple(UnitCategory::Length, 0.9144),
141 );
142 units.insert(
143 "yards".to_string(),
144 Unit::simple(UnitCategory::Length, 0.9144),
145 );
146 units.insert("ft".to_string(), Unit::simple(UnitCategory::Length, 0.3048));
147 units.insert(
148 "foot".to_string(),
149 Unit::simple(UnitCategory::Length, 0.3048),
150 );
151 units.insert(
152 "feet".to_string(),
153 Unit::simple(UnitCategory::Length, 0.3048),
154 );
155 units.insert("in".to_string(), Unit::simple(UnitCategory::Length, 0.0254));
156 units.insert(
157 "inch".to_string(),
158 Unit::simple(UnitCategory::Length, 0.0254),
159 );
160 units.insert(
161 "inches".to_string(),
162 Unit::simple(UnitCategory::Length, 0.0254),
163 );
164
165 units.insert(
167 "nmi".to_string(),
168 Unit::simple(UnitCategory::Length, 1852.0),
169 );
170 units.insert(
171 "nautical_mile".to_string(),
172 Unit::simple(UnitCategory::Length, 1852.0),
173 );
174
175 units.insert("kg".to_string(), Unit::simple(UnitCategory::Mass, 1.0));
177 units.insert(
178 "kilogram".to_string(),
179 Unit::simple(UnitCategory::Mass, 1.0),
180 );
181 units.insert(
182 "kilograms".to_string(),
183 Unit::simple(UnitCategory::Mass, 1.0),
184 );
185
186 units.insert("g".to_string(), Unit::simple(UnitCategory::Mass, 0.001));
188 units.insert("gram".to_string(), Unit::simple(UnitCategory::Mass, 0.001));
189 units.insert("grams".to_string(), Unit::simple(UnitCategory::Mass, 0.001));
190 units.insert("mg".to_string(), Unit::simple(UnitCategory::Mass, 0.000001));
191 units.insert(
192 "milligram".to_string(),
193 Unit::simple(UnitCategory::Mass, 0.000001),
194 );
195 units.insert(
196 "milligrams".to_string(),
197 Unit::simple(UnitCategory::Mass, 0.000001),
198 );
199 units.insert("ug".to_string(), Unit::simple(UnitCategory::Mass, 1e-9));
200 units.insert(
201 "microgram".to_string(),
202 Unit::simple(UnitCategory::Mass, 1e-9),
203 );
204 units.insert("t".to_string(), Unit::simple(UnitCategory::Mass, 1000.0));
205 units.insert(
206 "tonne".to_string(),
207 Unit::simple(UnitCategory::Mass, 1000.0),
208 );
209 units.insert(
210 "metric_ton".to_string(),
211 Unit::simple(UnitCategory::Mass, 1000.0),
212 );
213
214 units.insert(
216 "lb".to_string(),
217 Unit::simple(UnitCategory::Mass, 0.45359237),
218 );
219 units.insert(
220 "lbs".to_string(),
221 Unit::simple(UnitCategory::Mass, 0.45359237),
222 );
223 units.insert(
224 "pound".to_string(),
225 Unit::simple(UnitCategory::Mass, 0.45359237),
226 );
227 units.insert(
228 "pounds".to_string(),
229 Unit::simple(UnitCategory::Mass, 0.45359237),
230 );
231 units.insert(
232 "oz".to_string(),
233 Unit::simple(UnitCategory::Mass, 0.028349523125),
234 );
235 units.insert(
236 "ounce".to_string(),
237 Unit::simple(UnitCategory::Mass, 0.028349523125),
238 );
239 units.insert(
240 "ounces".to_string(),
241 Unit::simple(UnitCategory::Mass, 0.028349523125),
242 );
243 units.insert(
244 "ton".to_string(),
245 Unit::simple(UnitCategory::Mass, 907.18474),
246 );
247 units.insert(
248 "short_ton".to_string(),
249 Unit::simple(UnitCategory::Mass, 907.18474),
250 );
251 units.insert(
252 "long_ton".to_string(),
253 Unit::simple(UnitCategory::Mass, 1016.0469088),
254 );
255 units.insert(
256 "stone".to_string(),
257 Unit::simple(UnitCategory::Mass, 6.35029318),
258 );
259
260 units.insert(
262 "k".to_string(),
263 Unit::simple(UnitCategory::Temperature, 1.0),
264 );
265 units.insert(
266 "kelvin".to_string(),
267 Unit::simple(UnitCategory::Temperature, 1.0),
268 );
269
270 units.insert(
272 "c".to_string(),
273 Unit::temperature(1.0, 273.15, 1.0, -273.15),
274 );
275 units.insert(
276 "celsius".to_string(),
277 Unit::temperature(1.0, 273.15, 1.0, -273.15),
278 );
279 units.insert(
280 "centigrade".to_string(),
281 Unit::temperature(1.0, 273.15, 1.0, -273.15),
282 );
283
284 units.insert(
286 "f".to_string(),
287 Unit::temperature(5.0 / 9.0, 459.67 * 5.0 / 9.0, 9.0 / 5.0, -459.67),
288 );
289 units.insert(
290 "fahrenheit".to_string(),
291 Unit::temperature(5.0 / 9.0, 459.67 * 5.0 / 9.0, 9.0 / 5.0, -459.67),
292 );
293
294 units.insert("l".to_string(), Unit::simple(UnitCategory::Volume, 1.0));
296 units.insert("liter".to_string(), Unit::simple(UnitCategory::Volume, 1.0));
297 units.insert(
298 "liters".to_string(),
299 Unit::simple(UnitCategory::Volume, 1.0),
300 );
301 units.insert("litre".to_string(), Unit::simple(UnitCategory::Volume, 1.0));
302 units.insert(
303 "litres".to_string(),
304 Unit::simple(UnitCategory::Volume, 1.0),
305 );
306
307 units.insert("ml".to_string(), Unit::simple(UnitCategory::Volume, 0.001));
309 units.insert(
310 "milliliter".to_string(),
311 Unit::simple(UnitCategory::Volume, 0.001),
312 );
313 units.insert(
314 "milliliters".to_string(),
315 Unit::simple(UnitCategory::Volume, 0.001),
316 );
317 units.insert("m3".to_string(), Unit::simple(UnitCategory::Volume, 1000.0));
318 units.insert(
319 "cubic_meter".to_string(),
320 Unit::simple(UnitCategory::Volume, 1000.0),
321 );
322 units.insert("cm3".to_string(), Unit::simple(UnitCategory::Volume, 0.001));
323 units.insert(
324 "cubic_centimeter".to_string(),
325 Unit::simple(UnitCategory::Volume, 0.001),
326 );
327 units.insert("cc".to_string(), Unit::simple(UnitCategory::Volume, 0.001));
328
329 units.insert(
331 "gal".to_string(),
332 Unit::simple(UnitCategory::Volume, 3.785411784),
333 );
334 units.insert(
335 "gallon".to_string(),
336 Unit::simple(UnitCategory::Volume, 3.785411784),
337 );
338 units.insert(
339 "gallons".to_string(),
340 Unit::simple(UnitCategory::Volume, 3.785411784),
341 );
342 units.insert(
343 "qt".to_string(),
344 Unit::simple(UnitCategory::Volume, 0.946352946),
345 );
346 units.insert(
347 "quart".to_string(),
348 Unit::simple(UnitCategory::Volume, 0.946352946),
349 );
350 units.insert(
351 "quarts".to_string(),
352 Unit::simple(UnitCategory::Volume, 0.946352946),
353 );
354 units.insert(
355 "pt".to_string(),
356 Unit::simple(UnitCategory::Volume, 0.473176473),
357 );
358 units.insert(
359 "pint".to_string(),
360 Unit::simple(UnitCategory::Volume, 0.473176473),
361 );
362 units.insert(
363 "pints".to_string(),
364 Unit::simple(UnitCategory::Volume, 0.473176473),
365 );
366 units.insert(
367 "cup".to_string(),
368 Unit::simple(UnitCategory::Volume, 0.2365882365),
369 );
370 units.insert(
371 "cups".to_string(),
372 Unit::simple(UnitCategory::Volume, 0.2365882365),
373 );
374 units.insert(
375 "fl_oz".to_string(),
376 Unit::simple(UnitCategory::Volume, 0.0295735295625),
377 );
378 units.insert(
379 "fluid_ounce".to_string(),
380 Unit::simple(UnitCategory::Volume, 0.0295735295625),
381 );
382 units.insert(
383 "tbsp".to_string(),
384 Unit::simple(UnitCategory::Volume, 0.01478676478125),
385 );
386 units.insert(
387 "tablespoon".to_string(),
388 Unit::simple(UnitCategory::Volume, 0.01478676478125),
389 );
390 units.insert(
391 "tsp".to_string(),
392 Unit::simple(UnitCategory::Volume, 0.00492892159375),
393 );
394 units.insert(
395 "teaspoon".to_string(),
396 Unit::simple(UnitCategory::Volume, 0.00492892159375),
397 );
398
399 units.insert(
401 "imperial_gal".to_string(),
402 Unit::simple(UnitCategory::Volume, 4.54609),
403 );
404 units.insert(
405 "imperial_gallon".to_string(),
406 Unit::simple(UnitCategory::Volume, 4.54609),
407 );
408 units.insert(
409 "imperial_qt".to_string(),
410 Unit::simple(UnitCategory::Volume, 1.1365225),
411 );
412 units.insert(
413 "imperial_pt".to_string(),
414 Unit::simple(UnitCategory::Volume, 0.56826125),
415 );
416
417 units.insert("s".to_string(), Unit::simple(UnitCategory::Time, 1.0));
419 units.insert("sec".to_string(), Unit::simple(UnitCategory::Time, 1.0));
420 units.insert("second".to_string(), Unit::simple(UnitCategory::Time, 1.0));
421 units.insert("seconds".to_string(), Unit::simple(UnitCategory::Time, 1.0));
422
423 units.insert("ms".to_string(), Unit::simple(UnitCategory::Time, 0.001));
424 units.insert(
425 "millisecond".to_string(),
426 Unit::simple(UnitCategory::Time, 0.001),
427 );
428 units.insert(
429 "milliseconds".to_string(),
430 Unit::simple(UnitCategory::Time, 0.001),
431 );
432 units.insert("us".to_string(), Unit::simple(UnitCategory::Time, 0.000001));
433 units.insert(
434 "microsecond".to_string(),
435 Unit::simple(UnitCategory::Time, 0.000001),
436 );
437 units.insert(
438 "microseconds".to_string(),
439 Unit::simple(UnitCategory::Time, 0.000001),
440 );
441 units.insert(
442 "ns".to_string(),
443 Unit::simple(UnitCategory::Time, 0.000000001),
444 );
445 units.insert(
446 "nanosecond".to_string(),
447 Unit::simple(UnitCategory::Time, 0.000000001),
448 );
449 units.insert(
450 "nanoseconds".to_string(),
451 Unit::simple(UnitCategory::Time, 0.000000001),
452 );
453
454 units.insert("min".to_string(), Unit::simple(UnitCategory::Time, 60.0));
455 units.insert("minute".to_string(), Unit::simple(UnitCategory::Time, 60.0));
456 units.insert(
457 "minutes".to_string(),
458 Unit::simple(UnitCategory::Time, 60.0),
459 );
460 units.insert("hr".to_string(), Unit::simple(UnitCategory::Time, 3600.0));
461 units.insert("hour".to_string(), Unit::simple(UnitCategory::Time, 3600.0));
462 units.insert(
463 "hours".to_string(),
464 Unit::simple(UnitCategory::Time, 3600.0),
465 );
466 units.insert("day".to_string(), Unit::simple(UnitCategory::Time, 86400.0));
467 units.insert(
468 "days".to_string(),
469 Unit::simple(UnitCategory::Time, 86400.0),
470 );
471 units.insert(
472 "week".to_string(),
473 Unit::simple(UnitCategory::Time, 604800.0),
474 );
475 units.insert(
476 "weeks".to_string(),
477 Unit::simple(UnitCategory::Time, 604800.0),
478 );
479 units.insert(
480 "month".to_string(),
481 Unit::simple(UnitCategory::Time, 2629746.0),
482 ); units.insert(
484 "months".to_string(),
485 Unit::simple(UnitCategory::Time, 2629746.0),
486 );
487 units.insert(
488 "year".to_string(),
489 Unit::simple(UnitCategory::Time, 31557600.0),
490 ); units.insert(
492 "years".to_string(),
493 Unit::simple(UnitCategory::Time, 31557600.0),
494 );
495
496 units.insert("m2".to_string(), Unit::simple(UnitCategory::Area, 1.0));
498 units.insert("sqm".to_string(), Unit::simple(UnitCategory::Area, 1.0));
499 units.insert(
500 "square_meter".to_string(),
501 Unit::simple(UnitCategory::Area, 1.0),
502 );
503 units.insert(
504 "square_meters".to_string(),
505 Unit::simple(UnitCategory::Area, 1.0),
506 );
507
508 units.insert(
509 "km2".to_string(),
510 Unit::simple(UnitCategory::Area, 1000000.0),
511 );
512 units.insert(
513 "square_kilometer".to_string(),
514 Unit::simple(UnitCategory::Area, 1000000.0),
515 );
516 units.insert("cm2".to_string(), Unit::simple(UnitCategory::Area, 0.0001));
517 units.insert(
518 "square_centimeter".to_string(),
519 Unit::simple(UnitCategory::Area, 0.0001),
520 );
521
522 units.insert(
523 "sq_ft".to_string(),
524 Unit::simple(UnitCategory::Area, 0.09290304),
525 );
526 units.insert(
527 "square_foot".to_string(),
528 Unit::simple(UnitCategory::Area, 0.09290304),
529 );
530 units.insert(
531 "square_feet".to_string(),
532 Unit::simple(UnitCategory::Area, 0.09290304),
533 );
534 units.insert(
535 "sq_in".to_string(),
536 Unit::simple(UnitCategory::Area, 0.00064516),
537 );
538 units.insert(
539 "square_inch".to_string(),
540 Unit::simple(UnitCategory::Area, 0.00064516),
541 );
542 units.insert(
543 "sq_mi".to_string(),
544 Unit::simple(UnitCategory::Area, 2589988.110336),
545 );
546 units.insert(
547 "square_mile".to_string(),
548 Unit::simple(UnitCategory::Area, 2589988.110336),
549 );
550 units.insert(
551 "acre".to_string(),
552 Unit::simple(UnitCategory::Area, 4046.8564224),
553 );
554 units.insert(
555 "acres".to_string(),
556 Unit::simple(UnitCategory::Area, 4046.8564224),
557 );
558 units.insert(
559 "hectare".to_string(),
560 Unit::simple(UnitCategory::Area, 10000.0),
561 );
562 units.insert(
563 "hectares".to_string(),
564 Unit::simple(UnitCategory::Area, 10000.0),
565 );
566
567 units.insert("mps".to_string(), Unit::simple(UnitCategory::Speed, 1.0));
569 units.insert("m/s".to_string(), Unit::simple(UnitCategory::Speed, 1.0));
570 units.insert(
571 "meters_per_second".to_string(),
572 Unit::simple(UnitCategory::Speed, 1.0),
573 );
574
575 units.insert(
576 "kph".to_string(),
577 Unit::simple(UnitCategory::Speed, 0.277777778),
578 );
579 units.insert(
580 "kmh".to_string(),
581 Unit::simple(UnitCategory::Speed, 0.277777778),
582 );
583 units.insert(
584 "km/h".to_string(),
585 Unit::simple(UnitCategory::Speed, 0.277777778),
586 );
587 units.insert(
588 "kilometers_per_hour".to_string(),
589 Unit::simple(UnitCategory::Speed, 0.277777778),
590 );
591
592 units.insert(
593 "mph".to_string(),
594 Unit::simple(UnitCategory::Speed, 0.44704),
595 );
596 units.insert(
597 "mi/h".to_string(),
598 Unit::simple(UnitCategory::Speed, 0.44704),
599 );
600 units.insert(
601 "miles_per_hour".to_string(),
602 Unit::simple(UnitCategory::Speed, 0.44704),
603 );
604
605 units.insert(
606 "knot".to_string(),
607 Unit::simple(UnitCategory::Speed, 0.514444444),
608 );
609 units.insert(
610 "knots".to_string(),
611 Unit::simple(UnitCategory::Speed, 0.514444444),
612 );
613 units.insert(
614 "kt".to_string(),
615 Unit::simple(UnitCategory::Speed, 0.514444444),
616 );
617
618 units.insert("fps".to_string(), Unit::simple(UnitCategory::Speed, 0.3048));
619 units.insert(
620 "ft/s".to_string(),
621 Unit::simple(UnitCategory::Speed, 0.3048),
622 );
623 units.insert(
624 "feet_per_second".to_string(),
625 Unit::simple(UnitCategory::Speed, 0.3048),
626 );
627
628 units.insert(
631 "light_speed".to_string(),
632 Unit::simple(UnitCategory::Speed, 299792458.0),
633 );
634
635 units.insert("pa".to_string(), Unit::simple(UnitCategory::Pressure, 1.0));
637 units.insert(
638 "pascal".to_string(),
639 Unit::simple(UnitCategory::Pressure, 1.0),
640 );
641 units.insert(
642 "pascals".to_string(),
643 Unit::simple(UnitCategory::Pressure, 1.0),
644 );
645
646 units.insert(
647 "kpa".to_string(),
648 Unit::simple(UnitCategory::Pressure, 1000.0),
649 );
650 units.insert(
651 "kilopascal".to_string(),
652 Unit::simple(UnitCategory::Pressure, 1000.0),
653 );
654 units.insert(
655 "mpa".to_string(),
656 Unit::simple(UnitCategory::Pressure, 1000000.0),
657 );
658 units.insert(
659 "megapascal".to_string(),
660 Unit::simple(UnitCategory::Pressure, 1000000.0),
661 );
662 units.insert(
663 "gpa".to_string(),
664 Unit::simple(UnitCategory::Pressure, 1000000000.0),
665 );
666 units.insert(
667 "gigapascal".to_string(),
668 Unit::simple(UnitCategory::Pressure, 1000000000.0),
669 );
670
671 units.insert(
672 "bar".to_string(),
673 Unit::simple(UnitCategory::Pressure, 100000.0),
674 );
675 units.insert(
676 "bars".to_string(),
677 Unit::simple(UnitCategory::Pressure, 100000.0),
678 );
679 units.insert(
680 "mbar".to_string(),
681 Unit::simple(UnitCategory::Pressure, 100.0),
682 );
683 units.insert(
684 "millibar".to_string(),
685 Unit::simple(UnitCategory::Pressure, 100.0),
686 );
687
688 units.insert(
689 "atm".to_string(),
690 Unit::simple(UnitCategory::Pressure, 101325.0),
691 );
692 units.insert(
693 "atmosphere".to_string(),
694 Unit::simple(UnitCategory::Pressure, 101325.0),
695 );
696 units.insert(
697 "atmospheres".to_string(),
698 Unit::simple(UnitCategory::Pressure, 101325.0),
699 );
700
701 units.insert(
702 "psi".to_string(),
703 Unit::simple(UnitCategory::Pressure, 6894.757293168),
704 );
705 units.insert(
706 "pounds_per_square_inch".to_string(),
707 Unit::simple(UnitCategory::Pressure, 6894.757293168),
708 );
709
710 units.insert(
711 "torr".to_string(),
712 Unit::simple(UnitCategory::Pressure, 133.322368421),
713 );
714 units.insert(
715 "mmhg".to_string(),
716 Unit::simple(UnitCategory::Pressure, 133.322368421),
717 );
718 units.insert(
719 "mm_hg".to_string(),
720 Unit::simple(UnitCategory::Pressure, 133.322368421),
721 );
722
723 UnitConverter { units }
724 }
725
726 pub fn convert(&self, value: f64, from_unit: &str, to_unit: &str) -> Result<f64> {
728 let from_key = from_unit.to_lowercase();
730 let to_key = to_unit.to_lowercase();
731
732 let from = self
734 .units
735 .get(&from_key)
736 .ok_or_else(|| anyhow!("Unknown unit: {}", from_unit))?;
737 let to = self
738 .units
739 .get(&to_key)
740 .ok_or_else(|| anyhow!("Unknown unit: {}", to_unit))?;
741
742 if from.category != to.category {
744 return Err(anyhow!(
745 "Cannot convert between different unit types: {} ({:?}) to {} ({:?})",
746 from_unit,
747 from.category,
748 to_unit,
749 to.category
750 ));
751 }
752
753 let si_value = value * from.to_si_factor + from.to_si_offset;
755
756 let result = si_value * to.from_si_factor + to.from_si_offset;
758
759 Ok(result)
760 }
761
762 #[must_use]
764 pub fn get_units_for_category(&self, category: &str) -> Vec<String> {
765 let cat = match category.to_lowercase().as_str() {
766 "length" => Some(UnitCategory::Length),
767 "mass" | "weight" => Some(UnitCategory::Mass),
768 "temperature" | "temp" => Some(UnitCategory::Temperature),
769 "volume" => Some(UnitCategory::Volume),
770 "time" => Some(UnitCategory::Time),
771 "area" => Some(UnitCategory::Area),
772 "speed" | "velocity" => Some(UnitCategory::Speed),
773 "pressure" => Some(UnitCategory::Pressure),
774 _ => None,
775 };
776
777 if let Some(category) = cat {
778 self.units
779 .iter()
780 .filter(|(_, unit)| unit.category == category)
781 .map(|(name, _)| name.clone())
782 .collect()
783 } else {
784 Vec::new()
785 }
786 }
787}
788
789lazy_static::lazy_static! {
791 static ref CONVERTER: UnitConverter = UnitConverter::new();
792}
793
794pub fn convert_units(value: f64, from_unit: &str, to_unit: &str) -> Result<f64> {
796 CONVERTER.convert(value, from_unit, to_unit)
797}
798
799#[must_use]
801pub fn list_units(category: &str) -> Vec<String> {
802 CONVERTER.get_units_for_category(category)
803}
804
805#[cfg(test)]
806mod tests {
807 use super::*;
808
809 #[test]
810 fn test_length_conversions() {
811 let converter = UnitConverter::new();
812
813 let result = converter.convert(100.0, "km", "miles").unwrap();
815 assert!((result - 62.137).abs() < 0.01);
816
817 let result = converter.convert(100.0, "ft", "m").unwrap();
819 assert!((result - 30.48).abs() < 0.01);
820
821 let result = converter.convert(12.0, "in", "cm").unwrap();
823 assert!((result - 30.48).abs() < 0.01);
824 }
825
826 #[test]
827 fn test_mass_conversions() {
828 let converter = UnitConverter::new();
829
830 let result = converter.convert(100.0, "lb", "kg").unwrap();
832 assert!((result - 45.359).abs() < 0.01);
833
834 let result = converter.convert(16.0, "oz", "g").unwrap();
836 assert!((result - 453.592).abs() < 0.01);
837 }
838
839 #[test]
840 fn test_temperature_conversions() {
841 let converter = UnitConverter::new();
842
843 let result = converter.convert(32.0, "F", "C").unwrap();
845 assert!((result - 0.0).abs() < 0.01);
846
847 let result = converter.convert(212.0, "F", "C").unwrap();
848 assert!((result - 100.0).abs() < 0.01);
849
850 let result = converter.convert(0.0, "C", "K").unwrap();
852 assert!((result - 273.15).abs() < 0.01);
853 }
854
855 #[test]
856 fn test_volume_conversions() {
857 let converter = UnitConverter::new();
858
859 let result = converter.convert(1.0, "gal", "L").unwrap();
861 assert!((result - 3.785).abs() < 0.01);
862
863 let result = converter.convert(1.0, "cup", "ml").unwrap();
865 assert!((result - 236.588).abs() < 0.01);
866 }
867
868 #[test]
869 fn test_invalid_conversion() {
870 let converter = UnitConverter::new();
871
872 let result = converter.convert(100.0, "km", "kg");
874 assert!(result.is_err());
875
876 let result = converter.convert(100.0, "xyz", "m");
878 assert!(result.is_err());
879 }
880
881 #[test]
882 fn test_case_insensitive() {
883 let converter = UnitConverter::new();
884
885 let result1 = converter.convert(100.0, "KM", "MILES").unwrap();
887 let result2 = converter.convert(100.0, "km", "miles").unwrap();
888 let result3 = converter.convert(100.0, "Km", "Miles").unwrap();
889
890 assert!((result1 - result2).abs() < 0.001);
891 assert!((result2 - result3).abs() < 0.001);
892 }
893}