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