ocd/src/timefmt.rs
ocd 41f35aec34 Wire real OpenCode selectors and URLs from captured pages
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.
2026-07-30 01:16:36 +03:00

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))
}