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); let nanos = nanos as i128 + self.days as i128 * Interval::NANOS_IN_DAY; 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}