1use std::fmt::{self, Debug, Display};
18use std::str::FromStr;
19
20use once_cell::sync::Lazy;
21use regex::Regex;
22use schemars::JsonSchema;
23use serde::de::{Deserialize, Deserializer, Visitor};
24use serde::ser::{Serialize, Serializer};
25
26pub trait Prefix: Clone + Copy + Default + PartialEq + FromStr + Display + Debug {
29 fn relative_magnitude(&self) -> u128;
31
32 fn conversion_factor(&self, to: Self) -> Result<u128, String> {
34 let from_mag = self.relative_magnitude();
35 let to_mag = to.relative_magnitude();
36 if from_mag % to_mag != 0 {
37 return Err("Conversion would lose precision".to_string());
38 }
39 Ok(from_mag / to_mag)
40 }
41
42 fn conversion_factor_lossy(&self, to: Self) -> f64 {
44 let from_mag = self.relative_magnitude();
45 let to_mag = to.relative_magnitude();
46 from_mag as f64 / to_mag as f64
47 }
48}
49
50#[derive(Debug, Copy, Clone, PartialEq, Eq)]
52pub enum SiPrefix {
53 Nano,
54 Micro,
55 Milli,
56 Base,
57 Kilo,
58 Kibi,
59 Mega,
60 Mebi,
61 Giga,
62 Gibi,
63 Tera,
64 Tebi,
65}
66
67impl Default for SiPrefix {
68 fn default() -> Self {
69 Self::Base
70 }
71}
72
73impl FromStr for SiPrefix {
74 type Err = String;
75
76 fn from_str(s: &str) -> Result<Self, Self::Err> {
77 match s {
78 "n" | "nano" => Ok(Self::Nano),
79 "u" | "μ" | "micro" => Ok(Self::Micro),
80 "m" | "milli" => Ok(Self::Milli),
81 "K" | "kilo" => Ok(Self::Kilo),
82 "Ki" | "kibi" => Ok(Self::Kibi),
83 "M" | "mega" => Ok(Self::Mega),
84 "Mi" | "mebi" => Ok(Self::Mebi),
85 "G" | "giga" => Ok(Self::Giga),
86 "Gi" | "gibi" => Ok(Self::Gibi),
87 "T" | "tera" => Ok(Self::Tera),
88 "Ti" | "tebi" => Ok(Self::Tebi),
89 _ => Err(
90 "Unit prefix was not one of (n|nano|u|μ|micro|m|milli|K|kilo\
91 |Ki|kibi|M|mega|Mi|mebi|G|giga|Gi|gibi|T|tera|Ti|tebi)"
92 .to_string(),
93 ),
94 }
95 }
96}
97
98impl fmt::Display for SiPrefix {
99 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
100 match self {
101 Self::Nano => write!(f, "n"),
102 Self::Micro => write!(f, "μ"),
103 Self::Milli => write!(f, "m"),
104 Self::Kilo => write!(f, "K"),
105 Self::Kibi => write!(f, "Ki"),
106 Self::Mega => write!(f, "M"),
107 Self::Mebi => write!(f, "Mi"),
108 Self::Giga => write!(f, "G"),
109 Self::Gibi => write!(f, "Gi"),
110 Self::Tera => write!(f, "T"),
111 Self::Tebi => write!(f, "Ti"),
112 Self::Base => Ok(()),
113 }
114 }
115}
116
117impl Prefix for SiPrefix {
118 fn relative_magnitude(&self) -> u128 {
119 const TEN: u128 = 10;
120 const TWO: u128 = 2;
121 const BASE: u128 = TEN.pow(9);
122 match self {
123 Self::Nano => BASE / TEN.pow(9),
124 Self::Micro => BASE / TEN.pow(6),
125 Self::Milli => BASE / TEN.pow(3),
126 Self::Base => BASE,
127 Self::Kilo => BASE * TEN.pow(3),
128 Self::Kibi => BASE * TWO.pow(10),
129 Self::Mega => BASE * TEN.pow(6),
130 Self::Mebi => BASE * TWO.pow(20),
131 Self::Giga => BASE * TEN.pow(9),
132 Self::Gibi => BASE * TWO.pow(30),
133 Self::Tera => BASE * TEN.pow(12),
134 Self::Tebi => BASE * TWO.pow(40),
135 }
136 }
137}
138
139#[derive(Debug, Copy, Clone, PartialEq, Eq)]
142pub enum SiPrefixUpper {
143 Base,
144 Kilo,
145 Kibi,
146 Mega,
147 Mebi,
148 Giga,
149 Gibi,
150 Tera,
151 Tebi,
152}
153
154impl Default for SiPrefixUpper {
155 fn default() -> Self {
156 Self::Base
157 }
158}
159
160impl FromStr for SiPrefixUpper {
161 type Err = String;
162
163 fn from_str(s: &str) -> Result<Self, Self::Err> {
164 match s {
165 "K" | "kilo" => Ok(Self::Kilo),
166 "Ki" | "kibi" => Ok(Self::Kibi),
167 "M" | "mega" => Ok(Self::Mega),
168 "Mi" | "mebi" => Ok(Self::Mebi),
169 "G" | "giga" => Ok(Self::Giga),
170 "Gi" | "gibi" => Ok(Self::Gibi),
171 "T" | "tera" => Ok(Self::Tera),
172 "Ti" | "tebi" => Ok(Self::Tebi),
173 _ => Err("Unit prefix was not one of (K|kilo|Ki|kibi|M|mega|Mi|mebi\
174 |G|giga|Gi|gibi|T|tera|Ti|tebi)"
175 .to_string()),
176 }
177 }
178}
179
180impl fmt::Display for SiPrefixUpper {
181 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
182 match self {
183 Self::Kilo => write!(f, "K"),
184 Self::Kibi => write!(f, "Ki"),
185 Self::Mega => write!(f, "M"),
186 Self::Mebi => write!(f, "Mi"),
187 Self::Giga => write!(f, "G"),
188 Self::Gibi => write!(f, "Gi"),
189 Self::Tera => write!(f, "T"),
190 Self::Tebi => write!(f, "Ti"),
191 Self::Base => Ok(()),
192 }
193 }
194}
195
196impl Prefix for SiPrefixUpper {
197 fn relative_magnitude(&self) -> u128 {
198 const TEN: u128 = 10;
199 const TWO: u128 = 2;
200 match self {
201 Self::Base => 1,
202 Self::Kilo => TEN.pow(3),
203 Self::Kibi => TWO.pow(10),
204 Self::Mega => TEN.pow(6),
205 Self::Mebi => TWO.pow(20),
206 Self::Giga => TEN.pow(9),
207 Self::Gibi => TWO.pow(30),
208 Self::Tera => TEN.pow(12),
209 Self::Tebi => TWO.pow(40),
210 }
211 }
212}
213
214#[derive(Debug, Copy, Clone, PartialEq, Eq)]
218pub enum TimePrefix {
219 Nano,
220 Micro,
221 Milli,
222 Sec,
223 Min,
224 Hour,
225}
226
227impl Default for TimePrefix {
228 fn default() -> Self {
229 Self::Sec
230 }
231}
232
233impl FromStr for TimePrefix {
234 type Err = String;
235
236 fn from_str(s: &str) -> Result<Self, Self::Err> {
237 match s {
238 "ns" | "nanosecond" | "nanoseconds" => Ok(Self::Nano),
239 "us" | "μs" | "microsecond" | "microseconds" => Ok(Self::Micro),
240 "ms" | "millisecond" | "milliseconds" => Ok(Self::Milli),
241 "s" | "sec" | "secs" | "second" | "seconds" => Ok(Self::Sec),
242 "m" | "min" | "mins" | "minute" | "minutes" => Ok(Self::Min),
243 "h" | "hr" | "hrs" | "hour" | "hours" => Ok(Self::Hour),
244 _ => Err(
245 "Unit was not one of (ns|nanosecond|nanoseconds|us|μs|microsecond|microseconds\
246 |ms|millisecond|milliseconds|s|sec|secs|second|seconds|m|min|mins|minute|minutes\
247 |h|hr|hrs|hour|hours)"
248 .to_string(),
249 ),
250 }
251 }
252}
253
254impl fmt::Display for TimePrefix {
255 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
256 match self {
257 Self::Nano => write!(f, "ns"),
258 Self::Micro => write!(f, "μs"),
259 Self::Milli => write!(f, "ms"),
260 Self::Sec => write!(f, "sec"),
261 Self::Min => write!(f, "min"),
262 Self::Hour => write!(f, "hour"),
263 }
264 }
265}
266
267impl Prefix for TimePrefix {
268 fn relative_magnitude(&self) -> u128 {
269 const TEN: u128 = 10;
270 const BASE: u128 = TEN.pow(9);
271 match self {
272 Self::Nano => BASE / TEN.pow(9),
273 Self::Micro => BASE / TEN.pow(6),
274 Self::Milli => BASE / TEN.pow(3),
275 Self::Sec => BASE,
276 Self::Min => BASE * 60,
277 Self::Hour => BASE * 60 * 60,
278 }
279 }
280}
281
282#[derive(Debug, Copy, Clone, PartialEq, Eq)]
286pub enum TimePrefixUpper {
287 Sec,
288 Min,
289 Hour,
290}
291
292impl Default for TimePrefixUpper {
293 fn default() -> Self {
294 Self::Sec
295 }
296}
297
298impl FromStr for TimePrefixUpper {
299 type Err = String;
300
301 fn from_str(s: &str) -> Result<Self, Self::Err> {
302 match s {
303 "s" | "sec" | "secs" | "second" | "seconds" => Ok(Self::Sec),
304 "m" | "min" | "mins" | "minute" | "minutes" => Ok(Self::Min),
305 "h" | "hr" | "hrs" | "hour" | "hours" => Ok(Self::Hour),
306 _ => Err("Unit prefix was not one of (s|sec|secs|second|seconds\
307 |m|min|mins|minute|minutes|h|hr|hrs|hour|hours)"
308 .to_string()),
309 }
310 }
311}
312
313impl fmt::Display for TimePrefixUpper {
314 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
315 match self {
316 Self::Sec => write!(f, "sec"),
317 Self::Min => write!(f, "min"),
318 Self::Hour => write!(f, "hour"),
319 }
320 }
321}
322
323impl Prefix for TimePrefixUpper {
324 fn relative_magnitude(&self) -> u128 {
325 match self {
326 Self::Sec => 1,
327 Self::Min => 60,
328 Self::Hour => 60 * 60,
329 }
330 }
331}
332
333macro_rules! visit_fn {
334 ($fn_name:ident, $type:ty) => {
335 fn $fn_name<E>(self, x: $type) -> Result<Self::Value, E>
336 where
337 E: serde::de::Error,
338 {
339 Ok(Self::Value::new(
340 x.try_into().map_err(serde::de::Error::custom)?,
341 T::default(),
342 ))
343 }
344 };
345}
346
347macro_rules! unit_impl {
348 ($name:ident, $type:ident, $suffixes:tt) => {
349 impl<T: Prefix> $name<T> {
350 pub fn new(value: $type, prefix: T) -> Self {
351 Self { value, prefix }
352 }
353 }
354
355 impl<T: Prefix> Default for $name<T> {
356 fn default() -> Self {
357 Self::new(0, T::default())
358 }
359 }
360
361 impl<T: Prefix> Unit for $name<T> {
362 type U = $type;
363 type T = T;
364
365 fn value(&self) -> Self::U {
366 self.value
367 }
368
369 fn prefix(&self) -> Self::T {
370 self.prefix
371 }
372
373 fn suffixes() -> &'static [&'static str] {
374 &$suffixes
375 }
376
377 fn convert(&self, prefix: Self::T) -> Result<Self, String> {
378 let factor = self.prefix.conversion_factor(prefix)?;
379 let factor = factor.try_into().unwrap();
380 Ok(Self::new(
381 Self::U::checked_mul(self.value, factor).ok_or(format!(
382 "The resulting value is outside of the bounds [{}, {}]",
383 Self::U::MIN,
384 Self::U::MAX,
385 ))?,
386 prefix,
387 ))
388 }
389
390 fn convert_lossy(&self, prefix: Self::T) -> Self {
391 let factor = self.prefix.conversion_factor_lossy(prefix);
392 Self::new(
393 (self.value as f64 * factor).round() as Self::U,
394 prefix,
395 )
396 }
397 }
398
399 impl<T: Prefix> Display for $name<T> {
400 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
401 write!(f, "{} {}{}", self.value(), self.prefix(), Self::suffixes()[0])
402 }
403 }
404
405 impl<T: Prefix> FromStr for $name<T>
406 where
407 <T as FromStr>::Err: std::fmt::Debug + std::fmt::Display,
408 {
409 type Err = Box<dyn std::error::Error + Send + Sync>;
410
411 fn from_str(s: &str) -> Result<Self, Self::Err> {
412 static RE: Lazy<Regex> = Lazy::new(|| Regex::new(r"^([+-]?[0-9\.]*)\s*(.*)$").unwrap());
413
414 let captures = RE.captures(s).ok_or("Unable to identify value and unit")?;
415 let (value, unit) = (
416 captures.get(1).unwrap().as_str().trim(),
417 captures.get(2).unwrap().as_str().trim(),
418 );
419
420 let prefix = $name::<T>::suffixes()
422 .iter()
423 .map(|suffix| unit.strip_suffix(suffix))
424 .find(|x| x.is_some())
425 .flatten()
426 .or(Some(unit))
427 .unwrap();
428
429 let prefix = match prefix {
430 "" => T::default(),
431 _ => T::from_str(prefix).map_err(|x| x.to_string())?,
432 };
433
434 Ok($name::new(
435 value.parse()?,
436 prefix,
437 ))
438 }
439 }
440
441 impl<'de, T: Prefix> Deserialize<'de> for $name<T>
442 where
443 <T as FromStr>::Err: std::fmt::Debug + std::fmt::Display,
444 {
445 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
446 where
447 D: Deserializer<'de>,
448 {
449 struct ValueVisitor<T> {
450 marker: std::marker::PhantomData<T>,
451 }
452
453 impl<'de, T: Prefix> Visitor<'de> for ValueVisitor<T>
454 where
455 <T as FromStr>::Err: std::fmt::Debug + std::fmt::Display,
456 {
457 type Value = $name<T>;
458
459 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
460 formatter.write_str(stringify!(struct $name<T>))
461 }
462
463 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
464 where
465 E: serde::de::Error,
466 {
467 Self::Value::from_str(s).map_err(serde::de::Error::custom)
468 }
469
470 visit_fn!(visit_u64, u64);
471 visit_fn!(visit_u32, u32);
472 visit_fn!(visit_u8, u8);
473 visit_fn!(visit_i64, i64);
474 visit_fn!(visit_i32, i32);
475 visit_fn!(visit_i8, i8);
476 }
477
478 deserializer.deserialize_any(ValueVisitor {
479 marker: std::marker::PhantomData,
480 })
481 }
482 }
483
484 impl<T: Prefix> Serialize for $name<T> {
485 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
486 where
487 S: Serializer,
488 {
489 serializer.serialize_str(&self.to_string())
490 }
491 }
492
493 impl<T: Prefix> JsonSchema for $name<T> {
494 fn is_referenceable() -> bool { false }
495
496 fn schema_name() -> String {
497 stringify!($name).to_owned()
498 }
499
500 fn json_schema(_: &mut schemars::r#gen::SchemaGenerator) -> schemars::schema::Schema {
501 schemars::schema::SchemaObject {
502 instance_type: Some(schemars::schema::InstanceType::String.into()),
503 format: Some(stringify!($name).to_owned()),
504 ..Default::default()
505 }
506 .into()
507 }
508 }
509 };
510}
511
512pub trait Unit: Sized {
515 type U;
516 type T: Prefix;
517
518 fn value(&self) -> Self::U;
520
521 fn prefix(&self) -> Self::T;
523
524 fn suffixes() -> &'static [&'static str];
525
526 fn convert(&self, prefix: Self::T) -> Result<Self, String>
529 where
530 Self: Sized;
531
532 fn convert_lossy(&self, prefix: Self::T) -> Self
534 where
535 Self: Sized;
536}
537
538#[derive(Debug, Clone, Copy, PartialEq, Eq)]
541pub struct Time<T: Prefix> {
542 value: u64,
543 prefix: T,
544}
545
546unit_impl!(Time, u64, [""]);
549
550impl From<Time<TimePrefix>> for std::time::Duration {
551 fn from(time: Time<TimePrefix>) -> Self {
552 std::time::Duration::from_nanos(time.convert(TimePrefix::Nano).unwrap().value())
553 }
554}
555
556impl From<Time<TimePrefixUpper>> for std::time::Duration {
557 fn from(time: Time<TimePrefixUpper>) -> Self {
558 std::time::Duration::from_secs(time.convert(TimePrefixUpper::Sec).unwrap().value())
559 }
560}
561
562#[derive(Debug, Clone, Copy, PartialEq, Eq)]
564pub struct Bytes<T: Prefix> {
565 pub value: u64,
566 pub prefix: T,
567}
568
569unit_impl!(Bytes, u64, ["B", "byte", "bytes"]);
570
571#[derive(Debug, Clone, Copy, PartialEq, Eq)]
573pub struct BitsPerSec<T: Prefix> {
574 pub value: u64,
575 pub prefix: T,
576}
577
578unit_impl!(BitsPerSec, u64, ["bit", "bits"]);
579
580#[cfg(test)]
581mod tests {
582 use super::*;
583
584 #[test]
585 fn test_parse_string() {
586 assert_eq!(
587 Time::from_str("10").unwrap(),
588 Time::new(10, TimePrefix::Sec)
589 );
590 assert_eq!(
591 Time::from_str("10 s").unwrap(),
592 Time::new(10, TimePrefix::Sec)
593 );
594 assert_eq!(
595 Time::from_str("10s").unwrap(),
596 Time::new(10, TimePrefix::Sec)
597 );
598 assert_eq!(
599 Time::from_str("10 s").unwrap(),
600 Time::new(10, TimePrefix::Sec)
601 );
602 assert_eq!(
603 Time::from_str("10sec").unwrap(),
604 Time::new(10, TimePrefix::Sec)
605 );
606 assert_eq!(
607 Time::from_str("10 m").unwrap(),
608 Time::new(10, TimePrefix::Min)
609 );
610 assert_eq!(
611 Time::from_str("10 min").unwrap(),
612 Time::new(10, TimePrefix::Min)
613 );
614 assert_eq!(
615 Time::from_str("10 ms").unwrap(),
616 Time::new(10, TimePrefix::Milli)
617 );
618 assert_eq!(
619 Time::from_str("10 μs").unwrap(),
620 Time::new(10, TimePrefix::Micro)
621 );
622 assert_eq!(
623 Time::from_str("10 millisecond").unwrap(),
624 Time::new(10, TimePrefix::Milli)
625 );
626 assert_eq!(
627 Time::from_str("10 milliseconds").unwrap(),
628 Time::new(10, TimePrefix::Milli)
629 );
630
631 assert!(Time::<TimePrefix>::from_str("-10 ms").is_err());
632 assert!(Time::<TimePrefix>::from_str("abc 10 ms").is_err());
633 assert!(Time::<TimePrefix>::from_str("10.5 ms").is_err());
634 assert!(Time::<TimePrefix>::from_str("10 abc").is_err());
635 assert!(Time::<TimePrefixUpper>::from_str("10 ms").is_err());
636
637 assert_eq!(
638 Bytes::from_str("10").unwrap(),
639 Bytes::new(10, SiPrefixUpper::Base)
640 );
641 assert_eq!(
642 Bytes::from_str("10 B").unwrap(),
643 Bytes::new(10, SiPrefixUpper::Base)
644 );
645 assert_eq!(
646 Bytes::from_str("10B").unwrap(),
647 Bytes::new(10, SiPrefixUpper::Base)
648 );
649 assert_eq!(
650 Bytes::from_str("10 B").unwrap(),
651 Bytes::new(10, SiPrefixUpper::Base)
652 );
653 assert_eq!(
654 Bytes::from_str("10 KB").unwrap(),
655 Bytes::new(10, SiPrefixUpper::Kilo)
656 );
657 assert_eq!(
658 Bytes::from_str("10 KiB").unwrap(),
659 Bytes::new(10, SiPrefixUpper::Kibi)
660 );
661 assert_eq!(
662 Bytes::from_str("10 MB").unwrap(),
663 Bytes::new(10, SiPrefixUpper::Mega)
664 );
665 assert_eq!(
666 Bytes::from_str("10 megabyte").unwrap(),
667 Bytes::new(10, SiPrefixUpper::Mega)
668 );
669 assert_eq!(
670 Bytes::from_str("10 megabytes").unwrap(),
671 Bytes::new(10, SiPrefixUpper::Mega)
672 );
673
674 assert!(Bytes::<SiPrefixUpper>::from_str("-10 KB").is_err());
675 assert!(Bytes::<SiPrefixUpper>::from_str("abc 10 KB").is_err());
676 assert!(Bytes::<SiPrefixUpper>::from_str("10.5 KB").is_err());
677 assert!(Bytes::<SiPrefixUpper>::from_str("10 abc").is_err());
678 assert!(Bytes::<SiPrefixUpper>::from_str("10 mB").is_err());
679 assert!(Bytes::<SiPrefixUpper>::from_str("10 Megabyte").is_err());
680
681 assert_eq!(
682 BitsPerSec::from_str("10").unwrap(),
683 BitsPerSec::new(10, SiPrefixUpper::Base)
684 );
685 assert_eq!(
686 BitsPerSec::from_str("10 bit").unwrap(),
687 BitsPerSec::new(10, SiPrefixUpper::Base)
688 );
689 assert_eq!(
690 BitsPerSec::from_str("10bit").unwrap(),
691 BitsPerSec::new(10, SiPrefixUpper::Base)
692 );
693 assert_eq!(
694 BitsPerSec::from_str("10 bit").unwrap(),
695 BitsPerSec::new(10, SiPrefixUpper::Base)
696 );
697 assert_eq!(
698 BitsPerSec::from_str("10 Kbit").unwrap(),
699 BitsPerSec::new(10, SiPrefixUpper::Kilo)
700 );
701 assert_eq!(
702 BitsPerSec::from_str("10 Kibit").unwrap(),
703 BitsPerSec::new(10, SiPrefixUpper::Kibi)
704 );
705 assert_eq!(
706 BitsPerSec::from_str("10 Mbit").unwrap(),
707 BitsPerSec::new(10, SiPrefixUpper::Mega)
708 );
709 assert_eq!(
710 BitsPerSec::from_str("10 megabit").unwrap(),
711 BitsPerSec::new(10, SiPrefixUpper::Mega)
712 );
713 assert_eq!(
714 BitsPerSec::from_str("10 megabits").unwrap(),
715 BitsPerSec::new(10, SiPrefixUpper::Mega)
716 );
717
718 assert!(BitsPerSec::<SiPrefixUpper>::from_str("-10 Kbit").is_err());
719 assert!(BitsPerSec::<SiPrefixUpper>::from_str("abc 10 Kbit").is_err());
720 assert!(BitsPerSec::<SiPrefixUpper>::from_str("10.5 Kbit").is_err());
721 assert!(BitsPerSec::<SiPrefixUpper>::from_str("10 abc").is_err());
722 assert!(BitsPerSec::<SiPrefixUpper>::from_str("10 mbit").is_err());
723 }
724
725 #[test]
726 fn test_conversion() {
727 let time = Time::from_str("70 min").unwrap();
728
729 assert_eq!(
730 time.convert(TimePrefix::Sec).unwrap(),
731 Time::from_str("4200 sec").unwrap()
732 );
733 assert!(time.convert(TimePrefix::Hour).is_err());
734
735 assert_eq!(
736 time.convert_lossy(TimePrefix::Sec),
737 Time::from_str("4200 sec").unwrap()
738 );
739 assert_eq!(
740 time.convert_lossy(TimePrefix::Hour),
741 Time::from_str("1 hour").unwrap()
742 );
743
744 let bw = BitsPerSec::from_str("1024 Kbit").unwrap();
745
746 assert_eq!(
747 bw.convert(SiPrefixUpper::Base).unwrap(),
748 BitsPerSec::from_str("1024000 bit").unwrap()
749 );
750 assert!(bw.convert(SiPrefixUpper::Kibi).is_err());
751
752 assert_eq!(
753 bw.convert_lossy(SiPrefixUpper::Base),
754 BitsPerSec::from_str("1024000 bit").unwrap()
755 );
756 assert_eq!(
757 bw.convert_lossy(SiPrefixUpper::Kibi),
758 BitsPerSec::from_str("1000 Kibit").unwrap()
759 );
760 }
761
762 #[test]
763 fn test_time_conversion() {
764 let time = Time::<TimePrefixUpper>::from_str("70 min").unwrap();
765 assert_eq!(
766 std::time::Duration::from_secs(70 * 60),
767 std::time::Duration::from(time)
768 );
769
770 let time = Time::new(1_000_000_123, TimePrefix::Nano);
771 assert_eq!(
772 std::time::Duration::new(1, 123),
773 std::time::Duration::from(time)
774 );
775 }
776}