tank_core/
interval.rs

1use crate::{Error, Result};
2use std::{
3    hash::Hash,
4    ops::{Add, AddAssign, Neg, Sub, SubAssign},
5};
6
7/// Units used to represent different parts of an `Interval` value.
8///
9/// Used when converting or formatting `Interval` values into specific
10/// units (years, months, days, time components, etc.).
11#[derive(PartialEq, Eq)]
12pub enum IntervalUnit {
13    Year,
14    Month,
15    Day,
16    Hour,
17    Minute,
18    Second,
19    Microsecond,
20    Nanosecond,
21}
22
23impl IntervalUnit {
24    pub fn from_bitmask(mask: u8) -> Result<IntervalUnit> {
25        Ok(match mask {
26            1 => IntervalUnit::Nanosecond,
27            2 => IntervalUnit::Microsecond,
28            4 => IntervalUnit::Second,
29            8 => IntervalUnit::Minute,
30            16 => IntervalUnit::Hour,
31            32 => IntervalUnit::Day,
32            64 => IntervalUnit::Month,
33            128 => IntervalUnit::Year,
34            _ => return Err(Error::msg("Invalid mask, it must be a single bit on")),
35        })
36    }
37}
38
39/// A time interval supporting months, days and nanoseconds.
40///
41/// This type represents durations with separate month/day components to preserve calendar-aware semantics.
42/// Months are not a fixed number of days but often converted to 30 days for conversion purpose.
43/// It provides helpers to convert to/from common durations and to extract unit values.
44#[derive(Default, Debug, Clone, Copy)]
45pub struct Interval {
46    pub months: i64,
47    pub days: i64,
48    pub nanos: i128,
49}
50
51impl Interval {
52    pub const ZERO: Interval = Interval {
53        months: 0,
54        days: 0,
55        nanos: 0,
56    };
57
58    pub const DAYS_IN_MONTH: f64 = 30.0;
59    pub const DAYS_IN_MONTH_AVG: f64 = 30.436875;
60    pub const SECS_IN_DAY: i64 = 60 * 60 * 24;
61    pub const NANOS_IN_SEC: i128 = 1_000_000_000;
62    pub const NANOS_IN_HOUR: i128 = Self::NANOS_IN_SEC * 3600;
63    pub const NANOS_IN_DAY: i128 = Self::SECS_IN_DAY as i128 * Self::NANOS_IN_SEC;
64    pub const MICROS_IN_DAY: i128 = Self::SECS_IN_DAY as i128 * 1_000_000;
65
66    pub const fn new(months: i64, days: i64, nanos: i128) -> Self {
67        Self {
68            months,
69            days,
70            nanos,
71        }
72    }
73
74    pub const fn from_duration(duration: &std::time::Duration) -> Self {
75        Self {
76            months: 0,
77            days: 0,
78            nanos: duration.as_nanos() as i128,
79        }
80    }
81
82    pub const fn from_nanos(value: i128) -> Self {
83        Self {
84            months: 0,
85            days: (value / Self::NANOS_IN_DAY) as _,
86            nanos: (value % Self::NANOS_IN_DAY),
87        }
88    }
89
90    pub const fn from_micros(value: i128) -> Self {
91        const MICROS_IN_DAY: i128 = (Interval::SECS_IN_DAY * 1_000_000) as _;
92        Self {
93            months: 0,
94            days: (value / MICROS_IN_DAY) as _,
95            nanos: (value % MICROS_IN_DAY) * 1_000,
96        }
97    }
98
99    pub const fn from_millis(value: i128) -> Self {
100        const MILLIS_IN_DAY: i128 = (Interval::SECS_IN_DAY * 1_000) as _;
101        Self {
102            months: 0,
103            days: (value / MILLIS_IN_DAY) as _,
104            nanos: ((value % MILLIS_IN_DAY) * 1_000_000),
105        }
106    }
107
108    pub const fn from_secs(value: i64) -> Self {
109        Self {
110            months: 0,
111            days: (value / Self::SECS_IN_DAY) as _,
112            nanos: ((value % Self::SECS_IN_DAY) * 1_000_000_000) as _,
113        }
114    }
115
116    pub const fn from_mins(value: i64) -> Self {
117        const MINS_IN_DAYS: i64 = 60 * 24;
118        let days = value / MINS_IN_DAYS;
119        let nanos = (value % MINS_IN_DAYS) * Interval::NANOS_IN_SEC as i64 * 60;
120        Self {
121            months: 0,
122            days,
123            nanos: nanos as _,
124        }
125    }
126
127    pub const fn from_hours(value: i64) -> Self {
128        Self {
129            months: 0,
130            days: (value / 24),
131            nanos: ((value % 24) * Interval::NANOS_IN_HOUR as i64) as _,
132        }
133    }
134
135    pub const fn from_days(value: i64) -> Self {
136        Self {
137            months: 0,
138            days: value,
139            nanos: 0,
140        }
141    }
142
143    pub const fn from_weeks(value: i64) -> Self {
144        Self {
145            months: 0,
146            days: value * 7,
147            nanos: 0,
148        }
149    }
150
151    pub const fn from_months(value: i64) -> Self {
152        Self {
153            months: value,
154            days: 0,
155            nanos: 0,
156        }
157    }
158
159    pub const fn from_years(value: i64) -> Self {
160        Self {
161            months: value * 12,
162            days: 0,
163            nanos: 0,
164        }
165    }
166
167    pub const fn as_hmsns(&self) -> (i128, u8, u8, u32) {
168        let mut nanos = self.nanos;
169        let mut hours = self.nanos / Self::NANOS_IN_HOUR;
170        nanos %= Self::NANOS_IN_HOUR;
171        hours += ((self.months * 30 + self.days) * 24) as i128;
172        let m = nanos / (60 * Self::NANOS_IN_SEC);
173        nanos %= 60 * Self::NANOS_IN_SEC;
174        let s = nanos / Self::NANOS_IN_SEC;
175        nanos %= Self::NANOS_IN_SEC;
176        (hours, m as _, s as _, nanos as _)
177    }
178
179    pub const fn is_zero(&self) -> bool {
180        self.months == 0 && self.days == 0 && self.nanos == 0
181    }
182
183    pub const fn as_duration(&self, days_in_month: f64) -> std::time::Duration {
184        let nanos = (self.months as f64) * days_in_month * (Interval::NANOS_IN_DAY as f64); // months
185        let nanos = nanos as i128 + self.days as i128 * Interval::NANOS_IN_DAY; // days
186        let nanos = nanos + self.nanos as i128;
187        let secs = (nanos / Interval::NANOS_IN_SEC) as u64;
188        let nanos = (nanos % Interval::NANOS_IN_SEC) as u32;
189        std::time::Duration::new(secs, nanos)
190    }
191
192    pub const fn units_and_factors(&self) -> &[(IntervalUnit, i128)] {
193        static UNITS: &[(IntervalUnit, i128)] = &[
194            (IntervalUnit::Year, Interval::NANOS_IN_DAY * 30 * 12),
195            (IntervalUnit::Month, Interval::NANOS_IN_DAY * 30),
196            (IntervalUnit::Day, Interval::NANOS_IN_DAY),
197            (IntervalUnit::Hour, Interval::NANOS_IN_SEC * 3600),
198            (IntervalUnit::Minute, Interval::NANOS_IN_SEC * 60),
199            (IntervalUnit::Second, Interval::NANOS_IN_SEC),
200            (IntervalUnit::Microsecond, 1_000),
201            (IntervalUnit::Nanosecond, 1),
202        ];
203        UNITS
204    }
205
206    pub fn units_mask(&self) -> u8 {
207        let mut mask = 0_u8;
208        if self.months != 0 {
209            if self.months % 12 == 0 {
210                mask |= 1 << 7;
211            } else if self.months != 0 {
212                mask |= 1 << 6;
213            }
214        }
215        let nanos = self.nanos + self.days as i128 * Interval::NANOS_IN_DAY;
216        if nanos != 0 {
217            for (i, &(_, factor)) in self.units_and_factors().iter().skip(2).enumerate() {
218                if nanos % factor == 0 {
219                    let shift = 5 - i; // i:0..5
220                    mask |= 1 << shift;
221                    break;
222                }
223            }
224        }
225        mask
226    }
227
228    pub fn unit_value(&self, unit: IntervalUnit) -> i128 {
229        if unit == IntervalUnit::Year {
230            self.months as i128 / 12
231        } else if unit == IntervalUnit::Month {
232            self.months as i128
233        } else {
234            let factor = *self
235                .units_and_factors()
236                .iter()
237                .find_map(|(u, k)| if *u == unit { Some(k) } else { None })
238                .expect("The unit must be present");
239            (self.days as i128 * Interval::NANOS_IN_DAY + self.nanos) / factor
240        }
241    }
242}
243
244impl PartialEq for Interval {
245    fn eq(&self, other: &Self) -> bool {
246        self.months == other.months
247            && self.days as i128 * Interval::NANOS_IN_DAY + self.nanos
248                == other.days as i128 * Interval::NANOS_IN_DAY + other.nanos
249    }
250}
251
252impl Eq for Interval {}
253
254impl Hash for Interval {
255    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
256        self.months.hash(state);
257        (self.days as i128 * Interval::NANOS_IN_DAY + self.nanos).hash(state);
258    }
259}
260
261macro_rules! sum_intervals {
262    ($lhs:ident $op:tt $rhs:ident) => {{
263        let days_total = $lhs.days as i128 $op $rhs.days as i128
264            + $lhs.nanos / Interval::NANOS_IN_DAY $op $rhs.nanos / Interval::NANOS_IN_DAY;
265        let days = days_total.clamp(i64::MIN as _, i64::MAX as _);
266        let mut nanos = $lhs.nanos % Interval::NANOS_IN_DAY $op $rhs.nanos % Interval::NANOS_IN_DAY;
267        if days != days_total {
268            nanos += (days_total - days) * Interval::NANOS_IN_DAY;
269        }
270        Interval {
271            months: $lhs.months $op $rhs.months,
272            days: days as _,
273            nanos,
274        }
275    }};
276}
277
278impl Add for Interval {
279    type Output = Interval;
280    fn add(self, rhs: Self) -> Self {
281        sum_intervals!(self + rhs)
282    }
283}
284
285impl AddAssign for Interval {
286    fn add_assign(&mut self, rhs: Self) {
287        *self = sum_intervals!(self + rhs);
288    }
289}
290
291impl Sub for Interval {
292    type Output = Interval;
293    fn sub(self, rhs: Self) -> Self::Output {
294        sum_intervals!(self - rhs)
295    }
296}
297
298impl SubAssign for Interval {
299    fn sub_assign(&mut self, rhs: Self) {
300        *self = sum_intervals!(self - rhs);
301    }
302}
303
304impl Neg for Interval {
305    type Output = Interval;
306    fn neg(self) -> Self::Output {
307        Self::default() - self
308    }
309}
310
311impl From<std::time::Duration> for Interval {
312    fn from(value: std::time::Duration) -> Self {
313        Self {
314            months: 0,
315            days: value.as_secs() as i64 / Interval::SECS_IN_DAY,
316            nanos: (value.as_secs() as i64 % Interval::SECS_IN_DAY) as i128
317                * Interval::NANOS_IN_SEC
318                + value.subsec_nanos() as i128,
319        }
320    }
321}
322
323impl From<Interval> for std::time::Duration {
324    fn from(value: Interval) -> Self {
325        value.as_duration(Interval::DAYS_IN_MONTH)
326    }
327}
328
329impl From<time::Duration> for Interval {
330    fn from(value: time::Duration) -> Self {
331        let seconds = value.whole_seconds();
332        Self {
333            months: 0,
334            days: seconds / Interval::SECS_IN_DAY,
335            nanos: ((seconds % Interval::SECS_IN_DAY) * Interval::NANOS_IN_SEC as i64
336                + value.subsec_nanoseconds() as i64) as i128,
337        }
338    }
339}
340
341impl From<Interval> for time::Duration {
342    fn from(value: Interval) -> Self {
343        let seconds = ((value.days + value.months * Interval::DAYS_IN_MONTH as i64)
344            * Interval::SECS_IN_DAY) as i128
345            + value.nanos / Interval::NANOS_IN_SEC;
346        let nanos = (value.nanos % Interval::NANOS_IN_SEC) as i32;
347        time::Duration::new(seconds as i64, nanos)
348    }
349}