tank_core/
interval.rs

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