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
51pub 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
79pub 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
97pub fn concise_duration(d: Duration) -> String {
102 use std::fmt::Write;
103 let nanos = d.as_nanos();
104 if nanos == 0 {
105 return "0 s".to_string();
106 }
107 for (unit, scale) in DURATION_TABLE.iter() {
108 let scale = u128::from(*scale);
109 if nanos >= scale {
110 let a = nanos / scale;
112 let rem = nanos % scale;
113 let b = rem * 10 / scale;
115 let mut buf = String::new();
116 let _ = write!(&mut buf, "{a}");
117 if b != 0 {
118 let _ = write!(&mut buf, ".{b}");
119 }
120 let _ = write!(&mut buf, " {unit}");
121 return buf;
122 }
123 }
124 format!("{nanos} ns")
125}
126
127pub fn exact_duration(d: Duration) -> String {
130 let (number, unit) = get_duration_unit(d.as_nanos());
131 format!("{number} {unit}")
132}
133
134fn get_duration_unit(nanos: u128) -> (u128, &'static str) {
135 if nanos == 0 {
136 return (0, "s");
137 }
138 for (unit, scale) in DURATION_TABLE.iter() {
139 let scale = u128::from(*scale);
140 if nanos.is_multiple_of(scale) {
141 return (nanos / scale, unit);
142 }
143 }
144 (nanos, "ns")
145}
146
147pub fn hash(hash: &[u8; 32]) -> String {
149 use std::fmt::Write;
150 let mut s = String::with_capacity(64);
151 for byte in hash.iter() {
152 let _ = write!(&mut s, "{byte:02x}");
153 }
154 s
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160
161 #[test]
162 fn amount_works() {
163 assert_eq!(amount(0u32), "0");
164 assert_eq!(amount(999u32), "999");
165 assert_eq!(amount(9_999u32), "9999");
166 assert_eq!(amount(10_000u32), "10K");
167 assert_eq!(amount(10_049u32), "10K");
168 assert_eq!(amount(10_050u32), "10.1K");
169 assert_eq!(amount(100_499u32), "100K");
170 assert_eq!(amount(100_500u32), "101K");
171 assert_eq!(amount(1_004_999u32), "1M");
172 assert_eq!(amount(1_005_000u32), "1.01M");
173 assert_eq!(amount(10_049_999u32), "10M");
174 assert_eq!(amount(10_050_000u32), "10.1M");
175 }
176
177 #[test]
178 fn exact_bytes_works() {
179 assert_eq!("0 B", exact_bytes(0));
180 assert_eq!("1 B", exact_bytes(1));
181 assert_eq!("1 KiB", exact_bytes(1024));
182 assert_eq!("1 MiB", exact_bytes(1024 * 1024));
183 }
184
185 #[test]
186 fn concise_duration_works() {
187 assert_eq!("0 s", concise_duration(Duration::ZERO));
188 assert_eq!("1 s", concise_duration(Duration::from_secs(1)));
189 assert_eq!("2 m", concise_duration(Duration::from_secs(120)));
190 assert_eq!("2 m", concise_duration(Duration::from_secs(123)));
191 assert_eq!("1 m", concise_duration(Duration::from_secs(60)));
192 assert_eq!("1 h", concise_duration(Duration::from_secs(60 * 60)));
193 assert_eq!("1 d", concise_duration(Duration::from_secs(60 * 60 * 24)));
194 assert_eq!("1 w", concise_duration(Duration::from_secs(60 * 60 * 24 * 7)));
195 assert_eq!("1.5 s", concise_duration(Duration::from_millis(1500)));
196 assert_eq!("1.5 ms", concise_duration(Duration::from_nanos(1_500_000)));
197 assert_eq!("1.5 µs", concise_duration(Duration::from_nanos(1_500)));
198 }
199
200 #[test]
201 fn exact_duration_works() {
202 assert_eq!("0 s", exact_duration(Duration::ZERO));
203 assert_eq!("1 s", exact_duration(Duration::from_secs(1)));
204 assert_eq!("123 s", exact_duration(Duration::from_secs(123)));
205 assert_eq!("1 m", exact_duration(Duration::from_secs(60)));
206 assert_eq!("1 h", exact_duration(Duration::from_secs(60 * 60)));
207 assert_eq!("1 d", exact_duration(Duration::from_secs(60 * 60 * 24)));
208 assert_eq!("1 w", exact_duration(Duration::from_secs(60 * 60 * 24 * 7)));
209 }
210}