Skip to main content

tank_core/
interval.rs

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