diff --git a/src/admin/server/commands.rs b/src/admin/server/commands.rs index 2ab71033..06007fb8 100644 --- a/src/admin/server/commands.rs +++ b/src/admin/server/commands.rs @@ -1,24 +1,17 @@ -use conduit::{warn, Error, Result}; +use conduit::{utils::time, warn, Error, Result}; use ruma::events::room::message::RoomMessageEventContent; use crate::services; pub(super) async fn uptime(_body: Vec<&str>) -> Result { - let seconds = services() + let elapsed = services() .server .started .elapsed() - .expect("standard duration") - .as_secs(); - let result = format!( - "up {} days, {} hours, {} minutes, {} seconds.", - seconds / 86400, - (seconds % 86400) / 60 / 60, - (seconds % 3600) / 60, - seconds % 60, - ); + .expect("standard duration"); - Ok(RoomMessageEventContent::notice_plain(result)) + let result = time::pretty(elapsed); + Ok(RoomMessageEventContent::notice_plain(format!("{result}."))) } pub(super) async fn show_config(_body: Vec<&str>) -> Result { diff --git a/src/core/utils/time.rs b/src/core/utils/time.rs index 7de00e9e..39ceb3c3 100644 --- a/src/core/utils/time.rs +++ b/src/core/utils/time.rs @@ -1,4 +1,6 @@ -use std::time::{SystemTime, UNIX_EPOCH}; +#![allow(clippy::enum_glob_use)] + +use std::time::{Duration, SystemTime, UNIX_EPOCH}; #[inline] #[must_use] @@ -26,3 +28,78 @@ pub fn format(ts: SystemTime, str: &str) -> String { let dt: DateTime = ts.into(); dt.format(str).to_string() } + +#[must_use] +pub fn pretty(d: Duration) -> String { + use Unit::*; + + let fmt = |w, f, u| format!("{w}.{f} {u}"); + let gen64 = |w, f, u| fmt(w, (f * 100.0) as u64, u); + let gen128 = |w, f, u| gen64(u64::try_from(w).expect("u128 to u64"), f, u); + match whole_and_frac(d) { + (Days(whole), frac) => gen64(whole, frac, "days"), + (Hours(whole), frac) => gen64(whole, frac, "hours"), + (Mins(whole), frac) => gen64(whole, frac, "minutes"), + (Secs(whole), frac) => gen64(whole, frac, "seconds"), + (Millis(whole), frac) => gen128(whole, frac, "milliseconds"), + (Micros(whole), frac) => gen128(whole, frac, "microseconds"), + (Nanos(whole), frac) => gen128(whole, frac, "nanoseconds"), + } +} + +/// Return a pair of (whole part, frac part) from a duration where. The whole +/// part is the largest Unit containing a non-zero value, the frac part is a +/// rational remainder left over. +#[must_use] +pub fn whole_and_frac(d: Duration) -> (Unit, f64) { + use Unit::*; + + let whole = whole_unit(d); + ( + whole, + match whole { + Days(_) => (d.as_secs() % 86_400) as f64 / 86_400.0, + Hours(_) => (d.as_secs() % 3_600) as f64 / 3_600.0, + Mins(_) => (d.as_secs() % 60) as f64 / 60.0, + Secs(_) => f64::from(d.subsec_millis()) / 1000.0, + Millis(_) => f64::from(d.subsec_micros()) / 1000.0, + Micros(_) => f64::from(d.subsec_nanos()) / 1000.0, + Nanos(_) => 0.0, + }, + ) +} + +/// Return the largest Unit which represents the duration. The value is +/// rounded-down, but never zero. +#[must_use] +pub fn whole_unit(d: Duration) -> Unit { + use Unit::*; + + match d.as_secs() { + 86_400.. => Days(d.as_secs() / 86_400), + 3_600..=86_399 => Hours(d.as_secs() / 3_600), + 60..=3_599 => Mins(d.as_secs() / 60), + + _ => match d.as_micros() { + 1_000_000.. => Secs(d.as_secs()), + 1_000..=999_999 => Millis(d.subsec_millis().into()), + + _ => match d.as_nanos() { + 1_000.. => Micros(d.subsec_micros().into()), + + _ => Nanos(d.subsec_nanos().into()), + }, + }, + } +} + +#[derive(Eq, PartialEq, Clone, Copy, Debug)] +pub enum Unit { + Days(u64), + Hours(u64), + Mins(u64), + Secs(u64), + Millis(u128), + Micros(u128), + Nanos(u128), +}