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