use jiff::Timestamp;
const MINUTE: i64 = 60;
const HOUR: i64 = 60 * MINUTE;
const DAY: i64 = 24 * HOUR;
const MONTH: i64 = 30 * DAY;
const YEAR: i64 = 365 * DAY;
pub fn iso8601(unix_seconds: i64) -> String {
match Timestamp::from_second(unix_seconds) {
Ok(ts) => ts.strftime("%Y-%m-%dT%H:%M:%SZ").to_string(),
Err(_) => "(invalid time)".to_string(),
}
}
pub fn relative(now_unix: i64, then_unix: i64) -> String {
let secs = now_unix - then_unix;
if secs < MINUTE {
return "just now".to_string();
}
let (value, unit) = if secs < HOUR {
(secs / MINUTE, "m")
} else if secs < DAY {
(secs / HOUR, "h")
} else if secs < MONTH {
(secs / DAY, "d")
} else if secs < YEAR {
(secs / MONTH, "mo")
} else {
(secs / YEAR, "y")
};
format!("{value}{unit} ago")
}
pub fn now_unix() -> i64 {
Timestamp::now().as_second()
}
pub fn parse_iso8601(text: &str) -> Option<i64> {
text.parse::<Timestamp>().ok().map(|t| t.as_second())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn iso8601_formats_known_instant() {
assert_eq!(iso8601(1_705_314_600), "2024-01-15T10:30:00Z");
assert_eq!(iso8601(0), "1970-01-01T00:00:00Z");
}
#[test]
fn relative_units_pick_largest() {
assert_eq!(relative(1_000, 1_000), "just now");
assert_eq!(relative(1_000 + 30, 1_000), "just now");
assert_eq!(relative(1_000 + 5 * MINUTE, 1_000), "5m ago");
assert_eq!(relative(1_000 + 3 * HOUR, 1_000), "3h ago");
assert_eq!(relative(1_000 + 2 * DAY, 1_000), "2d ago");
assert_eq!(relative(1_000 + 4 * MONTH, 1_000), "4mo ago");
assert_eq!(relative(1_000 + YEAR, 1_000), "1y ago");
assert_eq!(relative(1_000 + 3 * YEAR, 1_000), "3y ago");
}
#[test]
fn relative_boundaries() {
assert_eq!(relative(59, 0), "just now");
assert_eq!(relative(60, 0), "1m ago");
assert_eq!(relative(HOUR - 1, 0), "59m ago");
assert_eq!(relative(HOUR, 0), "1h ago");
assert_eq!(relative(DAY - 1, 0), "23h ago");
assert_eq!(relative(DAY, 0), "1d ago");
assert_eq!(relative(MONTH - 1, 0), "29d ago");
assert_eq!(relative(MONTH, 0), "1mo ago");
assert_eq!(relative(YEAR, 0), "1y ago");
}
#[test]
fn future_times_are_just_now() {
assert_eq!(relative(0, 1_000), "just now");
}
#[test]
fn now_unix_is_after_2020() {
assert!(now_unix() > 1_600_000_000);
}
#[test]
fn iso8601_round_trips_through_parse() {
for unix in [0, 1_705_314_600, 1_600_000_000] {
assert_eq!(parse_iso8601(&iso8601(unix)), Some(unix));
}
assert_eq!(parse_iso8601("not a timestamp"), None);
}
}