1use std::fmt::{self, Write};
13use std::sync::LazyLock;
14use std::time::Duration;
15
16use anyhow::{anyhow, bail};
17use mz_persist_types::columnar::FixedSizeCodec;
18use mz_proto::{RustType, TryFromProtoError};
19use num_traits::CheckedMul;
20use proptest::prelude::{Arbitrary, BoxedStrategy, Strategy, any};
21use serde::{Deserialize, Serialize};
22
23use crate::adt::datetime::DateTimeField;
24use crate::adt::numeric::{DecimalLike, Numeric};
25
26include!(concat!(env!("OUT_DIR"), "/mz_repr.adt.interval.rs"));
27
28#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Hash, Deserialize)]
32pub struct Interval {
33 pub months: i32,
35 pub days: i32,
39 pub micros: i64,
44}
45
46impl Default for Interval {
47 fn default() -> Self {
48 Self {
49 months: 0,
50 days: 0,
51 micros: 0,
52 }
53 }
54}
55
56impl RustType<ProtoInterval> for Interval {
57 fn into_proto(&self) -> ProtoInterval {
58 ProtoInterval {
59 months: self.months,
60 days: self.days,
61 micros: self.micros,
62 }
63 }
64
65 fn from_proto(proto: ProtoInterval) -> Result<Self, TryFromProtoError> {
66 Ok(Interval {
67 months: proto.months,
68 days: proto.days,
69 micros: proto.micros,
70 })
71 }
72}
73
74impl num_traits::ops::checked::CheckedNeg for Interval {
75 fn checked_neg(&self) -> Option<Self> {
76 if let (Some(months), Some(days), Some(micros)) = (
77 self.months.checked_neg(),
78 self.days.checked_neg(),
79 self.micros.checked_neg(),
80 ) {
81 Some(Self::new(months, days, micros))
82 } else {
83 None
84 }
85 }
86}
87
88impl std::str::FromStr for Interval {
89 type Err = anyhow::Error;
90
91 fn from_str(s: &str) -> Result<Self, Self::Err> {
92 crate::strconv::parse_interval(s).map_err(|e| anyhow!(e))
93 }
94}
95
96static MONTH_OVERFLOW_ERROR: LazyLock<String> = LazyLock::new(|| {
97 format!(
98 "Overflows maximum months; cannot exceed {}/{} microseconds",
99 i32::MAX,
100 i32::MIN,
101 )
102});
103static DAY_OVERFLOW_ERROR: LazyLock<String> = LazyLock::new(|| {
104 format!(
105 "Overflows maximum days; cannot exceed {}/{} microseconds",
106 i32::MAX,
107 i32::MIN,
108 )
109});
110pub static USECS_PER_DAY: LazyLock<i64> = LazyLock::new(|| {
111 Interval::convert_date_time_unit(DateTimeField::Day, DateTimeField::Microseconds, 1i64).unwrap()
112});
113
114#[derive(Debug, Clone)]
115pub enum RoundBehavior {
116 Truncate,
117 Nearest,
118}
119
120impl Interval {
121 pub const CENTURY_PER_MILLENNIUM: u16 = 10;
122 pub const DECADE_PER_CENTURY: u16 = 10;
123 pub const YEAR_PER_DECADE: u16 = 10;
124 pub const MONTH_PER_YEAR: u16 = 12;
125 pub const DAY_PER_MONTH: u16 = 30;
127 pub const HOUR_PER_DAY: u16 = 24;
129 pub const MINUTE_PER_HOUR: u16 = 60;
130 pub const SECOND_PER_MINUTE: u16 = 60;
131 pub const MILLISECOND_PER_SECOND: u16 = 1_000;
132 pub const MICROSECOND_PER_MILLISECOND: u16 = 1_000;
133 pub const NANOSECOND_PER_MICROSECOND: u16 = 1_000;
134 pub const EPOCH_DAYS_PER_YEAR: f64 = 365.25;
140
141 pub const fn new(months: i32, days: i32, micros: i64) -> Interval {
143 Interval {
144 months,
145 days,
146 micros,
147 }
148 }
149
150 pub fn from_duration(duration: &Duration) -> Result<Interval, anyhow::Error> {
155 if duration.subsec_nanos() % 1000 != 0 {
156 return Err(anyhow!(
157 "cannot convert Duration to Interval due to fractional microseconds"
158 ));
159 }
160 Ok(Interval {
161 months: 0,
162 days: 0,
163 micros: duration.as_micros().try_into()?,
164 })
165 }
166
167 pub fn from_chrono_duration(duration: chrono::Duration) -> Result<Self, anyhow::Error> {
170 let Some(micros) = duration.num_microseconds() else {
171 bail!("cannot convert Duration to Interval due to overflowed microseconds");
172 };
173 Ok(Self {
174 months: 0,
175 days: 0,
176 micros,
177 })
178 }
179
180 pub fn checked_add(&self, other: &Self) -> Option<Self> {
181 let months = match self.months.checked_add(other.months) {
182 Some(m) => m,
183 None => return None,
184 };
185 let days = match self.days.checked_add(other.days) {
186 Some(d) => d,
187 None => return None,
188 };
189 let micros = match self.micros.checked_add(other.micros) {
190 Some(us) => us,
191 None => return None,
192 };
193
194 Some(Self::new(months, days, micros))
195 }
196
197 pub fn checked_mul(&self, other: f64) -> Option<Self> {
198 self.checked_op(other, |f1, f2| f1 * f2)
199 }
200
201 pub fn checked_div(&self, other: f64) -> Option<Self> {
202 self.checked_op(other, |f1, f2| f1 / f2)
203 }
204
205 #[allow(clippy::as_conversions)]
208 fn checked_op<F1>(&self, other: f64, op: F1) -> Option<Self>
209 where
210 F1: Fn(f64, f64) -> f64,
211 {
212 let months = op(f64::from(self.months), other);
213 if months.is_nan()
214 || months.is_infinite()
215 || months < i32::MIN.into()
216 || months > i32::MAX.into()
217 {
218 return None;
219 }
220
221 let days =
222 op(f64::from(self.days), other) + months.fract() * f64::from(Self::DAY_PER_MONTH);
223 if days.is_nan() || days.is_infinite() || days < i32::MIN.into() || days > i32::MAX.into() {
224 return None;
225 }
226
227 let micros = op(self.micros as f64, other)
228 + days.fract()
229 * f64::from(Self::HOUR_PER_DAY)
230 * f64::from(Self::MINUTE_PER_HOUR)
231 * f64::from(Self::SECOND_PER_MINUTE)
232 * f64::from(Self::MILLISECOND_PER_SECOND)
233 * f64::from(Self::MICROSECOND_PER_MILLISECOND);
234
235 if micros.is_nan()
236 || micros.is_infinite()
237 || Numeric::from(micros) < Numeric::from(i64::MIN)
238 || Numeric::from(micros) > Numeric::from(i64::MAX)
239 {
240 return None;
241 }
242
243 Some(Self::new(months as i32, days as i32, micros as i64))
244 }
245
246 pub fn millennia(&self) -> i32 {
251 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Millennium, self.months)
252 .unwrap()
253 }
254
255 pub fn centuries(&self) -> i32 {
260 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Century, self.months)
261 .unwrap()
262 }
263
264 pub fn decades(&self) -> i32 {
269 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Decade, self.months)
270 .unwrap()
271 }
272
273 pub fn years(&self) -> i32 {
278 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Year, self.months)
279 .unwrap()
280 }
281
282 pub fn quarters(&self) -> i32 {
288 self.months() / 3 + 1
289 }
290
291 pub fn months(&self) -> i32 {
297 self.months % i32::from(Self::MONTH_PER_YEAR)
298 }
299
300 pub fn days(&self) -> i64 {
306 self.days.into()
307 }
308
309 pub fn hours(&self) -> i64 {
315 Self::convert_date_time_unit(
316 DateTimeField::Microseconds,
317 DateTimeField::Hour,
318 self.micros,
319 )
320 .unwrap()
321 % i64::from(Self::HOUR_PER_DAY)
322 }
323
324 pub fn minutes(&self) -> i64 {
330 Self::convert_date_time_unit(
331 DateTimeField::Microseconds,
332 DateTimeField::Minute,
333 self.micros,
334 )
335 .unwrap()
336 % i64::from(Self::MINUTE_PER_HOUR)
337 }
338
339 pub fn seconds<T>(&self) -> T
344 where
345 T: DecimalLike,
346 {
347 T::lossy_from(self.micros % 60_000_000) / T::from(1e6)
348 }
349
350 pub fn milliseconds<T>(&self) -> T
355 where
356 T: DecimalLike,
357 {
358 T::lossy_from(self.micros % 60_000_000) / T::from(1e3)
359 }
360
361 pub fn microseconds<T>(&self) -> T
366 where
367 T: DecimalLike,
368 {
369 T::lossy_from(self.micros % 60_000_000)
370 }
371
372 pub fn nanoseconds(&self) -> i32 {
374 (self.micros % 1_000_000 * 1_000).try_into().unwrap()
375 }
376
377 pub fn as_epoch_seconds<T>(&self) -> T
381 where
382 T: DecimalLike,
383 {
384 let days = T::from(self.years()) * T::from(Self::EPOCH_DAYS_PER_YEAR)
385 + T::from(self.months()) * T::from(Self::DAY_PER_MONTH)
386 + T::from(self.days);
387 let seconds = days
388 * T::from(Self::HOUR_PER_DAY)
389 * T::from(Self::MINUTE_PER_HOUR)
390 * T::from(Self::SECOND_PER_MINUTE);
391
392 seconds
393 + T::lossy_from(self.micros)
394 / (T::from(Self::MICROSECOND_PER_MILLISECOND)
395 * T::from(Self::MILLISECOND_PER_SECOND))
396 }
397
398 pub fn as_microseconds(&self) -> i128 {
400 Self::convert_date_time_unit(
403 DateTimeField::Month,
404 DateTimeField::Microseconds,
405 i128::from(self.months),
406 ).unwrap() +
407 Self::convert_date_time_unit(
410 DateTimeField::Day,
411 DateTimeField::Microseconds,
412 i128::from(self.days),
413 ).unwrap() +
414 i128::from(self.micros)
415 }
416
417 pub fn as_milliseconds(&self) -> i128 {
419 self.as_microseconds() / 1000
420 }
421
422 pub fn duration_as_chrono(&self) -> chrono::Duration {
424 use chrono::Duration;
425 Duration::try_days(self.days.into()).unwrap() + Duration::microseconds(self.micros)
426 }
427
428 pub fn duration(&self) -> Result<Duration, anyhow::Error> {
429 if self.months != 0 {
430 bail!("cannot convert interval with months to duration");
431 }
432 if self.is_negative() {
433 bail!("cannot convert negative interval to duration");
434 }
435 let micros: u64 = u64::try_from(self.as_microseconds())?;
436 Ok(Duration::from_micros(micros))
437 }
438
439 pub fn truncate_high_fields(&mut self, f: DateTimeField) {
441 match f {
442 DateTimeField::Year => {}
443 DateTimeField::Month => self.months %= 12,
444 DateTimeField::Day => self.months = 0,
445 DateTimeField::Hour | DateTimeField::Minute | DateTimeField::Second => {
446 self.months = 0;
447 self.days = 0;
448 self.micros %= f.next_largest().micros_multiplier()
449 }
450 DateTimeField::Millennium
451 | DateTimeField::Century
452 | DateTimeField::Decade
453 | DateTimeField::Milliseconds
454 | DateTimeField::Microseconds => {
455 unreachable!("Cannot truncate interval by {f}");
456 }
457 }
458 }
459
460 pub fn truncate_low_fields(
469 &mut self,
470 f: DateTimeField,
471 fsec_max_precision: Option<u64>,
472 round_behavior: RoundBehavior,
473 ) -> Result<(), anyhow::Error> {
474 use DateTimeField::*;
475 match f {
476 Millennium => {
477 self.months -= self.months % (12 * 1000);
478 self.days = 0;
479 self.micros = 0;
480 }
481 Century => {
482 self.months -= self.months % (12 * 100);
483 self.days = 0;
484 self.micros = 0;
485 }
486 Decade => {
487 self.months -= self.months % (12 * 10);
488 self.days = 0;
489 self.micros = 0;
490 }
491 Year => {
492 self.months -= self.months % 12;
493 self.days = 0;
494 self.micros = 0;
495 }
496 Month => {
497 self.days = 0;
498 self.micros = 0;
499 }
500 Second => {
502 let default_precision = 6;
503 let precision = match fsec_max_precision {
504 Some(p) => p,
505 None => default_precision,
506 };
507
508 if precision > default_precision {
509 bail!(
510 "SECOND precision must be (0, 6), have SECOND({})",
511 precision
512 )
513 }
514
515 let precision = match u32::try_from(precision) {
516 Ok(p) => p,
517 Err(_) => bail!(
518 "SECOND precision must be (0, 6), have SECOND({})",
519 precision
520 ),
521 };
522 let remainder = self.micros % 10_i64.pow(6 - precision);
524 self.micros -= remainder;
525 if matches!(round_behavior, RoundBehavior::Nearest)
527 && u64::from(precision) != default_precision
528 {
529 let rounding_digit = remainder / 10_i64.pow(5 - precision);
530 let micros = if rounding_digit > 4 {
531 self.micros.checked_add(10_i64.pow(6 - precision))
532 } else if rounding_digit < -4 {
533 self.micros.checked_sub(10_i64.pow(6 - precision))
534 } else {
535 Some(self.micros)
536 };
537 let Some(micros) = micros else {
538 bail!("interval field value out of range: \"{self}\"");
539 };
540 self.micros = micros;
541 }
542 }
543 Day => {
544 self.micros = 0;
545 }
546 Hour | Minute | Milliseconds | Microseconds => {
547 self.micros -= self.micros % f.micros_multiplier();
548 }
549 }
550 Ok(())
551 }
552
553 pub fn as_time_interval(&self) -> Self {
555 Self::new(0, 0, self.micros)
556 }
557
558 pub fn is_negative(&self) -> bool {
560 self.as_microseconds() < 0
561 }
562
563 pub fn convert_date_time_unit<T>(
570 source: DateTimeField,
571 dest: DateTimeField,
572 val: T,
573 ) -> Option<T>
574 where
575 T: From<u16> + CheckedMul + std::ops::DivAssign,
576 {
577 if source < dest {
578 Self::convert_date_time_unit_increasing(source, dest, val)
579 } else if source > dest {
580 Self::convert_date_time_unit_decreasing(source, dest, val)
581 } else {
582 Some(val)
583 }
584 }
585
586 fn convert_date_time_unit_increasing<T>(
587 source: DateTimeField,
588 dest: DateTimeField,
589 val: T,
590 ) -> Option<T>
591 where
592 T: From<u16> + std::ops::DivAssign,
593 {
594 let mut cur_unit = source;
595 let mut res = val;
596 while cur_unit < dest {
597 let divisor: T = match cur_unit {
598 DateTimeField::Millennium => 1.into(),
599 DateTimeField::Century => Self::CENTURY_PER_MILLENNIUM.into(),
600 DateTimeField::Decade => Self::DECADE_PER_CENTURY.into(),
601 DateTimeField::Year => Self::YEAR_PER_DECADE.into(),
602 DateTimeField::Month => Self::MONTH_PER_YEAR.into(),
603 DateTimeField::Day => Self::DAY_PER_MONTH.into(),
604 DateTimeField::Hour => Self::HOUR_PER_DAY.into(),
605 DateTimeField::Minute => Self::MINUTE_PER_HOUR.into(),
606 DateTimeField::Second => Self::SECOND_PER_MINUTE.into(),
607 DateTimeField::Milliseconds => Self::MILLISECOND_PER_SECOND.into(),
608 DateTimeField::Microseconds => Self::MICROSECOND_PER_MILLISECOND.into(),
609 };
610 res /= divisor;
611 cur_unit = cur_unit.next_largest();
612 }
613
614 Some(res)
615 }
616
617 fn convert_date_time_unit_decreasing<T>(
618 source: DateTimeField,
619 dest: DateTimeField,
620 val: T,
621 ) -> Option<T>
622 where
623 T: From<u16> + CheckedMul,
624 {
625 let mut cur_unit = source;
626 let mut res = val;
627 while cur_unit > dest {
628 let multiplier: T = match cur_unit {
629 DateTimeField::Millennium => Self::CENTURY_PER_MILLENNIUM.into(),
630 DateTimeField::Century => Self::DECADE_PER_CENTURY.into(),
631 DateTimeField::Decade => Self::YEAR_PER_DECADE.into(),
632 DateTimeField::Year => Self::MONTH_PER_YEAR.into(),
633 DateTimeField::Month => Self::DAY_PER_MONTH.into(),
634 DateTimeField::Day => Self::HOUR_PER_DAY.into(),
635 DateTimeField::Hour => Self::MINUTE_PER_HOUR.into(),
636 DateTimeField::Minute => Self::SECOND_PER_MINUTE.into(),
637 DateTimeField::Second => Self::MILLISECOND_PER_SECOND.into(),
638 DateTimeField::Milliseconds => Self::MICROSECOND_PER_MILLISECOND.into(),
639 DateTimeField::Microseconds => 1.into(),
640 };
641 res = match res.checked_mul(&multiplier) {
642 Some(r) => r,
643 None => return None,
644 };
645 cur_unit = cur_unit.next_smallest();
646 }
647
648 Some(res)
649 }
650
651 pub fn justify_days(&self) -> Result<Self, anyhow::Error> {
653 let days_per_month = i32::from(Self::DAY_PER_MONTH);
654 let (mut months, mut days) = Self::justify_days_inner(self.months, self.days)?;
655 if months > 0 && days < 0 {
656 days += days_per_month;
657 months -= 1;
658 } else if months < 0 && days > 0 {
659 days -= days_per_month;
660 months += 1;
661 }
662
663 Ok(Self::new(months, days, self.micros))
664 }
665
666 fn justify_days_inner(months: i32, days: i32) -> Result<(i32, i32), anyhow::Error> {
667 let days_per_month = i32::from(Self::DAY_PER_MONTH);
668 let whole_month = days / days_per_month;
669 let days = days - whole_month * days_per_month;
670
671 let months = months
672 .checked_add(whole_month)
673 .ok_or_else(|| anyhow!(&*MONTH_OVERFLOW_ERROR))?;
674
675 Ok((months, days))
676 }
677
678 pub fn justify_hours(&self) -> Result<Self, anyhow::Error> {
680 let (mut days, mut micros) = Self::justify_hours_inner(self.days, self.micros)?;
681 if days > 0 && micros < 0 {
682 micros += &*USECS_PER_DAY;
683 days -= 1;
684 } else if days < 0 && micros > 0 {
685 micros -= &*USECS_PER_DAY;
686 days += 1;
687 }
688
689 Ok(Self::new(self.months, days, micros))
690 }
691
692 fn justify_hours_inner(days: i32, micros: i64) -> Result<(i32, i64), anyhow::Error> {
693 let days = i32::try_from(micros / &*USECS_PER_DAY)
694 .ok()
695 .and_then(|d| days.checked_add(d))
696 .ok_or_else(|| anyhow!(&*DAY_OVERFLOW_ERROR))?;
697 let micros = micros % &*USECS_PER_DAY;
698
699 Ok((days, micros))
700 }
701
702 pub fn justify_interval(&self) -> Result<Self, anyhow::Error> {
706 let days_per_month = i32::from(Self::DAY_PER_MONTH);
707 let mut months = self.months;
708 let mut days = self.days;
709 let micros = self.micros;
710 if (days > 0 && micros > 0) || (days < 0 && micros < 0) {
713 let (m, d) = Self::justify_days_inner(self.months, self.days)?;
714 months = m;
715 days = d;
716 }
717 let (days, mut micros) = Self::justify_hours_inner(days, micros)?;
718 let (mut months, mut days) = Self::justify_days_inner(months, days)?;
719
720 if months > 0 && (days < 0 || (days == 0 && micros < 0)) {
721 days += days_per_month;
722 months -= 1;
723 } else if months < 0 && (days > 0 || (days == 0 && micros > 0)) {
724 days -= days_per_month;
725 months += 1;
726 }
727
728 if days > 0 && micros < 0 {
729 micros += &*USECS_PER_DAY;
730 days -= 1;
731 } else if days < 0 && micros > 0 {
732 micros -= &*USECS_PER_DAY;
733 days += 1;
734 }
735
736 Ok(Self::new(months, days, micros))
737 }
738}
739
740impl fmt::Display for Interval {
748 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
749 let neg_months = self.months < 0;
750 let years = (self.months / 12).abs();
751 let months = (self.months % 12).abs();
752
753 let neg_days = self.days < 0;
754 let days = i64::from(self.days).abs();
755
756 let mut nanos = self.nanoseconds().abs();
757 let mut secs = (self.micros / 1_000_000).abs();
758
759 let sec_per_hr = 60 * 60;
760 let hours = secs / sec_per_hr;
761 secs %= sec_per_hr;
762
763 let sec_per_min = 60;
764 let minutes = secs / sec_per_min;
765 secs %= sec_per_min;
766
767 if years > 0 {
768 if neg_months {
769 f.write_char('-')?;
770 }
771 write!(f, "{} year", years)?;
772 if years > 1 || neg_months {
773 f.write_char('s')?;
774 }
775 }
776
777 if months > 0 {
778 if years != 0 {
779 f.write_char(' ')?;
780 }
781 if neg_months {
782 f.write_char('-')?;
783 }
784 write!(f, "{} month", months)?;
785 if months > 1 || neg_months {
786 f.write_char('s')?;
787 }
788 }
789
790 if days != 0 {
791 if years > 0 || months > 0 {
792 f.write_char(' ')?;
793 }
794 if neg_months && !neg_days {
795 f.write_char('+')?;
796 }
797 write!(f, "{} day", self.days)?;
798 if self.days != 1 {
799 f.write_char('s')?;
800 }
801 }
802
803 let non_zero_hmsn = hours > 0 || minutes > 0 || secs > 0 || nanos > 0;
804
805 if (years == 0 && months == 0 && days == 0) || non_zero_hmsn {
806 if years > 0 || months > 0 || days > 0 {
807 f.write_char(' ')?;
808 }
809 if self.micros < 0 && non_zero_hmsn {
810 f.write_char('-')?;
811 } else if neg_days || (days == 0 && neg_months) {
812 f.write_char('+')?;
813 }
814 write!(f, "{:02}:{:02}:{:02}", hours, minutes, secs)?;
815 if nanos > 0 {
816 let mut width = 9;
817 while nanos % 10 == 0 {
818 width -= 1;
819 nanos /= 10;
820 }
821 write!(f, ".{:0width$}", nanos, width = width)?;
822 }
823 }
824
825 Ok(())
826 }
827}
828
829impl Arbitrary for Interval {
830 type Strategy = BoxedStrategy<Self>;
831 type Parameters = ();
832
833 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
834 (
835 any::<i32>(),
836 any::<i32>(),
837 ((((i64::from(i32::MIN) * 60) - 59) * 60) * 1_000_000 - 59_999_999
838 ..(((i64::from(i32::MAX) * 60) + 59) * 60) * 1_000_000 + 59_999_999),
839 )
840 .prop_map(|(months, days, micros)| Interval {
841 months,
842 days,
843 micros,
844 })
845 .boxed()
846 }
847}
848
849#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
853pub struct PackedInterval([u8; Self::SIZE]);
854
855#[allow(clippy::as_conversions)]
859impl FixedSizeCodec<Interval> for PackedInterval {
860 const SIZE: usize = 16;
861
862 fn as_bytes(&self) -> &[u8] {
863 &self.0[..]
864 }
865
866 fn from_bytes(slice: &[u8]) -> Result<Self, String> {
867 let buf: [u8; Self::SIZE] = slice.try_into().map_err(|_| {
868 format!(
869 "size for PackedInterval is {} bytes, got {}",
870 Self::SIZE,
871 slice.len()
872 )
873 })?;
874 Ok(PackedInterval(buf))
875 }
876
877 #[inline]
878 fn from_value(value: Interval) -> Self {
879 let mut buf = [0u8; 16];
880
881 let months = (value.months as u32) ^ (0x8000_0000u32);
884 let days = (value.days as u32) ^ (0x8000_0000u32);
885 let micros = (value.micros as u64) ^ (0x8000_0000_0000_0000u64);
886
887 buf[..4].copy_from_slice(&months.to_be_bytes());
888 buf[4..8].copy_from_slice(&days.to_be_bytes());
889 buf[8..].copy_from_slice(µs.to_be_bytes());
890
891 PackedInterval(buf)
892 }
893
894 #[inline]
895 fn into_value(self) -> Interval {
896 let mut months = [0; 4];
899 months.copy_from_slice(&self.0[..4]);
900 let months = u32::from_be_bytes(months) ^ 0x8000_0000u32;
901
902 let mut days = [0; 4];
903 days.copy_from_slice(&self.0[4..8]);
904 let days = u32::from_be_bytes(days) ^ 0x8000_0000u32;
905
906 let mut micros = [0; 8];
907 micros.copy_from_slice(&self.0[8..]);
908 let micros = u64::from_be_bytes(micros) ^ 0x8000_0000_0000_0000u64;
909
910 Interval {
911 months: months as i32,
912 days: days as i32,
913 micros: micros as i64,
914 }
915 }
916}
917
918#[cfg(test)]
919mod test {
920 use super::*;
921 use proptest::prelude::*;
922
923 #[mz_ore::test]
924 fn interval_fmt() {
925 fn mon(mon: i32) -> String {
926 Interval {
927 months: mon,
928 ..Default::default()
929 }
930 .to_string()
931 }
932
933 assert_eq!(mon(1), "1 month");
934 assert_eq!(mon(12), "1 year");
935 assert_eq!(mon(13), "1 year 1 month");
936 assert_eq!(mon(24), "2 years");
937 assert_eq!(mon(25), "2 years 1 month");
938 assert_eq!(mon(26), "2 years 2 months");
939
940 fn dur(days: i32, micros: i64) -> String {
941 Interval::new(0, days, micros).to_string()
942 }
943 assert_eq!(&dur(2, 0), "2 days");
944 assert_eq!(&dur(2, 3 * 60 * 60 * 1_000_000), "2 days 03:00:00");
945 assert_eq!(
946 &dur(
947 2,
948 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
949 ),
950 "2 days 03:45:06"
951 );
952 assert_eq!(
953 &dur(2, (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000)),
954 "2 days 03:45:00"
955 );
956 assert_eq!(&dur(2, 6 * 1_000_000), "2 days 00:00:06");
957 assert_eq!(
958 &dur(2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
959 "2 days 00:45:06"
960 );
961 assert_eq!(
962 &dur(2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
963 "2 days 03:00:06"
964 );
965 assert_eq!(
966 &dur(
967 0,
968 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
969 ),
970 "03:45:06"
971 );
972 assert_eq!(
973 &dur(0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
974 "03:00:06"
975 );
976 assert_eq!(&dur(0, 3 * 60 * 60 * 1_000_000), "03:00:00");
977 assert_eq!(&dur(0, (45 * 60 * 1_000_000) + (6 * 1_000_000)), "00:45:06");
978 assert_eq!(&dur(0, 45 * 60 * 1_000_000), "00:45:00");
979 assert_eq!(&dur(0, 6 * 1_000_000), "00:00:06");
980
981 assert_eq!(&dur(-2, -6 * 1_000_000), "-2 days -00:00:06");
982 assert_eq!(
983 &dur(-2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
984 "-2 days -00:45:06"
985 );
986 assert_eq!(
987 &dur(-2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
988 "-2 days -03:00:06"
989 );
990 assert_eq!(
991 &dur(
992 0,
993 (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
994 ),
995 "-03:45:06"
996 );
997 assert_eq!(
998 &dur(0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
999 "-03:00:06"
1000 );
1001 assert_eq!(&dur(0, -3 * 60 * 60 * 1_000_000), "-03:00:00");
1002 assert_eq!(
1003 &dur(0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1004 "-00:45:06"
1005 );
1006 assert_eq!(&dur(0, -45 * 60 * 1_000_000), "-00:45:00");
1007 assert_eq!(&dur(0, -6 * 1_000_000), "-00:00:06");
1008
1009 fn mon_dur(mon: i32, days: i32, micros: i64) -> String {
1010 Interval::new(mon, days, micros).to_string()
1011 }
1012 assert_eq!(&mon_dur(1, 2, 6 * 1_000_000), "1 month 2 days 00:00:06");
1013 assert_eq!(
1014 &mon_dur(1, 2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1015 "1 month 2 days 00:45:06"
1016 );
1017 assert_eq!(
1018 &mon_dur(1, 2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1019 "1 month 2 days 03:00:06"
1020 );
1021 assert_eq!(
1022 &mon_dur(
1023 26,
1024 0,
1025 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
1026 ),
1027 "2 years 2 months 03:45:06"
1028 );
1029 assert_eq!(
1030 &mon_dur(26, 0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1031 "2 years 2 months 03:00:06"
1032 );
1033 assert_eq!(
1034 &mon_dur(26, 0, 3 * 60 * 60 * 1_000_000),
1035 "2 years 2 months 03:00:00"
1036 );
1037 assert_eq!(
1038 &mon_dur(26, 0, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1039 "2 years 2 months 00:45:06"
1040 );
1041 assert_eq!(
1042 &mon_dur(26, 0, 45 * 60 * 1_000_000),
1043 "2 years 2 months 00:45:00"
1044 );
1045 assert_eq!(&mon_dur(26, 0, 6 * 1_000_000), "2 years 2 months 00:00:06");
1046
1047 assert_eq!(
1048 &mon_dur(26, -2, -6 * 1_000_000),
1049 "2 years 2 months -2 days -00:00:06"
1050 );
1051 assert_eq!(
1052 &mon_dur(26, -2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1053 "2 years 2 months -2 days -00:45:06"
1054 );
1055 assert_eq!(
1056 &mon_dur(26, -2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1057 "2 years 2 months -2 days -03:00:06"
1058 );
1059 assert_eq!(
1060 &mon_dur(
1061 26,
1062 0,
1063 (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
1064 ),
1065 "2 years 2 months -03:45:06"
1066 );
1067 assert_eq!(
1068 &mon_dur(26, 0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1069 "2 years 2 months -03:00:06"
1070 );
1071 assert_eq!(
1072 &mon_dur(26, 0, -3 * 60 * 60 * 1_000_000),
1073 "2 years 2 months -03:00:00"
1074 );
1075 assert_eq!(
1076 &mon_dur(26, 0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1077 "2 years 2 months -00:45:06"
1078 );
1079 assert_eq!(
1080 &mon_dur(26, 0, -45 * 60 * 1_000_000),
1081 "2 years 2 months -00:45:00"
1082 );
1083 assert_eq!(
1084 &mon_dur(26, 0, -6 * 1_000_000),
1085 "2 years 2 months -00:00:06"
1086 );
1087
1088 assert_eq!(&mon_dur(-1, 2, 6 * 1_000_000), "-1 months +2 days 00:00:06");
1089 assert_eq!(
1090 &mon_dur(-1, 2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1091 "-1 months +2 days 00:45:06"
1092 );
1093 assert_eq!(
1094 &mon_dur(-1, 2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1095 "-1 months +2 days 03:00:06"
1096 );
1097 assert_eq!(
1098 &mon_dur(
1099 -26,
1100 0,
1101 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
1102 ),
1103 "-2 years -2 months +03:45:06"
1104 );
1105 assert_eq!(
1106 &mon_dur(-26, 0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1107 "-2 years -2 months +03:00:06"
1108 );
1109 assert_eq!(
1110 &mon_dur(-26, 0, 3 * 60 * 60 * 1_000_000),
1111 "-2 years -2 months +03:00:00"
1112 );
1113 assert_eq!(
1114 &mon_dur(-26, 0, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1115 "-2 years -2 months +00:45:06"
1116 );
1117 assert_eq!(
1118 &mon_dur(-26, 0, 45 * 60 * 1_000_000),
1119 "-2 years -2 months +00:45:00"
1120 );
1121 assert_eq!(
1122 &mon_dur(-26, 0, 6 * 1_000_000),
1123 "-2 years -2 months +00:00:06"
1124 );
1125
1126 assert_eq!(
1127 &mon_dur(-26, -2, -6 * 1_000_000),
1128 "-2 years -2 months -2 days -00:00:06"
1129 );
1130 assert_eq!(
1131 &mon_dur(-26, -2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1132 "-2 years -2 months -2 days -00:45:06"
1133 );
1134 assert_eq!(
1135 &mon_dur(-26, -2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1136 "-2 years -2 months -2 days -03:00:06"
1137 );
1138 assert_eq!(
1139 &mon_dur(
1140 -26,
1141 0,
1142 (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
1143 ),
1144 "-2 years -2 months -03:45:06"
1145 );
1146 assert_eq!(
1147 &mon_dur(-26, 0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1148 "-2 years -2 months -03:00:06"
1149 );
1150 assert_eq!(
1151 &mon_dur(-26, 0, -3 * 60 * 60 * 1_000_000),
1152 "-2 years -2 months -03:00:00"
1153 );
1154 assert_eq!(
1155 &mon_dur(-26, 0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1156 "-2 years -2 months -00:45:06"
1157 );
1158 assert_eq!(
1159 &mon_dur(-26, 0, -45 * 60 * 1_000_000),
1160 "-2 years -2 months -00:45:00"
1161 );
1162 assert_eq!(
1163 &mon_dur(-26, 0, -6 * 1_000_000),
1164 "-2 years -2 months -00:00:06"
1165 );
1166 }
1167
1168 #[mz_ore::test]
1169 fn test_interval_value_truncate_low_fields() {
1170 use DateTimeField::*;
1171
1172 let mut test_cases = [
1173 (
1174 Year,
1175 None,
1176 (
1177 321,
1178 7,
1179 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1180 ),
1181 (26 * 12, 0, 0),
1182 ),
1183 (
1184 Month,
1185 None,
1186 (
1187 321,
1188 7,
1189 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1190 ),
1191 (321, 0, 0),
1192 ),
1193 (
1194 Day,
1195 None,
1196 (
1197 321,
1198 7,
1199 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1200 ),
1201 (321, 7, 0),
1202 ),
1203 (
1204 Hour,
1205 None,
1206 (
1207 321,
1208 7,
1209 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1210 ),
1211 (321, 7, 13 * 60 * 60 * 1_000_000),
1212 ),
1213 (
1214 Minute,
1215 None,
1216 (
1217 321,
1218 7,
1219 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1220 ),
1221 (321, 7, (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000)),
1222 ),
1223 (
1224 Second,
1225 None,
1226 (
1227 321,
1228 7,
1229 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1230 ),
1231 (
1232 321,
1233 7,
1234 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1235 ),
1236 ),
1237 (
1238 Second,
1239 Some(1),
1240 (
1241 321,
1242 7,
1243 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1244 ),
1245 (
1246 321,
1247 7,
1248 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 300_000,
1249 ),
1250 ),
1251 (
1252 Second,
1253 Some(0),
1254 (
1255 321,
1256 7,
1257 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1258 ),
1259 (
1260 321,
1261 7,
1262 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1263 ),
1264 ),
1265 ];
1266
1267 for test in test_cases.iter_mut() {
1268 let mut i = Interval::new((test.2).0, (test.2).1, (test.2).2);
1269 let j = Interval::new((test.3).0, (test.3).1, (test.3).2);
1270
1271 i.truncate_low_fields(test.0, test.1, RoundBehavior::Nearest)
1272 .unwrap();
1273
1274 if i != j {
1275 panic!(
1276 "test_interval_value_truncate_low_fields failed on {} \n actual: {:?} \n expected: {:?}",
1277 test.0, i, j
1278 );
1279 }
1280 }
1281 }
1282
1283 #[mz_ore::test]
1284 fn test_interval_value_truncate_high_fields() {
1285 use DateTimeField::*;
1286
1287 let mut test_cases = [
1289 (
1290 Year,
1291 (
1292 321,
1293 7,
1294 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1295 ),
1296 (
1297 321,
1298 7,
1299 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1300 ),
1301 ),
1302 (
1303 Month,
1304 (
1305 321,
1306 7,
1307 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1308 ),
1309 (
1310 9,
1311 7,
1312 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1313 ),
1314 ),
1315 (
1316 Day,
1317 (
1318 321,
1319 7,
1320 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1321 ),
1322 (
1323 0,
1324 7,
1325 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1326 ),
1327 ),
1328 (
1329 Hour,
1330 (
1331 321,
1332 7,
1333 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1334 ),
1335 (
1336 0,
1337 0,
1338 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1339 ),
1340 ),
1341 (
1342 Minute,
1343 (
1344 321,
1345 7,
1346 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1347 ),
1348 (0, 0, (45 * 60 * 1_000_000) + (21 * 1_000_000)),
1349 ),
1350 (
1351 Second,
1352 (
1353 321,
1354 7,
1355 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1356 ),
1357 (0, 0, 21 * 1_000_000),
1358 ),
1359 ];
1360
1361 for test in test_cases.iter_mut() {
1362 let mut i = Interval::new((test.1).0, (test.1).1, (test.1).2);
1363 let j = Interval::new((test.2).0, (test.2).1, (test.2).2);
1364
1365 i.truncate_high_fields(test.0);
1366
1367 if i != j {
1368 panic!(
1369 "test_interval_value_truncate_high_fields failed on {} \n actual: {:?} \n expected: {:?}",
1370 test.0, i, j
1371 );
1372 }
1373 }
1374 }
1375
1376 #[mz_ore::test]
1377 fn test_convert_date_time_unit() {
1378 assert_eq!(
1379 Some(1_123_200_000_000),
1380 Interval::convert_date_time_unit(
1381 DateTimeField::Day,
1382 DateTimeField::Microseconds,
1383 13i64
1384 )
1385 );
1386
1387 assert_eq!(
1388 Some(3_558_399_705),
1389 Interval::convert_date_time_unit(
1390 DateTimeField::Milliseconds,
1391 DateTimeField::Month,
1392 i64::MAX
1393 )
1394 );
1395
1396 assert_eq!(
1397 None,
1398 Interval::convert_date_time_unit(
1399 DateTimeField::Minute,
1400 DateTimeField::Second,
1401 i32::MAX
1402 )
1403 );
1404
1405 assert_eq!(
1406 Some(1),
1407 Interval::convert_date_time_unit(DateTimeField::Day, DateTimeField::Year, 365)
1408 );
1409
1410 assert_eq!(
1412 Some(360),
1413 Interval::convert_date_time_unit(DateTimeField::Year, DateTimeField::Day, 1)
1414 );
1415 }
1416
1417 #[mz_ore::test]
1418 fn proptest_packed_interval_roundtrips() {
1419 fn roundtrip_interval(og: Interval) {
1420 let packed = PackedInterval::from_value(og);
1421 let rnd = packed.into_value();
1422
1423 assert_eq!(og, rnd);
1424 }
1425
1426 proptest!(|(interval in any::<Interval>())| {
1427 roundtrip_interval(interval);
1428 });
1429 }
1430
1431 #[mz_ore::test]
1432 fn proptest_packed_interval_sorts() {
1433 fn sort_intervals(mut og: Vec<Interval>) {
1434 let mut packed: Vec<_> = og.iter().copied().map(PackedInterval::from_value).collect();
1435
1436 og.sort();
1437 packed.sort();
1438
1439 let rnd: Vec<_> = packed.into_iter().map(PackedInterval::into_value).collect();
1440
1441 assert_eq!(og, rnd);
1442 }
1443
1444 proptest!(|(interval in any::<Vec<Interval>>())| {
1445 sort_intervals(interval);
1446 });
1447 }
1448}