use k8s_openapi::jiff::Timestamp;
use k8s_openapi::jiff::tz::TimeZone;
pub(crate) fn age(time: Option<Timestamp>, now: Timestamp) -> String {
let Some(time) = time.filter(|t| t.as_second() != -62135596800) else {
return "<unknown>".into();
};
human_duration((now.as_nanosecond() - time.as_nanosecond()) / 1_000_000_000)
}
pub(crate) fn human_duration(seconds: i128) -> String {
let minutes = seconds / 60;
let hours = minutes / 60;
let days = hours / 24;
let years = days / 365;
let pair = |n, suffix, rest, tail| {
if rest == 0 {
format!("{n}{suffix}")
} else {
format!("{n}{suffix}{rest}{tail}")
}
};
match seconds {
..=-2 => "<invalid>".into(),
-1 => "0s".into(),
0..120 => format!("{seconds}s"),
_ if minutes < 10 => pair(minutes, "m", seconds % 60, "s"),
_ if minutes < 180 => format!("{minutes}m"),
_ if hours < 8 => pair(hours, "h", minutes % 60, "m"),
_ if hours < 48 => format!("{hours}h"),
_ if days < 8 => pair(days, "d", hours % 24, "h"),
_ if days < 730 => format!("{days}d"),
_ if days < 2920 => pair(years, "y", days % 365, "d"),
_ => format!("{years}y"),
}
}
pub(crate) fn timestamp(value: &str, zone: &k8s_openapi::jiff::tz::TimeZone) -> String {
value
.parse::<Timestamp>()
.ok()
.map(|t| {
t.to_zoned(zone.clone())
.strftime("%a, %d %b %Y %H:%M:%S %z")
.to_string()
})
.unwrap_or_else(|| "Mon, 01 Jan 0001 00:00:00 +0000".into())
}
pub(crate) fn value_timestamp(value: &serde_json::Value, zone: &TimeZone) -> String {
timestamp(crate::json::text(value), zone)
}
#[cfg(test)]
mod tests {
use super::human_duration;
#[test]
fn event_age_matches_upstream_duration_boundaries() {
for (seconds, expected) in [
(-2, "<invalid>"),
(-1, "0s"),
(0, "0s"),
(119, "119s"),
(120, "2m"),
(121, "2m1s"),
(600, "10m"),
(10800, "3h"),
(10860, "3h1m"),
(28800, "8h"),
(172800, "2d"),
(176400, "2d1h"),
(691200, "8d"),
(63072000, "2y"),
(63158400, "2y1d"),
(252288000, "8y"),
] {
assert_eq!(human_duration(seconds), expected);
}
}
}