tank_core/
interval.rs

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