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