Skip to main content

tank_core/
interval.rs

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