use crate::parsing::{DURATION_TABLE, IEC_TABLE};
use chrono::prelude::*;
use jam_types::{Slot, JAM_COMMON_ERA};
use std::time::Duration;
pub fn slot_time(slot: Slot) -> String {
chrono::Local
.timestamp_opt(JAM_COMMON_ERA as i64 + slot as i64 * 6, 0)
.unwrap()
.format("%H:%M:%S")
.to_string()
}
pub fn slot(slot: Slot) -> String {
slot.to_string()
.as_bytes()
.rchunks(3)
.rev()
.map(std::str::from_utf8)
.collect::<Result<Vec<&str>, _>>()
.expect("converted from number; qed")
.join(",")
}
pub fn percent<T: Into<u128>>(n: T, d: T) -> String {
let ppm = (n.into() as f64 / d.into() as f64 * 100_000f64).ceil() / 1_000f64;
if ppm >= 1000.0 {
ppm.round()
} else if ppm >= 100.0 {
(ppm * 10f64).round() / 10f64
} else if ppm >= 10.0 {
(ppm * 100f64).round() / 100f64
} else {
ppm
}
.to_string()
}
pub fn bytes<T: Into<u128>>(n: T) -> String {
units(n, "B", 1024.0)
}
pub fn amount<T: Into<u128>>(n: T) -> String {
units(n, "", 1000.0)
}
pub fn gas<T: Into<u128>>(n: T) -> String {
units(n, "", 1000.0)
}
pub fn units<T: Into<u128>>(n: T, ones: &str, divisor: f64) -> String {
const UNITS: [&str; 7] = ["", "K", "M", "G", "T", "P", "E"];
let mut i = 0;
let mut n = n.into() as f64;
while n >= 999.5 && i > 0 || n >= 9999.5 {
n /= divisor;
i += 1;
}
let q = if n >= 100.0 {
1.0
} else if n >= 10.0 {
10.0
} else {
100.0
};
format!("{}{}", (n * q + 1.0e-14).round() / q, if i == 0 { ones } else { UNITS[i] })
}
pub fn exact_bytes(size: u64) -> String {
let (number, prefix) = get_iec_prefix(size);
format!("{number} {prefix}B")
}
fn get_iec_prefix(size: u64) -> (u64, &'static str) {
if size != 0 {
for (prefix, scale) in IEC_TABLE.iter() {
if size.is_multiple_of(*scale) {
return (size / scale, prefix);
}
}
}
(size, "")
}
pub fn exact_duration(d: Duration) -> String {
let (number, unit) = get_duration_unit(d);
format!("{number}{unit}")
}
fn get_duration_unit(duration: Duration) -> (u64, &'static str) {
let secs = duration.as_secs();
if secs != 0 {
for (unit, scale) in DURATION_TABLE.iter() {
if secs.is_multiple_of(*scale) {
return (secs / scale, unit);
}
}
}
(secs, "s")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn amount_works() {
assert_eq!(amount(0u32), "0");
assert_eq!(amount(999u32), "999");
assert_eq!(amount(9_999u32), "9999");
assert_eq!(amount(10_000u32), "10K");
assert_eq!(amount(10_049u32), "10K");
assert_eq!(amount(10_050u32), "10.1K");
assert_eq!(amount(100_499u32), "100K");
assert_eq!(amount(100_500u32), "101K");
assert_eq!(amount(1_004_999u32), "1M");
assert_eq!(amount(1_005_000u32), "1.01M");
assert_eq!(amount(10_049_999u32), "10M");
assert_eq!(amount(10_050_000u32), "10.1M");
}
#[test]
fn exact_bytes_works() {
assert_eq!("0 B", exact_bytes(0));
assert_eq!("1 B", exact_bytes(1));
assert_eq!("1 KiB", exact_bytes(1024));
assert_eq!("1 MiB", exact_bytes(1024 * 1024));
}
#[test]
fn exact_duration_works() {
assert_eq!("0s", exact_duration(Duration::ZERO));
assert_eq!("1s", exact_duration(Duration::from_secs(1)));
assert_eq!("123s", exact_duration(Duration::from_secs(123)));
assert_eq!("1m", exact_duration(Duration::from_secs(60)));
assert_eq!("1h", exact_duration(Duration::from_secs(60 * 60)));
assert_eq!("1d", exact_duration(Duration::from_secs(60 * 60 * 24)));
assert_eq!("1w", exact_duration(Duration::from_secs(60 * 60 * 24 * 7)));
}
}