gluesql_core/data/
interval.rs

1mod error;
2mod primitive;
3mod string;
4
5pub use error::IntervalError;
6use {
7    super::Value,
8    crate::{ast::DateTimeField, result::Result},
9    chrono::{Datelike, Duration, NaiveDate, NaiveDateTime, NaiveTime, Timelike},
10    core::str::FromStr,
11    rust_decimal::{Decimal, prelude::ToPrimitive},
12    serde::{Deserialize, Serialize},
13    std::{cmp::Ordering, fmt::Debug},
14};
15
16/// Represents a time interval, which can be either in months or microseconds.
17///
18/// The [`Interval`] type is divided into two variants: [`Interval::Month`] and [`Interval::Microsecond`].
19/// This distinction is made because the conversion between months and days is not consistent.
20/// While a year can be clearly calculated as 12 months in the solar calendar,
21/// a month can vary in the number of days (28, 30, or 31 days).
22///
23/// To ensure precise calculations and comparisons, intervals are represented in the smallest
24/// unambiguous units: `Month` for month-based intervals and `Microsecond` for microsecond-based intervals.
25/// Comparisons are only allowed within the same unit type to avoid ambiguity.
26///
27/// For more details on how comparisons are implemented, refer to the [`Interval::partial_cmp`] trait.
28#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
29pub enum Interval {
30    Month(i32),
31    Microsecond(i64),
32}
33
34impl PartialOrd<Interval> for Interval {
35    fn partial_cmp(&self, other: &Interval) -> Option<Ordering> {
36        match (self, other) {
37            (Interval::Month(l), Interval::Month(r)) => Some(l.cmp(r)),
38            (Interval::Microsecond(l), Interval::Microsecond(r)) => Some(l.cmp(r)),
39            _ => None,
40        }
41    }
42}
43
44const SECOND: i64 = 1_000_000;
45const MINUTE: i64 = 60 * SECOND;
46const HOUR: i64 = 3600 * SECOND;
47const DAY: i64 = 24 * HOUR;
48
49impl Interval {
50    #[must_use]
51    pub fn unary_minus(&self) -> Self {
52        match self {
53            Interval::Month(v) => Interval::Month(-v),
54            Interval::Microsecond(v) => Interval::Microsecond(-v),
55        }
56    }
57
58    pub fn add(&self, other: &Interval) -> Result<Self> {
59        use Interval::*;
60
61        match (self, other) {
62            (Month(l), Month(r)) => Ok(Month(l + r)),
63            (Microsecond(l), Microsecond(r)) => Ok(Microsecond(l + r)),
64            _ => Err(IntervalError::AddBetweenYearToMonthAndHourToSecond.into()),
65        }
66    }
67
68    pub fn subtract(&self, other: &Interval) -> Result<Self> {
69        use Interval::*;
70
71        match (self, other) {
72            (Month(l), Month(r)) => Ok(Month(l - r)),
73            (Microsecond(l), Microsecond(r)) => Ok(Microsecond(l - r)),
74            _ => Err(IntervalError::SubtractBetweenYearToMonthAndHourToSecond.into()),
75        }
76    }
77
78    pub fn add_date(&self, date: &NaiveDate) -> Result<NaiveDateTime> {
79        self.add_timestamp(
80            &date
81                .and_hms_opt(0, 0, 0)
82                .ok_or_else(|| IntervalError::FailedToParseTime(date.to_string()))?,
83        )
84    }
85
86    pub fn subtract_from_date(&self, date: &NaiveDate) -> Result<NaiveDateTime> {
87        self.subtract_from_timestamp(
88            &date
89                .and_hms_opt(0, 0, 0)
90                .ok_or_else(|| IntervalError::FailedToParseTime(date.to_string()))?,
91        )
92    }
93
94    pub fn add_timestamp(&self, timestamp: &NaiveDateTime) -> Result<NaiveDateTime> {
95        match self {
96            Interval::Month(n) => {
97                let month = timestamp.month() as i32 + n;
98
99                let year = timestamp.year() + month / 12;
100                let month = month % 12;
101
102                timestamp
103                    .with_year(year)
104                    .and_then(|d| d.with_month(month as u32))
105                    .ok_or_else(|| IntervalError::DateOverflow { year, month }.into())
106            }
107            Interval::Microsecond(n) => Ok(*timestamp + Duration::microseconds(*n)),
108        }
109    }
110
111    pub fn subtract_from_timestamp(&self, timestamp: &NaiveDateTime) -> Result<NaiveDateTime> {
112        match self {
113            Interval::Month(n) => {
114                let months = timestamp.year() * 12 + timestamp.month() as i32 - n;
115
116                let year = months / 12;
117                let month = months % 12;
118
119                timestamp
120                    .with_year(year)
121                    .and_then(|d| d.with_month(month as u32))
122                    .ok_or_else(|| IntervalError::DateOverflow { year, month }.into())
123            }
124            Interval::Microsecond(n) => Ok(*timestamp - Duration::microseconds(*n)),
125        }
126    }
127
128    pub fn add_time(&self, time: &NaiveTime) -> Result<NaiveTime> {
129        match self {
130            Interval::Month(_) => Err(IntervalError::AddYearOrMonthToTime {
131                time: *time,
132                interval: *self,
133            }
134            .into()),
135            Interval::Microsecond(n) => Ok(*time + Duration::microseconds(*n)),
136        }
137    }
138
139    pub fn subtract_from_time(&self, time: &NaiveTime) -> Result<NaiveTime> {
140        match self {
141            Interval::Month(_) => Err(IntervalError::SubtractYearOrMonthToTime {
142                time: *time,
143                interval: *self,
144            }
145            .into()),
146            Interval::Microsecond(n) => Ok(*time - Duration::microseconds(*n)),
147        }
148    }
149
150    pub fn years(years: i32) -> Self {
151        Interval::Month(12 * years)
152    }
153
154    pub fn months(months: i32) -> Self {
155        Interval::Month(months)
156    }
157
158    pub fn extract(&self, field: &DateTimeField) -> Result<Value> {
159        let value = match (field, *self) {
160            (DateTimeField::Year, Interval::Month(i)) => i64::from(i) / 12,
161            (DateTimeField::Month, Interval::Month(i)) => i64::from(i),
162            (DateTimeField::Day, Interval::Microsecond(i)) => i / DAY,
163            (DateTimeField::Hour, Interval::Microsecond(i)) => i / HOUR,
164            (DateTimeField::Minute, Interval::Microsecond(i)) => i / MINUTE,
165            (DateTimeField::Second, Interval::Microsecond(i)) => i / SECOND,
166            _ => {
167                return Err(IntervalError::FailedToExtract.into());
168            }
169        };
170
171        Ok(Value::I64(value))
172    }
173
174    pub fn days(days: i32) -> Self {
175        Interval::Microsecond(i64::from(days) * DAY)
176    }
177
178    pub fn hours(hours: i32) -> Self {
179        Interval::Microsecond(i64::from(hours) * HOUR)
180    }
181
182    pub fn minutes(minutes: i32) -> Self {
183        Interval::Microsecond(i64::from(minutes) * MINUTE)
184    }
185
186    pub fn seconds(seconds: i64) -> Self {
187        Interval::Microsecond(seconds * SECOND)
188    }
189
190    pub fn milliseconds(milliseconds: i64) -> Self {
191        Interval::Microsecond(milliseconds * 1_000)
192    }
193
194    pub fn microseconds(microseconds: i64) -> Self {
195        Interval::Microsecond(microseconds)
196    }
197
198    pub fn try_from_str(
199        value: &str,
200        leading_field: Option<DateTimeField>,
201        last_field: Option<DateTimeField>,
202    ) -> Result<Self> {
203        use DateTimeField::*;
204
205        let value = value.trim_matches('\'');
206
207        let sign = if value.get(0..1) == Some("-") { -1 } else { 1 };
208
209        let parse_integer = |v: &str| {
210            v.parse::<i32>()
211                .map_err(|_| IntervalError::FailedToParseInteger(value.to_owned()).into())
212        };
213
214        let parse_decimal = |duration: i64| {
215            let parsed = Decimal::from_str(value)
216                .map_err(|_| IntervalError::FailedToParseDecimal(value.to_owned()))?;
217
218            (parsed * Decimal::from(duration))
219                .to_i64()
220                .ok_or_else(|| IntervalError::FailedToParseDecimal(value.to_owned()).into())
221                .map(Interval::Microsecond)
222        };
223
224        let parse_time = |v: &str| {
225            let sign = if v.get(0..1) == Some("-") { -1 } else { 1 };
226            let v = v.trim_start_matches('-');
227            let time = NaiveTime::from_str(v)
228                .map_err(|_| IntervalError::FailedToParseTime(value.to_owned()))?;
229
230            let msec = i64::from(time.hour()) * HOUR
231                + i64::from(time.minute()) * MINUTE
232                + i64::from(time.second()) * SECOND
233                + i64::from(time.nanosecond()) / 1000;
234
235            Ok(Interval::Microsecond(i64::from(sign) * msec))
236        };
237
238        match (leading_field, last_field) {
239            (Some(Year), None) => parse_integer(value).map(Interval::years),
240            (Some(Month), None) => parse_integer(value).map(Interval::months),
241            (Some(Day), None) => parse_decimal(DAY),
242            (Some(Hour), None) => parse_decimal(HOUR),
243            (Some(Minute), None) => parse_decimal(MINUTE),
244            (Some(Second), None) => parse_decimal(SECOND),
245            (Some(Year), Some(Month)) => {
246                let nums = value
247                    .trim_start_matches('-')
248                    .split('-')
249                    .map(parse_integer)
250                    .collect::<Result<Vec<_>>>()?;
251
252                match (nums.first(), nums.get(1)) {
253                    (Some(years), Some(months)) => {
254                        Ok(Interval::months(sign * (12 * years + months)))
255                    }
256                    _ => Err(IntervalError::FailedToParseYearToMonth(value.to_owned()).into()),
257                }
258            }
259            (Some(Day), Some(Hour)) => {
260                let nums = value
261                    .trim_start_matches('-')
262                    .split(' ')
263                    .map(parse_integer)
264                    .collect::<Result<Vec<_>>>()?;
265
266                match (nums.first(), nums.get(1)) {
267                    (Some(days), Some(hours)) => Ok(Interval::hours(sign * (24 * days + hours))),
268                    _ => Err(IntervalError::FailedToParseDayToHour(value.to_owned()).into()),
269                }
270            }
271            (Some(Day), Some(Minute)) => {
272                let nums = value.trim_start_matches('-').split(' ').collect::<Vec<_>>();
273
274                match (nums.first(), nums.get(1)) {
275                    (Some(days), Some(time)) => {
276                        let days = parse_integer(days)?;
277                        let time = format!("{time}:00");
278
279                        Interval::days(days)
280                            .add(&parse_time(&time)?)
281                            .map(|interval| sign * interval)
282                    }
283                    _ => Err(IntervalError::FailedToParseDayToMinute(value.to_owned()).into()),
284                }
285            }
286            (Some(Day), Some(Second)) => {
287                let nums = value.trim_start_matches('-').split(' ').collect::<Vec<_>>();
288
289                match (nums.first(), nums.get(1)) {
290                    (Some(days), Some(time)) => {
291                        let days = parse_integer(days)?;
292
293                        Interval::days(days)
294                            .add(&parse_time(time)?)
295                            .map(|interval| sign * interval)
296                    }
297                    _ => Err(IntervalError::FailedToParseDayToSecond(value.to_owned()).into()),
298                }
299            }
300            (Some(Hour), Some(Minute)) => parse_time(&format!("{value}:00")),
301            (Some(Hour), Some(Second)) => parse_time(value),
302            (Some(Minute), Some(Second)) => {
303                let time = value.trim_start_matches('-');
304
305                parse_time(&format!("00:{time}")).map(|v| sign * v)
306            }
307            (Some(from), Some(to)) => {
308                Err(IntervalError::UnsupportedRange(format!("{from:?}"), format!("{to:?}")).into())
309            }
310            (None, _) => Err(IntervalError::Unreachable.into()),
311        }
312    }
313}
314
315#[cfg(test)]
316mod tests {
317    use {
318        super::{Interval, IntervalError},
319        crate::ast::DateTimeField,
320        chrono::{NaiveDate, NaiveTime},
321    };
322
323    #[test]
324    fn cmp() {
325        assert!(Interval::Month(12) > Interval::Month(1));
326        assert!(Interval::Microsecond(300) > Interval::Microsecond(1));
327
328        // NOTE: Month and Microsecond are incomparable
329        assert!(
330            Interval::Month(1)
331                .partial_cmp(&Interval::Microsecond(1000))
332                .is_none()
333        );
334        assert!(
335            Interval::Microsecond(1000)
336                .partial_cmp(&Interval::Month(1))
337                .is_none()
338        );
339    }
340
341    fn date(year: i32, month: u32, day: u32) -> NaiveDate {
342        NaiveDate::from_ymd_opt(year, month, day).unwrap()
343    }
344
345    fn time(hour: u32, min: u32, sec: u32) -> NaiveTime {
346        NaiveTime::from_hms_opt(hour, min, sec).unwrap()
347    }
348
349    #[test]
350    fn arithmetic() {
351        use Interval::*;
352
353        macro_rules! test {
354            ($op: ident $a: expr, $b: expr => $c: expr) => {
355                assert_eq!($a.$op(&$b), Ok($c));
356            };
357        }
358
359        assert_eq!(Month(1).unary_minus(), Month(-1));
360        assert_eq!(Microsecond(1).unary_minus(), Microsecond(-1));
361
362        // date
363        assert_eq!(
364            Month(2).add_date(&date(2021, 11, 11)),
365            Ok(date(2022, 1, 11).and_hms_opt(0, 0, 0).unwrap())
366        );
367        assert_eq!(
368            Interval::hours(30).add_date(&date(2021, 11, 11)),
369            Ok(date(2021, 11, 12).and_hms_opt(6, 0, 0).unwrap())
370        );
371        assert_eq!(
372            Interval::years(999_999).add_date(&date(2021, 11, 11)),
373            Err(IntervalError::DateOverflow {
374                year: 1_002_020,
375                month: 11,
376            }
377            .into())
378        );
379        assert_eq!(
380            Month(2).subtract_from_date(&date(2021, 11, 11)),
381            Ok(date(2021, 9, 11).and_hms_opt(0, 0, 0).unwrap())
382        );
383        assert_eq!(
384            Month(14).subtract_from_date(&date(2021, 11, 11)),
385            Ok(date(2020, 9, 11).and_hms_opt(0, 0, 0).unwrap())
386        );
387        assert_eq!(
388            Interval::hours(30).subtract_from_date(&date(2021, 11, 11)),
389            Ok(date(2021, 11, 9).and_hms_opt(18, 0, 0).unwrap())
390        );
391        assert_eq!(
392            Interval::years(999_999).subtract_from_date(&date(2021, 11, 11)),
393            Err(IntervalError::DateOverflow {
394                year: -997_977,
395                month: -1,
396            }
397            .into())
398        );
399
400        // timestamp
401        assert_eq!(
402            Interval::minutes(2).add_timestamp(&date(2021, 11, 11).and_hms_opt(12, 3, 1).unwrap()),
403            Ok(date(2021, 11, 11).and_hms_opt(12, 5, 1).unwrap())
404        );
405        assert_eq!(
406            Interval::hours(30).add_timestamp(&date(2021, 11, 11).and_hms_opt(0, 30, 0).unwrap()),
407            Ok(date(2021, 11, 12).and_hms_opt(6, 30, 0).unwrap())
408        );
409        assert_eq!(
410            Interval::years(999_999)
411                .add_timestamp(&date(2021, 11, 11).and_hms_opt(1, 1, 1).unwrap()),
412            Err(IntervalError::DateOverflow {
413                year: 1_002_020,
414                month: 11,
415            }
416            .into())
417        );
418        assert_eq!(
419            Month(2).subtract_from_timestamp(&date(2021, 11, 11).and_hms_opt(1, 3, 59).unwrap()),
420            Ok(date(2021, 9, 11).and_hms_opt(1, 3, 59).unwrap())
421        );
422        assert_eq!(
423            Month(14).subtract_from_timestamp(&date(2021, 11, 11).and_hms_opt(23, 1, 1).unwrap()),
424            Ok(date(2020, 9, 11).and_hms_opt(23, 1, 1).unwrap())
425        );
426        assert_eq!(
427            Interval::seconds(30)
428                .subtract_from_timestamp(&date(2021, 11, 11).and_hms_opt(0, 0, 0).unwrap()),
429            Ok(date(2021, 11, 10).and_hms_opt(23, 59, 30).unwrap())
430        );
431        assert_eq!(
432            Interval::years(999_999)
433                .subtract_from_timestamp(&date(2021, 11, 11).and_hms_opt(0, 0, 0).unwrap()),
434            Err(IntervalError::DateOverflow {
435                year: -997_977,
436                month: -1,
437            }
438            .into())
439        );
440
441        // time
442        assert_eq!(
443            Interval::minutes(30).add_time(&time(23, 0, 1)),
444            Ok(time(23, 30, 1))
445        );
446        assert_eq!(
447            Interval::hours(20).add_time(&time(5, 30, 0)),
448            Ok(time(1, 30, 0))
449        );
450        assert_eq!(
451            Interval::years(1).add_time(&time(23, 0, 1)),
452            Err(IntervalError::AddYearOrMonthToTime {
453                time: time(23, 0, 1),
454                interval: Interval::years(1),
455            }
456            .into())
457        );
458        assert_eq!(
459            Interval::minutes(30).subtract_from_time(&time(23, 0, 1)),
460            Ok(time(22, 30, 1))
461        );
462        assert_eq!(
463            Interval::hours(20).subtract_from_time(&time(5, 30, 0)),
464            Ok(time(9, 30, 0))
465        );
466        assert_eq!(
467            Interval::months(3).subtract_from_time(&time(23, 0, 1)),
468            Err(IntervalError::SubtractYearOrMonthToTime {
469                time: time(23, 0, 1),
470                interval: Interval::months(3),
471            }
472            .into())
473        );
474
475        test!(add      Month(1), Month(2) => Month(3));
476        test!(subtract Month(1), Month(2) => Month(-1));
477
478        test!(add      Microsecond(1), Microsecond(2) => Microsecond(3));
479        test!(subtract Microsecond(1), Microsecond(2) => Microsecond(-1));
480    }
481
482    #[test]
483    fn try_from_literal() {
484        macro_rules! test {
485            ($value: expr, $datetime: ident => $expected_value: expr, $duration: ident) => {
486                let interval = Interval::try_from_str($value, Some(DateTimeField::$datetime), None);
487
488                assert_eq!(interval, Ok(Interval::$duration($expected_value)));
489            };
490            ($value: expr, $from: ident to $to: ident => $expected_value: expr, $duration: ident) => {
491                let interval = Interval::try_from_str(
492                    $value,
493                    Some(DateTimeField::$from),
494                    Some(DateTimeField::$to),
495                );
496
497                assert_eq!(interval, Ok(Interval::$duration($expected_value)));
498            };
499        }
500
501        test!("11",   Year   => 11,  years);
502        test!("-11",  Year   => -11, years);
503        test!("18",   Month  => 18,  months);
504        test!("-19",  Month  => -19, months);
505        test!("2",    Day    => 2,   days);
506        test!("1.5",  Day    => 36,  hours);
507        test!("-1.5", Day    => -36, hours);
508        test!("2.5",  Hour   => 150, minutes);
509        test!("1",    Hour   => 60,  minutes);
510        test!("-1",   Hour   => -60, minutes);
511        test!("35",   Minute => 35,  minutes);
512        test!("-35",  Minute => -35, minutes);
513        test!("10.5", Minute => 630, seconds);
514        test!("10",   Second => 10,  seconds);
515        test!("-10",  Second => -10, seconds);
516        test!("10.5", Second => 10_500_000, microseconds);
517        test!("-1.5", Second => -1_500_000, microseconds);
518
519        test!("10-2", Year to Month => 122, months);
520        test!("2 12", Day to Hour => 60, hours);
521        test!("1 01:30", Day to Minute => 60 * 24 + 90, minutes);
522        test!("1 01:30:40", Day to Second => (60 * 24 + 90) * 60 + 40, seconds);
523        test!("3 02:30:40.1234", Day to Second =>
524            (((3 * 24 + 2) * 60 + 30) * 60 + 40) * 1_000_000 + 123_400, microseconds);
525        test!("12:34", Hour to Minute => 12 * 60 + 34, minutes);
526        test!("12:34:56", Hour to Second => (12 * 60 + 34) * 60 + 56, seconds);
527        test!("12:34:56.1234", Hour to Second => ((12 * 60 + 34) * 60 + 56) * 1_000_000 + 123_400, microseconds);
528        test!("34:56.1234", Minute to Second => (34 * 60 + 56) * 1_000_000 + 123_400, microseconds);
529
530        test!("-1-4", Year to Month => -16, months);
531        test!("-2 10", Day to Hour => -58, hours);
532        test!("-1 00:01", Day to Minute => -(24 * 60 + 1), minutes);
533        test!("-1 00:00:01", Day to Second => -(24 * 3600 + 1), seconds);
534        test!("-1 00:00:01.1", Day to Second => -((24 * 3600 + 1) * 1000 + 100), milliseconds);
535        test!("-21:10", Hour to Minute => -(21 * 60 + 10), minutes);
536        test!("-05:12:03", Hour to Second => -(5 * 3600 + 12 * 60 + 3), seconds);
537        test!("-03:59:22.372", Hour to Second => -((3 * 3600 + 59 * 60 + 22) * 1000 + 372), milliseconds);
538        test!("-09:33", Minute to Second => -(9 * 60 + 33), seconds);
539        test!("-09:33.192", Minute to Second => -((9 * 60 + 33) * 1000 + 192), milliseconds);
540    }
541}