1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
|
// Copyright (c) 2023 Joining7943 <joining@posteo.de>
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
//! A systemd time span parser as specified in `man systemd.time`. The output of this example is
//! imitating the output of `systemd-analyze timespan SYSTEMD_TIME_SPAN`
use std::time::Duration;
use clap::{command, Arg};
use fundu::TimeUnit::*;
use fundu::{CustomDurationParser, SYSTEMD_TIME_UNITS};
/// Create a human readable string like `100y 2h 46min 40s 123us 456ns` from a `Duration`
fn make_human(duration: Duration) -> String {
const YEAR: u64 = Year.multiplier().0.unsigned_abs();
const MONTH: u64 = Month.multiplier().0.unsigned_abs();
const WEEK: u64 = Week.multiplier().0.unsigned_abs();
const DAY: u64 = Day.multiplier().0.unsigned_abs();
const HOUR: u64 = Hour.multiplier().0.unsigned_abs();
const MINUTE: u64 = Minute.multiplier().0.unsigned_abs();
const MILLIS_PER_NANO: u32 = 1_000_000;
const MICROS_PER_NANO: u32 = 1_000;
if duration.is_zero() {
return "0".to_string();
}
let mut result = Vec::with_capacity(10);
let mut secs = duration.as_secs();
if secs > 0 {
if secs >= YEAR {
result.push(format!("{}y", secs / YEAR));
secs %= YEAR;
}
if secs >= MONTH {
result.push(format!("{}month", secs / MONTH));
secs %= MONTH;
}
if secs >= WEEK {
result.push(format!("{}w", secs / WEEK));
secs %= WEEK;
}
if secs >= DAY {
result.push(format!("{}d", secs / DAY));
secs %= DAY;
}
if secs >= HOUR {
result.push(format!("{}h", secs / HOUR));
secs %= HOUR;
}
if secs >= MINUTE {
result.push(format!("{}min", secs / MINUTE));
secs %= MINUTE;
}
if secs >= 1 {
result.push(format!("{}s", secs));
}
}
let mut nanos = duration.subsec_nanos();
if nanos > 0 {
if nanos >= MILLIS_PER_NANO {
result.push(format!("{}ms", nanos / MILLIS_PER_NANO));
nanos %= MILLIS_PER_NANO;
}
if nanos >= MICROS_PER_NANO {
result.push(format!("{}us", nanos / MICROS_PER_NANO));
nanos %= MICROS_PER_NANO;
}
if nanos >= 1 {
result.push(format!("{}ns", nanos));
}
}
result.join(" ")
}
fn main() {
let matches = command!()
.about(
"A systemd time span parser as specified in `man systemd.time`. The output of this \
example is imitating the output of `systemd-analyze timespan SYSTEMD_TIME_SPAN`",
)
.allow_negative_numbers(true)
.arg(
Arg::new("SYSTEMD_TIME_SPAN")
.action(clap::ArgAction::Set)
.help("A time span as specified in `man systemd.time`"),
)
.get_matches();
let delimiter = |byte| matches!(byte, b' ' | b'\t' | b'\n' | b'\r');
let parser = CustomDurationParser::builder()
.time_units(&SYSTEMD_TIME_UNITS)
.disable_exponent()
.disable_fraction()
.disable_infinity()
.allow_delimiter(delimiter)
.parse_multiple(delimiter, None)
.build();
let input: &String = matches
.get_one("SYSTEMD_TIME_SPAN")
.expect("At least one argument must be present");
match parser.parse(input.trim()) {
Ok(duration) => {
let duration: std::time::Duration = duration.try_into().unwrap();
println!("{:>8}: {}", "Original", input);
println!("{:>8}: {}", "μs", duration.as_micros());
println!("{:>8}: {}", "Human", make_human(duration));
}
Err(error) => eprintln!("Failed to parse time span '{}': {}", &input, error),
}
}
|