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 checked_add(&self, other: &Self) -> Option<Self> {
168 let months = match self.months.checked_add(other.months) {
169 Some(m) => m,
170 None => return None,
171 };
172 let days = match self.days.checked_add(other.days) {
173 Some(d) => d,
174 None => return None,
175 };
176 let micros = match self.micros.checked_add(other.micros) {
177 Some(us) => us,
178 None => return None,
179 };
180
181 Some(Self::new(months, days, micros))
182 }
183
184 pub fn checked_mul(&self, other: f64) -> Option<Self> {
185 self.checked_op(other, |f1, f2| f1 * f2)
186 }
187
188 pub fn checked_div(&self, other: f64) -> Option<Self> {
189 self.checked_op(other, |f1, f2| f1 / f2)
190 }
191
192 #[allow(clippy::as_conversions)]
195 fn checked_op<F1>(&self, other: f64, op: F1) -> Option<Self>
196 where
197 F1: Fn(f64, f64) -> f64,
198 {
199 let months = op(f64::from(self.months), other);
200 if months.is_nan()
201 || months.is_infinite()
202 || months < i32::MIN.into()
203 || months > i32::MAX.into()
204 {
205 return None;
206 }
207
208 let days =
209 op(f64::from(self.days), other) + months.fract() * f64::from(Self::DAY_PER_MONTH);
210 if days.is_nan() || days.is_infinite() || days < i32::MIN.into() || days > i32::MAX.into() {
211 return None;
212 }
213
214 let micros = op(self.micros as f64, other)
215 + days.fract()
216 * f64::from(Self::HOUR_PER_DAY)
217 * f64::from(Self::MINUTE_PER_HOUR)
218 * f64::from(Self::SECOND_PER_MINUTE)
219 * f64::from(Self::MILLISECOND_PER_SECOND)
220 * f64::from(Self::MICROSECOND_PER_MILLISECOND);
221
222 if micros.is_nan()
223 || micros.is_infinite()
224 || Numeric::from(micros) < Numeric::from(i64::MIN)
225 || Numeric::from(micros) > Numeric::from(i64::MAX)
226 {
227 return None;
228 }
229
230 Some(Self::new(months as i32, days as i32, micros as i64))
231 }
232
233 pub fn millennia(&self) -> i32 {
238 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Millennium, self.months)
239 .unwrap()
240 }
241
242 pub fn centuries(&self) -> i32 {
247 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Century, self.months)
248 .unwrap()
249 }
250
251 pub fn decades(&self) -> i32 {
256 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Decade, self.months)
257 .unwrap()
258 }
259
260 pub fn years(&self) -> i32 {
265 Self::convert_date_time_unit(DateTimeField::Month, DateTimeField::Year, self.months)
266 .unwrap()
267 }
268
269 pub fn quarters(&self) -> i32 {
275 self.months() / 3 + 1
276 }
277
278 pub fn months(&self) -> i32 {
284 self.months % i32::from(Self::MONTH_PER_YEAR)
285 }
286
287 pub fn days(&self) -> i64 {
293 self.days.into()
294 }
295
296 pub fn hours(&self) -> i64 {
302 Self::convert_date_time_unit(
303 DateTimeField::Microseconds,
304 DateTimeField::Hour,
305 self.micros,
306 )
307 .unwrap()
308 % i64::from(Self::HOUR_PER_DAY)
309 }
310
311 pub fn minutes(&self) -> i64 {
317 Self::convert_date_time_unit(
318 DateTimeField::Microseconds,
319 DateTimeField::Minute,
320 self.micros,
321 )
322 .unwrap()
323 % i64::from(Self::MINUTE_PER_HOUR)
324 }
325
326 pub fn seconds<T>(&self) -> T
331 where
332 T: DecimalLike,
333 {
334 T::lossy_from(self.micros % 60_000_000) / T::from(1e6)
335 }
336
337 pub fn milliseconds<T>(&self) -> T
342 where
343 T: DecimalLike,
344 {
345 T::lossy_from(self.micros % 60_000_000) / T::from(1e3)
346 }
347
348 pub fn microseconds<T>(&self) -> T
353 where
354 T: DecimalLike,
355 {
356 T::lossy_from(self.micros % 60_000_000)
357 }
358
359 pub fn nanoseconds(&self) -> i32 {
361 (self.micros % 1_000_000 * 1_000).try_into().unwrap()
362 }
363
364 pub fn as_epoch_seconds<T>(&self) -> T
368 where
369 T: DecimalLike,
370 {
371 let days = T::from(self.years()) * T::from(Self::EPOCH_DAYS_PER_YEAR)
372 + T::from(self.months()) * T::from(Self::DAY_PER_MONTH)
373 + T::from(self.days);
374 let seconds = days
375 * T::from(Self::HOUR_PER_DAY)
376 * T::from(Self::MINUTE_PER_HOUR)
377 * T::from(Self::SECOND_PER_MINUTE);
378
379 seconds
380 + T::lossy_from(self.micros)
381 / (T::from(Self::MICROSECOND_PER_MILLISECOND)
382 * T::from(Self::MILLISECOND_PER_SECOND))
383 }
384
385 pub fn as_microseconds(&self) -> i128 {
387 Self::convert_date_time_unit(
390 DateTimeField::Month,
391 DateTimeField::Microseconds,
392 i128::from(self.months),
393 ).unwrap() +
394 Self::convert_date_time_unit(
397 DateTimeField::Day,
398 DateTimeField::Microseconds,
399 i128::from(self.days),
400 ).unwrap() +
401 i128::from(self.micros)
402 }
403
404 pub fn as_milliseconds(&self) -> i128 {
406 self.as_microseconds() / 1000
407 }
408
409 pub fn duration_as_chrono(&self) -> chrono::Duration {
411 use chrono::Duration;
412 Duration::try_days(self.days.into()).unwrap() + Duration::microseconds(self.micros)
413 }
414
415 pub fn duration(&self) -> Result<Duration, anyhow::Error> {
416 if self.months != 0 {
417 bail!("cannot convert interval with months to duration");
418 }
419 if self.is_negative() {
420 bail!("cannot convert negative interval to duration");
421 }
422 let micros: u64 = u64::try_from(self.as_microseconds())?;
423 Ok(Duration::from_micros(micros))
424 }
425
426 pub fn truncate_high_fields(&mut self, f: DateTimeField) {
428 match f {
429 DateTimeField::Year => {}
430 DateTimeField::Month => self.months %= 12,
431 DateTimeField::Day => self.months = 0,
432 DateTimeField::Hour | DateTimeField::Minute | DateTimeField::Second => {
433 self.months = 0;
434 self.days = 0;
435 self.micros %= f.next_largest().micros_multiplier()
436 }
437 DateTimeField::Millennium
438 | DateTimeField::Century
439 | DateTimeField::Decade
440 | DateTimeField::Milliseconds
441 | DateTimeField::Microseconds => {
442 unreachable!("Cannot truncate interval by {f}");
443 }
444 }
445 }
446
447 pub fn truncate_low_fields(
456 &mut self,
457 f: DateTimeField,
458 fsec_max_precision: Option<u64>,
459 round_behavior: RoundBehavior,
460 ) -> Result<(), anyhow::Error> {
461 use DateTimeField::*;
462 match f {
463 Millennium => {
464 self.months -= self.months % (12 * 1000);
465 self.days = 0;
466 self.micros = 0;
467 }
468 Century => {
469 self.months -= self.months % (12 * 100);
470 self.days = 0;
471 self.micros = 0;
472 }
473 Decade => {
474 self.months -= self.months % (12 * 10);
475 self.days = 0;
476 self.micros = 0;
477 }
478 Year => {
479 self.months -= self.months % 12;
480 self.days = 0;
481 self.micros = 0;
482 }
483 Month => {
484 self.days = 0;
485 self.micros = 0;
486 }
487 Second => {
489 let default_precision = 6;
490 let precision = match fsec_max_precision {
491 Some(p) => p,
492 None => default_precision,
493 };
494
495 if precision > default_precision {
496 bail!(
497 "SECOND precision must be (0, 6), have SECOND({})",
498 precision
499 )
500 }
501
502 let precision = match u32::try_from(precision) {
503 Ok(p) => p,
504 Err(_) => bail!(
505 "SECOND precision must be (0, 6), have SECOND({})",
506 precision
507 ),
508 };
509 let remainder = self.micros % 10_i64.pow(6 - precision);
511 self.micros -= remainder;
512 if matches!(round_behavior, RoundBehavior::Nearest)
514 && u64::from(precision) != default_precision
515 {
516 let rounding_digit = remainder / 10_i64.pow(5 - precision);
517 let micros = if rounding_digit > 4 {
518 self.micros.checked_add(10_i64.pow(6 - precision))
519 } else if rounding_digit < -4 {
520 self.micros.checked_sub(10_i64.pow(6 - precision))
521 } else {
522 Some(self.micros)
523 };
524 let Some(micros) = micros else {
525 bail!("interval field value out of range: \"{self}\"");
526 };
527 self.micros = micros;
528 }
529 }
530 Day => {
531 self.micros = 0;
532 }
533 Hour | Minute | Milliseconds | Microseconds => {
534 self.micros -= self.micros % f.micros_multiplier();
535 }
536 }
537 Ok(())
538 }
539
540 pub fn as_time_interval(&self) -> Self {
542 Self::new(0, 0, self.micros)
543 }
544
545 pub fn is_negative(&self) -> bool {
547 self.as_microseconds() < 0
548 }
549
550 pub fn convert_date_time_unit<T>(
557 source: DateTimeField,
558 dest: DateTimeField,
559 val: T,
560 ) -> Option<T>
561 where
562 T: From<u16> + CheckedMul + std::ops::DivAssign,
563 {
564 if source < dest {
565 Self::convert_date_time_unit_increasing(source, dest, val)
566 } else if source > dest {
567 Self::convert_date_time_unit_decreasing(source, dest, val)
568 } else {
569 Some(val)
570 }
571 }
572
573 fn convert_date_time_unit_increasing<T>(
574 source: DateTimeField,
575 dest: DateTimeField,
576 val: T,
577 ) -> Option<T>
578 where
579 T: From<u16> + std::ops::DivAssign,
580 {
581 let mut cur_unit = source;
582 let mut res = val;
583 while cur_unit < dest {
584 let divisor: T = match cur_unit {
585 DateTimeField::Millennium => 1.into(),
586 DateTimeField::Century => Self::CENTURY_PER_MILLENNIUM.into(),
587 DateTimeField::Decade => Self::DECADE_PER_CENTURY.into(),
588 DateTimeField::Year => Self::YEAR_PER_DECADE.into(),
589 DateTimeField::Month => Self::MONTH_PER_YEAR.into(),
590 DateTimeField::Day => Self::DAY_PER_MONTH.into(),
591 DateTimeField::Hour => Self::HOUR_PER_DAY.into(),
592 DateTimeField::Minute => Self::MINUTE_PER_HOUR.into(),
593 DateTimeField::Second => Self::SECOND_PER_MINUTE.into(),
594 DateTimeField::Milliseconds => Self::MILLISECOND_PER_SECOND.into(),
595 DateTimeField::Microseconds => Self::MICROSECOND_PER_MILLISECOND.into(),
596 };
597 res /= divisor;
598 cur_unit = cur_unit.next_largest();
599 }
600
601 Some(res)
602 }
603
604 fn convert_date_time_unit_decreasing<T>(
605 source: DateTimeField,
606 dest: DateTimeField,
607 val: T,
608 ) -> Option<T>
609 where
610 T: From<u16> + CheckedMul,
611 {
612 let mut cur_unit = source;
613 let mut res = val;
614 while cur_unit > dest {
615 let multiplier: T = match cur_unit {
616 DateTimeField::Millennium => Self::CENTURY_PER_MILLENNIUM.into(),
617 DateTimeField::Century => Self::DECADE_PER_CENTURY.into(),
618 DateTimeField::Decade => Self::YEAR_PER_DECADE.into(),
619 DateTimeField::Year => Self::MONTH_PER_YEAR.into(),
620 DateTimeField::Month => Self::DAY_PER_MONTH.into(),
621 DateTimeField::Day => Self::HOUR_PER_DAY.into(),
622 DateTimeField::Hour => Self::MINUTE_PER_HOUR.into(),
623 DateTimeField::Minute => Self::SECOND_PER_MINUTE.into(),
624 DateTimeField::Second => Self::MILLISECOND_PER_SECOND.into(),
625 DateTimeField::Milliseconds => Self::MICROSECOND_PER_MILLISECOND.into(),
626 DateTimeField::Microseconds => 1.into(),
627 };
628 res = match res.checked_mul(&multiplier) {
629 Some(r) => r,
630 None => return None,
631 };
632 cur_unit = cur_unit.next_smallest();
633 }
634
635 Some(res)
636 }
637
638 pub fn justify_days(&self) -> Result<Self, anyhow::Error> {
640 let days_per_month = i32::from(Self::DAY_PER_MONTH);
641 let (mut months, mut days) = Self::justify_days_inner(self.months, self.days)?;
642 if months > 0 && days < 0 {
643 days += days_per_month;
644 months -= 1;
645 } else if months < 0 && days > 0 {
646 days -= days_per_month;
647 months += 1;
648 }
649
650 Ok(Self::new(months, days, self.micros))
651 }
652
653 fn justify_days_inner(months: i32, days: i32) -> Result<(i32, i32), anyhow::Error> {
654 let days_per_month = i32::from(Self::DAY_PER_MONTH);
655 let whole_month = days / days_per_month;
656 let days = days - whole_month * days_per_month;
657
658 let months = months
659 .checked_add(whole_month)
660 .ok_or_else(|| anyhow!(&*MONTH_OVERFLOW_ERROR))?;
661
662 Ok((months, days))
663 }
664
665 pub fn justify_hours(&self) -> Result<Self, anyhow::Error> {
667 let (mut days, mut micros) = Self::justify_hours_inner(self.days, self.micros)?;
668 if days > 0 && micros < 0 {
669 micros += &*USECS_PER_DAY;
670 days -= 1;
671 } else if days < 0 && micros > 0 {
672 micros -= &*USECS_PER_DAY;
673 days += 1;
674 }
675
676 Ok(Self::new(self.months, days, micros))
677 }
678
679 fn justify_hours_inner(days: i32, micros: i64) -> Result<(i32, i64), anyhow::Error> {
680 let days = i32::try_from(micros / &*USECS_PER_DAY)
681 .ok()
682 .and_then(|d| days.checked_add(d))
683 .ok_or_else(|| anyhow!(&*DAY_OVERFLOW_ERROR))?;
684 let micros = micros % &*USECS_PER_DAY;
685
686 Ok((days, micros))
687 }
688
689 pub fn justify_interval(&self) -> Result<Self, anyhow::Error> {
693 let days_per_month = i32::from(Self::DAY_PER_MONTH);
694 let mut months = self.months;
695 let mut days = self.days;
696 let micros = self.micros;
697 if (days > 0 && micros > 0) || (days < 0 && micros < 0) {
700 let (m, d) = Self::justify_days_inner(self.months, self.days)?;
701 months = m;
702 days = d;
703 }
704 let (days, mut micros) = Self::justify_hours_inner(days, micros)?;
705 let (mut months, mut days) = Self::justify_days_inner(months, days)?;
706
707 if months > 0 && (days < 0 || (days == 0 && micros < 0)) {
708 days += days_per_month;
709 months -= 1;
710 } else if months < 0 && (days > 0 || (days == 0 && micros > 0)) {
711 days -= days_per_month;
712 months += 1;
713 }
714
715 if days > 0 && micros < 0 {
716 micros += &*USECS_PER_DAY;
717 days -= 1;
718 } else if days < 0 && micros > 0 {
719 micros -= &*USECS_PER_DAY;
720 days += 1;
721 }
722
723 Ok(Self::new(months, days, micros))
724 }
725}
726
727impl fmt::Display for Interval {
735 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
736 let neg_months = self.months < 0;
737 let years = (self.months / 12).abs();
738 let months = (self.months % 12).abs();
739
740 let neg_days = self.days < 0;
741 let days = i64::from(self.days).abs();
742
743 let mut nanos = self.nanoseconds().abs();
744 let mut secs = (self.micros / 1_000_000).abs();
745
746 let sec_per_hr = 60 * 60;
747 let hours = secs / sec_per_hr;
748 secs %= sec_per_hr;
749
750 let sec_per_min = 60;
751 let minutes = secs / sec_per_min;
752 secs %= sec_per_min;
753
754 if years > 0 {
755 if neg_months {
756 f.write_char('-')?;
757 }
758 write!(f, "{} year", years)?;
759 if years > 1 || neg_months {
760 f.write_char('s')?;
761 }
762 }
763
764 if months > 0 {
765 if years != 0 {
766 f.write_char(' ')?;
767 }
768 if neg_months {
769 f.write_char('-')?;
770 }
771 write!(f, "{} month", months)?;
772 if months > 1 || neg_months {
773 f.write_char('s')?;
774 }
775 }
776
777 if days != 0 {
778 if years > 0 || months > 0 {
779 f.write_char(' ')?;
780 }
781 if neg_months && !neg_days {
782 f.write_char('+')?;
783 }
784 write!(f, "{} day", self.days)?;
785 if self.days != 1 {
786 f.write_char('s')?;
787 }
788 }
789
790 let non_zero_hmsn = hours > 0 || minutes > 0 || secs > 0 || nanos > 0;
791
792 if (years == 0 && months == 0 && days == 0) || non_zero_hmsn {
793 if years > 0 || months > 0 || days > 0 {
794 f.write_char(' ')?;
795 }
796 if self.micros < 0 && non_zero_hmsn {
797 f.write_char('-')?;
798 } else if neg_days || (days == 0 && neg_months) {
799 f.write_char('+')?;
800 }
801 write!(f, "{:02}:{:02}:{:02}", hours, minutes, secs)?;
802 if nanos > 0 {
803 let mut width = 9;
804 while nanos % 10 == 0 {
805 width -= 1;
806 nanos /= 10;
807 }
808 write!(f, ".{:0width$}", nanos, width = width)?;
809 }
810 }
811
812 Ok(())
813 }
814}
815
816impl Arbitrary for Interval {
817 type Strategy = BoxedStrategy<Self>;
818 type Parameters = ();
819
820 fn arbitrary_with(_: Self::Parameters) -> Self::Strategy {
821 (
822 any::<i32>(),
823 any::<i32>(),
824 ((((i64::from(i32::MIN) * 60) - 59) * 60) * 1_000_000 - 59_999_999
825 ..(((i64::from(i32::MAX) * 60) + 59) * 60) * 1_000_000 + 59_999_999),
826 )
827 .prop_map(|(months, days, micros)| Interval {
828 months,
829 days,
830 micros,
831 })
832 .boxed()
833 }
834}
835
836#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
840pub struct PackedInterval([u8; Self::SIZE]);
841
842#[allow(clippy::as_conversions)]
846impl FixedSizeCodec<Interval> for PackedInterval {
847 const SIZE: usize = 16;
848
849 fn as_bytes(&self) -> &[u8] {
850 &self.0[..]
851 }
852
853 fn from_bytes(slice: &[u8]) -> Result<Self, String> {
854 let buf: [u8; Self::SIZE] = slice.try_into().map_err(|_| {
855 format!(
856 "size for PackedInterval is {} bytes, got {}",
857 Self::SIZE,
858 slice.len()
859 )
860 })?;
861 Ok(PackedInterval(buf))
862 }
863
864 #[inline]
865 fn from_value(value: Interval) -> Self {
866 let mut buf = [0u8; 16];
867
868 let months = (value.months as u32) ^ (0x8000_0000u32);
871 let days = (value.days as u32) ^ (0x8000_0000u32);
872 let micros = (value.micros as u64) ^ (0x8000_0000_0000_0000u64);
873
874 buf[..4].copy_from_slice(&months.to_be_bytes());
875 buf[4..8].copy_from_slice(&days.to_be_bytes());
876 buf[8..].copy_from_slice(µs.to_be_bytes());
877
878 PackedInterval(buf)
879 }
880
881 #[inline]
882 fn into_value(self) -> Interval {
883 let mut months = [0; 4];
886 months.copy_from_slice(&self.0[..4]);
887 let months = u32::from_be_bytes(months) ^ 0x8000_0000u32;
888
889 let mut days = [0; 4];
890 days.copy_from_slice(&self.0[4..8]);
891 let days = u32::from_be_bytes(days) ^ 0x8000_0000u32;
892
893 let mut micros = [0; 8];
894 micros.copy_from_slice(&self.0[8..]);
895 let micros = u64::from_be_bytes(micros) ^ 0x8000_0000_0000_0000u64;
896
897 Interval {
898 months: months as i32,
899 days: days as i32,
900 micros: micros as i64,
901 }
902 }
903}
904
905#[cfg(test)]
906mod test {
907 use super::*;
908 use proptest::prelude::*;
909
910 #[mz_ore::test]
911 fn interval_fmt() {
912 fn mon(mon: i32) -> String {
913 Interval {
914 months: mon,
915 ..Default::default()
916 }
917 .to_string()
918 }
919
920 assert_eq!(mon(1), "1 month");
921 assert_eq!(mon(12), "1 year");
922 assert_eq!(mon(13), "1 year 1 month");
923 assert_eq!(mon(24), "2 years");
924 assert_eq!(mon(25), "2 years 1 month");
925 assert_eq!(mon(26), "2 years 2 months");
926
927 fn dur(days: i32, micros: i64) -> String {
928 Interval::new(0, days, micros).to_string()
929 }
930 assert_eq!(&dur(2, 0), "2 days");
931 assert_eq!(&dur(2, 3 * 60 * 60 * 1_000_000), "2 days 03:00:00");
932 assert_eq!(
933 &dur(
934 2,
935 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
936 ),
937 "2 days 03:45:06"
938 );
939 assert_eq!(
940 &dur(2, (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000)),
941 "2 days 03:45:00"
942 );
943 assert_eq!(&dur(2, 6 * 1_000_000), "2 days 00:00:06");
944 assert_eq!(
945 &dur(2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
946 "2 days 00:45:06"
947 );
948 assert_eq!(
949 &dur(2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
950 "2 days 03:00:06"
951 );
952 assert_eq!(
953 &dur(
954 0,
955 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
956 ),
957 "03:45:06"
958 );
959 assert_eq!(
960 &dur(0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
961 "03:00:06"
962 );
963 assert_eq!(&dur(0, 3 * 60 * 60 * 1_000_000), "03:00:00");
964 assert_eq!(&dur(0, (45 * 60 * 1_000_000) + (6 * 1_000_000)), "00:45:06");
965 assert_eq!(&dur(0, 45 * 60 * 1_000_000), "00:45:00");
966 assert_eq!(&dur(0, 6 * 1_000_000), "00:00:06");
967
968 assert_eq!(&dur(-2, -6 * 1_000_000), "-2 days -00:00:06");
969 assert_eq!(
970 &dur(-2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
971 "-2 days -00:45:06"
972 );
973 assert_eq!(
974 &dur(-2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
975 "-2 days -03:00:06"
976 );
977 assert_eq!(
978 &dur(
979 0,
980 (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
981 ),
982 "-03:45:06"
983 );
984 assert_eq!(
985 &dur(0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
986 "-03:00:06"
987 );
988 assert_eq!(&dur(0, -3 * 60 * 60 * 1_000_000), "-03:00:00");
989 assert_eq!(
990 &dur(0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
991 "-00:45:06"
992 );
993 assert_eq!(&dur(0, -45 * 60 * 1_000_000), "-00:45:00");
994 assert_eq!(&dur(0, -6 * 1_000_000), "-00:00:06");
995
996 fn mon_dur(mon: i32, days: i32, micros: i64) -> String {
997 Interval::new(mon, days, micros).to_string()
998 }
999 assert_eq!(&mon_dur(1, 2, 6 * 1_000_000), "1 month 2 days 00:00:06");
1000 assert_eq!(
1001 &mon_dur(1, 2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1002 "1 month 2 days 00:45:06"
1003 );
1004 assert_eq!(
1005 &mon_dur(1, 2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1006 "1 month 2 days 03:00:06"
1007 );
1008 assert_eq!(
1009 &mon_dur(
1010 26,
1011 0,
1012 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
1013 ),
1014 "2 years 2 months 03:45:06"
1015 );
1016 assert_eq!(
1017 &mon_dur(26, 0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1018 "2 years 2 months 03:00:06"
1019 );
1020 assert_eq!(
1021 &mon_dur(26, 0, 3 * 60 * 60 * 1_000_000),
1022 "2 years 2 months 03:00:00"
1023 );
1024 assert_eq!(
1025 &mon_dur(26, 0, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1026 "2 years 2 months 00:45:06"
1027 );
1028 assert_eq!(
1029 &mon_dur(26, 0, 45 * 60 * 1_000_000),
1030 "2 years 2 months 00:45:00"
1031 );
1032 assert_eq!(&mon_dur(26, 0, 6 * 1_000_000), "2 years 2 months 00:00:06");
1033
1034 assert_eq!(
1035 &mon_dur(26, -2, -6 * 1_000_000),
1036 "2 years 2 months -2 days -00:00:06"
1037 );
1038 assert_eq!(
1039 &mon_dur(26, -2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1040 "2 years 2 months -2 days -00:45:06"
1041 );
1042 assert_eq!(
1043 &mon_dur(26, -2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1044 "2 years 2 months -2 days -03:00:06"
1045 );
1046 assert_eq!(
1047 &mon_dur(
1048 26,
1049 0,
1050 (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
1051 ),
1052 "2 years 2 months -03:45:06"
1053 );
1054 assert_eq!(
1055 &mon_dur(26, 0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1056 "2 years 2 months -03:00:06"
1057 );
1058 assert_eq!(
1059 &mon_dur(26, 0, -3 * 60 * 60 * 1_000_000),
1060 "2 years 2 months -03:00:00"
1061 );
1062 assert_eq!(
1063 &mon_dur(26, 0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1064 "2 years 2 months -00:45:06"
1065 );
1066 assert_eq!(
1067 &mon_dur(26, 0, -45 * 60 * 1_000_000),
1068 "2 years 2 months -00:45:00"
1069 );
1070 assert_eq!(
1071 &mon_dur(26, 0, -6 * 1_000_000),
1072 "2 years 2 months -00:00:06"
1073 );
1074
1075 assert_eq!(&mon_dur(-1, 2, 6 * 1_000_000), "-1 months +2 days 00:00:06");
1076 assert_eq!(
1077 &mon_dur(-1, 2, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1078 "-1 months +2 days 00:45:06"
1079 );
1080 assert_eq!(
1081 &mon_dur(-1, 2, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1082 "-1 months +2 days 03:00:06"
1083 );
1084 assert_eq!(
1085 &mon_dur(
1086 -26,
1087 0,
1088 (3 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (6 * 1_000_000)
1089 ),
1090 "-2 years -2 months +03:45:06"
1091 );
1092 assert_eq!(
1093 &mon_dur(-26, 0, (3 * 60 * 60 * 1_000_000) + (6 * 1_000_000)),
1094 "-2 years -2 months +03:00:06"
1095 );
1096 assert_eq!(
1097 &mon_dur(-26, 0, 3 * 60 * 60 * 1_000_000),
1098 "-2 years -2 months +03:00:00"
1099 );
1100 assert_eq!(
1101 &mon_dur(-26, 0, (45 * 60 * 1_000_000) + (6 * 1_000_000)),
1102 "-2 years -2 months +00:45:06"
1103 );
1104 assert_eq!(
1105 &mon_dur(-26, 0, 45 * 60 * 1_000_000),
1106 "-2 years -2 months +00:45:00"
1107 );
1108 assert_eq!(
1109 &mon_dur(-26, 0, 6 * 1_000_000),
1110 "-2 years -2 months +00:00:06"
1111 );
1112
1113 assert_eq!(
1114 &mon_dur(-26, -2, -6 * 1_000_000),
1115 "-2 years -2 months -2 days -00:00:06"
1116 );
1117 assert_eq!(
1118 &mon_dur(-26, -2, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1119 "-2 years -2 months -2 days -00:45:06"
1120 );
1121 assert_eq!(
1122 &mon_dur(-26, -2, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1123 "-2 years -2 months -2 days -03:00:06"
1124 );
1125 assert_eq!(
1126 &mon_dur(
1127 -26,
1128 0,
1129 (-3 * 60 * 60 * 1_000_000) + (-45 * 60 * 1_000_000) + (-6 * 1_000_000)
1130 ),
1131 "-2 years -2 months -03:45:06"
1132 );
1133 assert_eq!(
1134 &mon_dur(-26, 0, (-3 * 60 * 60 * 1_000_000) + (-6 * 1_000_000)),
1135 "-2 years -2 months -03:00:06"
1136 );
1137 assert_eq!(
1138 &mon_dur(-26, 0, -3 * 60 * 60 * 1_000_000),
1139 "-2 years -2 months -03:00:00"
1140 );
1141 assert_eq!(
1142 &mon_dur(-26, 0, (-45 * 60 * 1_000_000) + (-6 * 1_000_000)),
1143 "-2 years -2 months -00:45:06"
1144 );
1145 assert_eq!(
1146 &mon_dur(-26, 0, -45 * 60 * 1_000_000),
1147 "-2 years -2 months -00:45:00"
1148 );
1149 assert_eq!(
1150 &mon_dur(-26, 0, -6 * 1_000_000),
1151 "-2 years -2 months -00:00:06"
1152 );
1153 }
1154
1155 #[mz_ore::test]
1156 fn test_interval_value_truncate_low_fields() {
1157 use DateTimeField::*;
1158
1159 let mut test_cases = [
1160 (
1161 Year,
1162 None,
1163 (
1164 321,
1165 7,
1166 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1167 ),
1168 (26 * 12, 0, 0),
1169 ),
1170 (
1171 Month,
1172 None,
1173 (
1174 321,
1175 7,
1176 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1177 ),
1178 (321, 0, 0),
1179 ),
1180 (
1181 Day,
1182 None,
1183 (
1184 321,
1185 7,
1186 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1187 ),
1188 (321, 7, 0),
1189 ),
1190 (
1191 Hour,
1192 None,
1193 (
1194 321,
1195 7,
1196 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1197 ),
1198 (321, 7, 13 * 60 * 60 * 1_000_000),
1199 ),
1200 (
1201 Minute,
1202 None,
1203 (
1204 321,
1205 7,
1206 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1207 ),
1208 (321, 7, (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000)),
1209 ),
1210 (
1211 Second,
1212 None,
1213 (
1214 321,
1215 7,
1216 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1217 ),
1218 (
1219 321,
1220 7,
1221 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1222 ),
1223 ),
1224 (
1225 Second,
1226 Some(1),
1227 (
1228 321,
1229 7,
1230 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1231 ),
1232 (
1233 321,
1234 7,
1235 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 300_000,
1236 ),
1237 ),
1238 (
1239 Second,
1240 Some(0),
1241 (
1242 321,
1243 7,
1244 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000) + 321_000,
1245 ),
1246 (
1247 321,
1248 7,
1249 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1250 ),
1251 ),
1252 ];
1253
1254 for test in test_cases.iter_mut() {
1255 let mut i = Interval::new((test.2).0, (test.2).1, (test.2).2);
1256 let j = Interval::new((test.3).0, (test.3).1, (test.3).2);
1257
1258 i.truncate_low_fields(test.0, test.1, RoundBehavior::Nearest)
1259 .unwrap();
1260
1261 if i != j {
1262 panic!(
1263 "test_interval_value_truncate_low_fields failed on {} \n actual: {:?} \n expected: {:?}",
1264 test.0, i, j
1265 );
1266 }
1267 }
1268 }
1269
1270 #[mz_ore::test]
1271 fn test_interval_value_truncate_high_fields() {
1272 use DateTimeField::*;
1273
1274 let mut test_cases = [
1276 (
1277 Year,
1278 (
1279 321,
1280 7,
1281 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1282 ),
1283 (
1284 321,
1285 7,
1286 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1287 ),
1288 ),
1289 (
1290 Month,
1291 (
1292 321,
1293 7,
1294 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1295 ),
1296 (
1297 9,
1298 7,
1299 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1300 ),
1301 ),
1302 (
1303 Day,
1304 (
1305 321,
1306 7,
1307 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1308 ),
1309 (
1310 0,
1311 7,
1312 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1313 ),
1314 ),
1315 (
1316 Hour,
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 0,
1325 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1326 ),
1327 ),
1328 (
1329 Minute,
1330 (
1331 321,
1332 7,
1333 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1334 ),
1335 (0, 0, (45 * 60 * 1_000_000) + (21 * 1_000_000)),
1336 ),
1337 (
1338 Second,
1339 (
1340 321,
1341 7,
1342 (13 * 60 * 60 * 1_000_000) + (45 * 60 * 1_000_000) + (21 * 1_000_000),
1343 ),
1344 (0, 0, 21 * 1_000_000),
1345 ),
1346 ];
1347
1348 for test in test_cases.iter_mut() {
1349 let mut i = Interval::new((test.1).0, (test.1).1, (test.1).2);
1350 let j = Interval::new((test.2).0, (test.2).1, (test.2).2);
1351
1352 i.truncate_high_fields(test.0);
1353
1354 if i != j {
1355 panic!(
1356 "test_interval_value_truncate_high_fields failed on {} \n actual: {:?} \n expected: {:?}",
1357 test.0, i, j
1358 );
1359 }
1360 }
1361 }
1362
1363 #[mz_ore::test]
1364 fn test_convert_date_time_unit() {
1365 assert_eq!(
1366 Some(1_123_200_000_000),
1367 Interval::convert_date_time_unit(
1368 DateTimeField::Day,
1369 DateTimeField::Microseconds,
1370 13i64
1371 )
1372 );
1373
1374 assert_eq!(
1375 Some(3_558_399_705),
1376 Interval::convert_date_time_unit(
1377 DateTimeField::Milliseconds,
1378 DateTimeField::Month,
1379 i64::MAX
1380 )
1381 );
1382
1383 assert_eq!(
1384 None,
1385 Interval::convert_date_time_unit(
1386 DateTimeField::Minute,
1387 DateTimeField::Second,
1388 i32::MAX
1389 )
1390 );
1391
1392 assert_eq!(
1393 Some(1),
1394 Interval::convert_date_time_unit(DateTimeField::Day, DateTimeField::Year, 365)
1395 );
1396
1397 assert_eq!(
1399 Some(360),
1400 Interval::convert_date_time_unit(DateTimeField::Year, DateTimeField::Day, 1)
1401 );
1402 }
1403
1404 #[mz_ore::test]
1405 fn proptest_packed_interval_roundtrips() {
1406 fn roundtrip_interval(og: Interval) {
1407 let packed = PackedInterval::from_value(og);
1408 let rnd = packed.into_value();
1409
1410 assert_eq!(og, rnd);
1411 }
1412
1413 proptest!(|(interval in any::<Interval>())| {
1414 roundtrip_interval(interval);
1415 });
1416 }
1417
1418 #[mz_ore::test]
1419 fn proptest_packed_interval_sorts() {
1420 fn sort_intervals(mut og: Vec<Interval>) {
1421 let mut packed: Vec<_> = og.iter().copied().map(PackedInterval::from_value).collect();
1422
1423 og.sort();
1424 packed.sort();
1425
1426 let rnd: Vec<_> = packed.into_iter().map(PackedInterval::into_value).collect();
1427
1428 assert_eq!(og, rnd);
1429 }
1430
1431 proptest!(|(interval in any::<Vec<Interval>>())| {
1432 sort_intervals(interval);
1433 });
1434 }
1435}