Go page (/workspace/{id}/go): parse three [data-slot=usage-item] frames
(Rolling/Weekly/Monthly) into usage percentages + computed resets_at from the
relative reset-duration text. Zen page (/workspace/{id}): parse
[data-slot=balance] b into remaining + currency unit. Add an offline unit test
against tmp/ captures. Fix pretty renderer (percent display, money format,
day-aware relative times). Update DESIGN.md, README, examples and config
template to the real wrk_ workspace ids.
58 lines
1.7 KiB
Rust
58 lines
1.7 KiB
Rust
//! Human time strings from ISO 8601 UTC timestamps.
|
|
//!
|
|
//! Used by the pretty renderer for "resets in 3h12m" and "in 47m". Parses are
|
|
//! lenient. A parse failure returns the raw string unchanged.
|
|
|
|
use chrono::{DateTime, Utc};
|
|
|
|
/// Relative phrasing of a future or past timestamp. Examples: "in 30d7h",
|
|
/// "in 3h12m", "in 47m", "12m ago". Falls back to the input on parse failure.
|
|
pub fn relative(iso: &str) -> String {
|
|
let Some(t) = parse(iso) else {
|
|
return iso.to_string();
|
|
};
|
|
let now = Utc::now();
|
|
let d = t.signed_duration_since(now);
|
|
let secs = d.num_seconds();
|
|
let sign = if secs >= 0 { "in " } else { " ago" };
|
|
let body = dur_words(secs.abs());
|
|
format!("{sign}{body}")
|
|
}
|
|
|
|
/// Absolute UTC string like "2025-08-31 17:00 UTC".
|
|
pub fn absolute(iso: &str) -> String {
|
|
let Some(t) = parse(iso) else {
|
|
return iso.to_string();
|
|
};
|
|
t.format("%Y-%m-%d %H:%M UTC").to_string()
|
|
}
|
|
|
|
/// Compact duration. Starts with days when the span is at least one day.
|
|
fn dur_words(total: i64) -> String {
|
|
let s = total.max(0);
|
|
if s == 0 {
|
|
return "<1m".to_string();
|
|
}
|
|
let days = s / 86_400;
|
|
let rem = s % 86_400;
|
|
if days > 0 {
|
|
let hours = rem / 3_600;
|
|
if hours > 0 {
|
|
return format!("{days}d{hours}h");
|
|
}
|
|
return format!("{days}d");
|
|
}
|
|
let hours = s / 3_600;
|
|
let mins = (s % 3_600) / 60;
|
|
if hours > 0 {
|
|
return format!("{hours}h{m}m", m = if mins > 0 { mins.to_string() } else { String::new() });
|
|
}
|
|
let mins = (s / 60).max(1);
|
|
format!("{mins}m")
|
|
}
|
|
|
|
fn parse(iso: &str) -> Option<DateTime<Utc>> {
|
|
DateTime::parse_from_rfc3339(iso)
|
|
.ok()
|
|
.map(|dt| dt.with_timezone(&Utc))
|
|
} |