nu_protocol/value/
duration.rs

1use chrono::Duration;
2use std::{
3    borrow::Cow,
4    fmt::{Display, Formatter},
5};
6
7#[derive(Clone, Copy)]
8pub enum TimePeriod {
9    Nanos(i64),
10    Micros(i64),
11    Millis(i64),
12    Seconds(i64),
13    Minutes(i64),
14    Hours(i64),
15    Days(i64),
16    Weeks(i64),
17    Months(i64),
18    Years(i64),
19}
20
21impl TimePeriod {
22    pub fn to_text(self) -> Cow<'static, str> {
23        match self {
24            Self::Nanos(n) => format!("{n} ns").into(),
25            Self::Micros(n) => format!("{n} µs").into(),
26            Self::Millis(n) => format!("{n} ms").into(),
27            Self::Seconds(n) => format!("{n} sec").into(),
28            Self::Minutes(n) => format!("{n} min").into(),
29            Self::Hours(n) => format!("{n} hr").into(),
30            Self::Days(n) => format!("{n} day").into(),
31            Self::Weeks(n) => format!("{n} wk").into(),
32            Self::Months(n) => format!("{n} month").into(),
33            Self::Years(n) => format!("{n} yr").into(),
34        }
35    }
36}
37
38impl Display for TimePeriod {
39    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
40        write!(f, "{}", self.to_text())
41    }
42}
43
44pub fn format_duration(duration: i64) -> String {
45    let (sign, periods) = format_duration_as_timeperiod(duration);
46
47    let text = periods
48        .into_iter()
49        .map(|p| p.to_text().to_string().replace(' ', ""))
50        .collect::<Vec<String>>();
51
52    format!(
53        "{}{}",
54        if sign == -1 { "-" } else { "" },
55        text.join(" ").trim()
56    )
57}
58
59pub fn format_duration_as_timeperiod(duration: i64) -> (i32, Vec<TimePeriod>) {
60    // Attribution: most of this is taken from chrono-humanize-rs. Thanks!
61    // https://gitlab.com/imp/chrono-humanize-rs/-/blob/master/src/humantime.rs
62    // Current duration doesn't know a date it's based on, weeks is the max time unit it can normalize into.
63    // Don't guess or estimate how many years or months it might contain.
64
65    let (sign, duration) = if duration >= 0 {
66        (1, duration)
67    } else {
68        (-1, -duration)
69    };
70
71    let dur = Duration::nanoseconds(duration);
72
73    /// Split this a duration into number of whole weeks and the remainder
74    fn split_weeks(duration: Duration) -> (Option<i64>, Duration) {
75        let weeks = duration.num_weeks();
76        normalize_split(weeks, Duration::try_weeks(weeks), duration)
77    }
78
79    /// Split this a duration into number of whole days and the remainder
80    fn split_days(duration: Duration) -> (Option<i64>, Duration) {
81        let days = duration.num_days();
82        normalize_split(days, Duration::try_days(days), duration)
83    }
84
85    /// Split this a duration into number of whole hours and the remainder
86    fn split_hours(duration: Duration) -> (Option<i64>, Duration) {
87        let hours = duration.num_hours();
88        normalize_split(hours, Duration::try_hours(hours), duration)
89    }
90
91    /// Split this a duration into number of whole minutes and the remainder
92    fn split_minutes(duration: Duration) -> (Option<i64>, Duration) {
93        let minutes = duration.num_minutes();
94        normalize_split(minutes, Duration::try_minutes(minutes), duration)
95    }
96
97    /// Split this a duration into number of whole seconds and the remainder
98    fn split_seconds(duration: Duration) -> (Option<i64>, Duration) {
99        let seconds = duration.num_seconds();
100        normalize_split(seconds, Duration::try_seconds(seconds), duration)
101    }
102
103    /// Split this a duration into number of whole milliseconds and the remainder
104    fn split_milliseconds(duration: Duration) -> (Option<i64>, Duration) {
105        let millis = duration.num_milliseconds();
106        normalize_split(millis, Duration::try_milliseconds(millis), duration)
107    }
108
109    /// Split this a duration into number of whole seconds and the remainder
110    fn split_microseconds(duration: Duration) -> (Option<i64>, Duration) {
111        let micros = duration.num_microseconds().unwrap_or_default();
112        normalize_split(micros, Duration::microseconds(micros), duration)
113    }
114
115    /// Split this a duration into number of whole seconds and the remainder
116    fn split_nanoseconds(duration: Duration) -> (Option<i64>, Duration) {
117        let nanos = duration.num_nanoseconds().unwrap_or_default();
118        normalize_split(nanos, Duration::nanoseconds(nanos), duration)
119    }
120
121    fn normalize_split(
122        wholes: i64,
123        wholes_duration: impl Into<Option<Duration>>,
124        total_duration: Duration,
125    ) -> (Option<i64>, Duration) {
126        match wholes_duration.into() {
127            Some(wholes_duration) if wholes != 0 => {
128                (Some(wholes), total_duration - wholes_duration)
129            }
130            _ => (None, total_duration),
131        }
132    }
133
134    let mut periods = vec![];
135
136    let (weeks, remainder) = split_weeks(dur);
137    if let Some(weeks) = weeks {
138        periods.push(TimePeriod::Weeks(weeks));
139    }
140
141    let (days, remainder) = split_days(remainder);
142    if let Some(days) = days {
143        periods.push(TimePeriod::Days(days));
144    }
145
146    let (hours, remainder) = split_hours(remainder);
147    if let Some(hours) = hours {
148        periods.push(TimePeriod::Hours(hours));
149    }
150
151    let (minutes, remainder) = split_minutes(remainder);
152    if let Some(minutes) = minutes {
153        periods.push(TimePeriod::Minutes(minutes));
154    }
155
156    let (seconds, remainder) = split_seconds(remainder);
157    if let Some(seconds) = seconds {
158        periods.push(TimePeriod::Seconds(seconds));
159    }
160
161    let (millis, remainder) = split_milliseconds(remainder);
162    if let Some(millis) = millis {
163        periods.push(TimePeriod::Millis(millis));
164    }
165
166    let (micros, remainder) = split_microseconds(remainder);
167    if let Some(micros) = micros {
168        periods.push(TimePeriod::Micros(micros));
169    }
170
171    let (nanos, _remainder) = split_nanoseconds(remainder);
172    if let Some(nanos) = nanos {
173        periods.push(TimePeriod::Nanos(nanos));
174    }
175
176    if periods.is_empty() {
177        periods.push(TimePeriod::Seconds(0));
178    }
179
180    (sign, periods)
181}