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        if nanos < 0 {
171            nanos = -nanos;
172        }
173        let m = nanos / (60 * Self::NANOS_IN_SEC);
174        nanos %= 60 * Self::NANOS_IN_SEC;
175        let s = nanos / Self::NANOS_IN_SEC;
176        nanos %= Self::NANOS_IN_SEC;
177        (hours, m as _, s as _, nanos as _)
178    }
179
180    pub const fn is_zero(&self) -> bool {
181        self.months == 0 && self.days == 0 && self.nanos == 0
182    }
183
184    /// Convert to `std::time::Duration` (approximate).
185    ///
186    /// Uses `days_in_month` for conversion.
187    pub const fn as_duration(&self, days_in_month: f64) -> std::time::Duration {
188        let nanos = (self.months as f64) * days_in_month * (Interval::NANOS_IN_DAY as f64); // months
189        let nanos = nanos as i128 + self.days as i128 * Interval::NANOS_IN_DAY; // days
190        let nanos = nanos + self.nanos as i128;
191        let secs = (nanos / Interval::NANOS_IN_SEC) as u64;
192        let nanos = (nanos % Interval::NANOS_IN_SEC) as u32;
193        std::time::Duration::new(secs, nanos)
194    }
195
196    pub const fn units_and_factors(&self) -> &[(IntervalUnit, i128)] {
197        static UNITS: &[(IntervalUnit, i128)] = &[
198            (IntervalUnit::Year, Interval::NANOS_IN_DAY * 30 * 12),
199            (IntervalUnit::Month, Interval::NANOS_IN_DAY * 30),
200            (IntervalUnit::Day, Interval::NANOS_IN_DAY),
201            (IntervalUnit::Hour, Interval::NANOS_IN_SEC * 3600),
202            (IntervalUnit::Minute, Interval::NANOS_IN_SEC * 60),
203            (IntervalUnit::Second, Interval::NANOS_IN_SEC),
204            (IntervalUnit::Microsecond, 1_000),
205            (IntervalUnit::Nanosecond, 1),
206        ];
207        UNITS
208    }
209
210    /// Identify non-zero components (year, month, etc.).
211    pub fn units_mask(&self) -> u8 {
212        let mut mask = 0_u8;
213        if self.months != 0 {
214            if self.months % 12 == 0 {
215                mask |= 1 << 7;
216            } else if self.months != 0 {
217                mask |= 1 << 6;
218            }
219        }
220        let nanos = self.nanos + self.days as i128 * Interval::NANOS_IN_DAY;
221        if nanos != 0 {
222            for (i, &(_, factor)) in self.units_and_factors().iter().skip(2).enumerate() {
223                if nanos % factor == 0 {
224                    let shift = 5 - i; // i:0..5
225                    mask |= 1 << shift;
226                    break;
227                }
228            }
229        }
230        mask
231    }
232
233    /// Extract value for a specific unit (e.g. number of full years).
234    pub fn unit_value(&self, unit: IntervalUnit) -> i128 {
235        if unit == IntervalUnit::Year {
236            self.months as i128 / 12
237        } else if unit == IntervalUnit::Month {
238            self.months as i128
239        } else {
240            let factor = *self
241                .units_and_factors()
242                .iter()
243                .find_map(|(u, k)| if *u == unit { Some(k) } else { None })
244                .expect("The unit must be present");
245            (self.days as i128 * Interval::NANOS_IN_DAY + self.nanos) / factor
246        }
247    }
248}
249
250impl PartialEq for Interval {
251    fn eq(&self, other: &Self) -> bool {
252        self.months == other.months
253            && self.days as i128 * Interval::NANOS_IN_DAY + self.nanos
254                == other.days as i128 * Interval::NANOS_IN_DAY + other.nanos
255    }
256}
257
258impl Eq for Interval {}
259
260impl Hash for Interval {
261    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
262        self.months.hash(state);
263        (self.days as i128 * Interval::NANOS_IN_DAY + self.nanos).hash(state);
264    }
265}
266
267macro_rules! sum_intervals {
268    ($lhs:ident $op:tt $rhs:ident) => {{
269        let days_total = $lhs.days as i128 $op $rhs.days as i128
270            + $lhs.nanos / Interval::NANOS_IN_DAY $op $rhs.nanos / Interval::NANOS_IN_DAY;
271        let days = days_total.clamp(i64::MIN as _, i64::MAX as _);
272        let mut nanos = $lhs.nanos % Interval::NANOS_IN_DAY $op $rhs.nanos % Interval::NANOS_IN_DAY;
273        if days != days_total {
274            nanos += (days_total - days) * Interval::NANOS_IN_DAY;
275        }
276        Interval {
277            months: $lhs.months $op $rhs.months,
278            days: days as _,
279            nanos,
280        }
281    }};
282}
283
284impl Add for Interval {
285    type Output = Interval;
286    fn add(self, rhs: Self) -> Self {
287        sum_intervals!(self + rhs)
288    }
289}
290
291impl AddAssign for Interval {
292    fn add_assign(&mut self, rhs: Self) {
293        *self = sum_intervals!(self + rhs);
294    }
295}
296
297impl Sub for Interval {
298    type Output = Interval;
299    fn sub(self, rhs: Self) -> Self::Output {
300        sum_intervals!(self - rhs)
301    }
302}
303
304impl SubAssign for Interval {
305    fn sub_assign(&mut self, rhs: Self) {
306        *self = sum_intervals!(self - rhs);
307    }
308}
309
310impl Neg for Interval {
311    type Output = Interval;
312    fn neg(self) -> Self::Output {
313        Self::default() - self
314    }
315}
316
317impl From<std::time::Duration> for Interval {
318    fn from(value: std::time::Duration) -> Self {
319        Self {
320            months: 0,
321            days: value.as_secs() as i64 / Interval::SECS_IN_DAY,
322            nanos: (value.as_secs() as i64 % Interval::SECS_IN_DAY) as i128
323                * Interval::NANOS_IN_SEC
324                + value.subsec_nanos() as i128,
325        }
326    }
327}
328
329impl From<Interval> for std::time::Duration {
330    fn from(value: Interval) -> Self {
331        value.as_duration(Interval::DAYS_IN_MONTH)
332    }
333}
334
335impl From<time::Duration> for Interval {
336    fn from(value: time::Duration) -> Self {
337        let seconds = value.whole_seconds();
338        Self {
339            months: 0,
340            days: seconds / Interval::SECS_IN_DAY,
341            nanos: ((seconds % Interval::SECS_IN_DAY) * Interval::NANOS_IN_SEC as i64
342                + value.subsec_nanoseconds() as i64) as i128,
343        }
344    }
345}
346
347impl From<Interval> for time::Duration {
348    fn from(value: Interval) -> Self {
349        let seconds = ((value.days + value.months * Interval::DAYS_IN_MONTH as i64)
350            * Interval::SECS_IN_DAY) as i128
351            + value.nanos / Interval::NANOS_IN_SEC;
352        let nanos = (value.nanos % Interval::NANOS_IN_SEC) as i32;
353        time::Duration::new(seconds as i64, nanos)
354    }
355}