llkv_types/
interval.rs

1//! Interval value stored as a combination of calendar months, whole days, and nanoseconds.
2
3use std::convert::TryFrom;
4
5use llkv_result::{Error, Result};
6
7/// Interval value stored as a combination of calendar months, whole days, and nanoseconds.
8///
9/// Months capture both month and year components (12 months == 1 year). Days represent
10/// whole 24-hour periods and nanoseconds account for sub-day precision. This mirrors the
11/// semantics of Arrow's `IntervalMonthDayNano` while keeping arithmetic manageable.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
13pub struct IntervalValue {
14    pub months: i32,
15    pub days: i32,
16    pub nanos: i64,
17}
18
19impl IntervalValue {
20    pub const fn new(months: i32, days: i32, nanos: i64) -> Self {
21        Self {
22            months,
23            days,
24            nanos,
25        }
26    }
27
28    pub const fn zero() -> Self {
29        Self::new(0, 0, 0)
30    }
31
32    /// Construct an interval from raw component totals, validating range limits.
33    pub fn from_components(months: i64, days: i64, nanos: i64) -> Result<Self> {
34        let months = i32::try_from(months)
35            .map_err(|_| Error::InvalidArgumentError("interval months out of range".into()))?;
36        let days = i32::try_from(days)
37            .map_err(|_| Error::InvalidArgumentError("interval days out of range".into()))?;
38        Ok(Self::new(months, days, nanos))
39    }
40
41    pub fn checked_add(self, other: Self) -> Option<Self> {
42        Some(Self {
43            months: self.months.checked_add(other.months)?,
44            days: self.days.checked_add(other.days)?,
45            nanos: self.nanos.checked_add(other.nanos)?,
46        })
47    }
48
49    pub fn checked_sub(self, other: Self) -> Option<Self> {
50        Some(Self {
51            months: self.months.checked_sub(other.months)?,
52            days: self.days.checked_sub(other.days)?,
53            nanos: self.nanos.checked_sub(other.nanos)?,
54        })
55    }
56
57    pub fn checked_neg(self) -> Option<Self> {
58        Some(Self {
59            months: self.months.checked_neg()?,
60            days: self.days.checked_neg()?,
61            nanos: self.nanos.checked_neg()?,
62        })
63    }
64
65    pub fn checked_scale(self, factor: i64) -> Option<Self> {
66        let months = i64::from(self.months).checked_mul(factor)?;
67        let days = i64::from(self.days).checked_mul(factor)?;
68        let nanos = self.nanos.checked_mul(factor)?;
69        Some(Self {
70            months: months.try_into().ok()?,
71            days: days.try_into().ok()?,
72            nanos,
73        })
74    }
75
76    pub const fn is_zero(self) -> bool {
77        self.months == 0 && self.days == 0 && self.nanos == 0
78    }
79}