jam_tooling/
format.rs

1use crate::parsing::{DURATION_TABLE, IEC_TABLE};
2use chrono::prelude::*;
3use jam_types::{Slot, JAM_COMMON_ERA};
4use std::time::Duration;
5
6pub fn slot_time(slot: Slot) -> String {
7	chrono::Local
8		.timestamp_opt(JAM_COMMON_ERA as i64 + slot as i64 * 6, 0)
9		.unwrap()
10		.format("%H:%M:%S")
11		.to_string()
12}
13
14pub fn slot(slot: Slot) -> String {
15	slot.to_string()
16		.as_bytes()
17		.rchunks(3)
18		.rev()
19		.map(std::str::from_utf8)
20		.collect::<Result<Vec<&str>, _>>()
21		.expect("converted from number; qed")
22		.join(",")
23}
24
25pub fn percent<T: Into<u128>>(n: T, d: T) -> String {
26	let ppm = (n.into() as f64 / d.into() as f64 * 100_000f64).ceil() / 1_000f64;
27	if ppm >= 1000.0 {
28		ppm.round()
29	} else if ppm >= 100.0 {
30		(ppm * 10f64).round() / 10f64
31	} else if ppm >= 10.0 {
32		(ppm * 100f64).round() / 100f64
33	} else {
34		ppm
35	}
36	.to_string()
37}
38
39pub fn bytes<T: Into<u128>>(n: T) -> String {
40	units(n, "B", 1024.0)
41}
42
43pub fn amount<T: Into<u128>>(n: T) -> String {
44	units(n, "", 1000.0)
45}
46
47pub fn gas<T: Into<u128>>(n: T) -> String {
48	units(n, "", 1000.0)
49}
50
51/// Returns an approximate representation of quantity `n` measured in units `ones` using prefix
52/// notation.
53///
54/// `divisor` is the prefix value (e.g. 1000 for SI units, 1024 for IEC units).
55///
56/// ```rust
57/// use jam_tooling::format::units;
58/// assert_eq!("10K", units(10_000_u32, "B", 1000.0));
59/// assert_eq!("2560B", units(2_u32 * 1024 + 512, "B", 1024.0));
60/// ```
61pub fn units<T: Into<u128>>(n: T, ones: &str, divisor: f64) -> String {
62	const UNITS: [&str; 7] = ["", "K", "M", "G", "T", "P", "E"];
63	let mut i = 0;
64	let mut n = n.into() as f64;
65	while n >= 999.5 && i > 0 || n >= 9999.5 {
66		n /= divisor;
67		i += 1;
68	}
69	let q = if n >= 100.0 {
70		1.0
71	} else if n >= 10.0 {
72		10.0
73	} else {
74		100.0
75	};
76	format!("{}{}", (n * q + 1.0e-14).round() / q, if i == 0 { ones } else { UNITS[i] })
77}
78
79/// A counterpart of [parsing::exact_bytes](crate::parsing::exact_bytes) that produces the
80/// string from which the exact same size can be parsed.
81pub fn exact_bytes(size: u64) -> String {
82	let (number, prefix) = get_iec_prefix(size);
83	format!("{number} {prefix}B")
84}
85
86fn get_iec_prefix(size: u64) -> (u64, &'static str) {
87	if size != 0 {
88		for (prefix, scale) in IEC_TABLE.iter() {
89			if size.is_multiple_of(*scale) {
90				return (size / scale, prefix);
91			}
92		}
93	}
94	(size, "")
95}
96
97/// A counterpart of [parsing::exact_duration](crate::parsing::duration) that produces the
98/// string from which the exact same duration can be parsed.
99pub fn exact_duration(d: Duration) -> String {
100	let (number, unit) = get_duration_unit(d);
101	format!("{number}{unit}")
102}
103
104fn get_duration_unit(duration: Duration) -> (u64, &'static str) {
105	let secs = duration.as_secs();
106	if secs != 0 {
107		for (unit, scale) in DURATION_TABLE.iter() {
108			if secs.is_multiple_of(*scale) {
109				return (secs / scale, unit);
110			}
111		}
112	}
113	(secs, "s")
114}
115
116#[cfg(test)]
117mod tests {
118	use super::*;
119
120	#[test]
121	fn amount_works() {
122		assert_eq!(amount(0u32), "0");
123		assert_eq!(amount(999u32), "999");
124		assert_eq!(amount(9_999u32), "9999");
125		assert_eq!(amount(10_000u32), "10K");
126		assert_eq!(amount(10_049u32), "10K");
127		assert_eq!(amount(10_050u32), "10.1K");
128		assert_eq!(amount(100_499u32), "100K");
129		assert_eq!(amount(100_500u32), "101K");
130		assert_eq!(amount(1_004_999u32), "1M");
131		assert_eq!(amount(1_005_000u32), "1.01M");
132		assert_eq!(amount(10_049_999u32), "10M");
133		assert_eq!(amount(10_050_000u32), "10.1M");
134	}
135
136	#[test]
137	fn exact_bytes_works() {
138		assert_eq!("0 B", exact_bytes(0));
139		assert_eq!("1 B", exact_bytes(1));
140		assert_eq!("1 KiB", exact_bytes(1024));
141		assert_eq!("1 MiB", exact_bytes(1024 * 1024));
142	}
143
144	#[test]
145	fn exact_duration_works() {
146		assert_eq!("0s", exact_duration(Duration::ZERO));
147		assert_eq!("1s", exact_duration(Duration::from_secs(1)));
148		assert_eq!("123s", exact_duration(Duration::from_secs(123)));
149		assert_eq!("1m", exact_duration(Duration::from_secs(60)));
150		assert_eq!("1h", exact_duration(Duration::from_secs(60 * 60)));
151		assert_eq!("1d", exact_duration(Duration::from_secs(60 * 60 * 24)));
152		assert_eq!("1w", exact_duration(Duration::from_secs(60 * 60 * 24 * 7)));
153	}
154}