proto_types/common/
interval.rs1use core::cmp::Ordering;
2
3use thiserror::Error;
4
5use crate::{Duration, String, Timestamp, ToString, common::Interval, constants::NANOS_PER_SECOND};
6
7#[derive(Debug, Error, PartialEq, Eq, Clone)]
9#[non_exhaustive]
10pub enum IntervalError {
11 #[error("Interval's end_time is before its start_time")]
12 EndTimeBeforeStartTime,
13 #[error("Interval conversion error: {0}")]
14 ConversionError(String),
15}
16
17fn validate_interval(
18 start: Option<Timestamp>,
19 end: Option<Timestamp>,
20) -> Result<(), IntervalError> {
21 if start.is_some_and(|s| end.is_some_and(|e| e < s)) {
22 Err(IntervalError::EndTimeBeforeStartTime)
23 } else {
24 Ok(())
25 }
26}
27
28impl Interval {
29 #[inline]
31 pub fn new(
32 start_time: Option<Timestamp>,
33 end_time: Option<Timestamp>,
34 ) -> Result<Self, IntervalError> {
35 validate_interval(start_time, end_time)?;
36
37 Ok(Self {
38 start_time,
39 end_time,
40 })
41 }
42
43 #[cfg(any(feature = "std", feature = "chrono-wasm"))]
44 #[must_use]
46 #[inline]
47 pub fn from_now_to(end_time: Timestamp) -> Self {
48 Self {
49 start_time: Some(Timestamp::now()),
50 end_time: Some(end_time),
51 }
52 }
53
54 #[cfg(any(feature = "std", feature = "chrono-wasm"))]
55 #[must_use]
57 #[inline]
58 pub fn from_start_to_now(start_time: Timestamp) -> Self {
59 Self {
60 start_time: Some(start_time),
61 end_time: Some(Timestamp::now()),
62 }
63 }
64
65 #[must_use]
67 pub fn is_valid(&self) -> bool {
68 validate_interval(self.start_time, self.end_time).is_ok()
69 }
70
71 #[must_use]
73 #[inline]
74 pub fn is_empty(&self) -> bool {
75 self.start_time
76 .as_ref()
77 .zip(self.end_time.as_ref())
78 .map_or_else(|| false, |(start, end)| start == end)
79 }
80
81 #[must_use]
83 #[inline]
84 pub const fn is_unspecified(&self) -> bool {
85 self.start_time.is_none() && self.end_time.is_none()
86 }
87}
88
89impl TryFrom<Interval> for Duration {
90 type Error = IntervalError;
91 fn try_from(value: Interval) -> Result<Self, Self::Error> {
92 let result = value
93 .start_time
94 .zip(value.end_time)
95 .map(|(start, end)| {
96 let mut seconds_diff = end.seconds - start.seconds;
97 let mut nanos_diff = end.nanos - start.nanos;
98
99 if nanos_diff < 0 {
100 seconds_diff -= 1;
101 nanos_diff += NANOS_PER_SECOND;
102 } else if nanos_diff >= NANOS_PER_SECOND {
103 seconds_diff += 1;
104 nanos_diff -= NANOS_PER_SECOND;
105 }
106
107 Self {
108 seconds: seconds_diff,
109 nanos: nanos_diff,
110 }
111 });
112
113 result.ok_or(IntervalError::ConversionError(
114 "Cannot convert to Duration due to missing start or end time".to_string(),
115 ))
116 }
117}
118
119impl PartialOrd for Interval {
120 fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
121 if !self.is_valid() || !other.is_valid() {
122 return None;
123 }
124
125 if self.is_empty() {
127 return if other.is_empty() {
128 Some(Ordering::Equal)
129 } else {
130 Some(Ordering::Less)
131 };
132 }
133 if other.is_empty() {
134 return Some(Ordering::Greater);
135 }
136
137 let self_dur = Duration::try_from(*self);
140 let other_dur = Duration::try_from(*other);
141
142 match (self_dur, other_dur) {
143 (Ok(d1), Ok(d2)) => d1.partial_cmp(&d2),
145
146 (Ok(_), Err(_)) => Some(Ordering::Less),
148
149 (Err(_), Ok(_)) => Some(Ordering::Greater),
151
152 (Err(_), Err(_)) => Some(Ordering::Equal),
155 }
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use super::*;
162
163 fn ts(s: i64) -> Timestamp {
164 Timestamp {
165 seconds: s,
166 nanos: 0,
167 }
168 }
169
170 #[test]
171 fn test_constructor() {
172 let t = ts(100);
173
174 let empty = Interval::new(Some(t), Some(t)).unwrap();
176 assert!(empty.is_empty());
177 assert!(!empty.is_unspecified());
178
179 let unspec = Interval::new(None, None).unwrap();
181 assert!(unspec.is_unspecified());
182
183 let open = Interval::new(Some(t), None).unwrap();
185 assert!(!open.is_empty());
186 }
187
188 #[test]
189 fn test_partial_ord_ranking() {
190 let t0 = ts(0);
191 let t10 = ts(10);
192 let t20 = ts(20);
193
194 let empty = Interval::new(Some(t0), Some(t0)).unwrap(); let finite_small = Interval::new(Some(t0), Some(t10)).unwrap(); let finite_large = Interval::new(Some(t0), Some(t20)).unwrap(); let infinite_end = Interval::new(Some(t0), None).unwrap(); let infinite_start = Interval::new(None, Some(t20)).unwrap(); let infinite_all = Interval::new(None, None).unwrap(); assert!(empty < finite_small);
203
204 assert!(finite_small < finite_large);
206
207 assert!(finite_large < infinite_end);
209 assert!(finite_large < infinite_start);
210
211 assert!(infinite_end.partial_cmp(&infinite_start) == Some(Ordering::Equal));
215 assert!(infinite_end.partial_cmp(&infinite_all) == Some(Ordering::Equal));
216 }
217}