Skip to main content

nu_protocol/value/
duration.rs

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