Skip to main content

emit_core/
extent.rs

1/*!
2The [`Extent`] type.
3
4An extent is the time for which an event is active. It may be either a point for an event that occurred at a particular time, or a range for an event that was active over a particular period.
5
6Extents can be constructed directly, or generically through the [`ToExtent`] trait.
7*/
8
9use crate::{
10    empty::Empty,
11    props::Props,
12    str::{Str, ToStr},
13    timestamp::Timestamp,
14    value::{ToValue, Value},
15    well_known::{KEY_TS, KEY_TS_START},
16};
17use core::{fmt, ops::ControlFlow, ops::Range, time::Duration};
18
19/**
20Either a single [`Timestamp`] for a point in time, or a pair of [`Timestamp`]s for a range.
21*/
22#[derive(Clone)]
23pub struct Extent {
24    range: Range<Timestamp>,
25    is_range: bool,
26}
27
28impl Extent {
29    /**
30    Create an extent for a point in time.
31    */
32    pub fn point(ts: Timestamp) -> Self {
33        Extent {
34            range: ts..ts,
35            is_range: false,
36        }
37    }
38
39    /**
40    Create an extent for a range.
41
42    The end of the range should be after the start, but an empty range is still considered a range.
43    */
44    pub fn range(ts: Range<Timestamp>) -> Self {
45        Extent {
46            range: ts,
47            is_range: true,
48        }
49    }
50
51    /**
52    Get the extent as a point in time.
53
54    For point extents, this will return exactly the value the extent was created from. For range extents, this will return the end bound.
55    */
56    pub fn as_point(&self) -> &Timestamp {
57        &self.range.end
58    }
59
60    /**
61    Try get the extent as a range.
62
63    This method will return `Some` if the extent is a range, even if that range is empty. It will return `None` for point extents.
64    */
65    pub fn as_range(&self) -> Option<&Range<Timestamp>> {
66        if self.is_range() {
67            Some(&self.range)
68        } else {
69            None
70        }
71    }
72
73    /**
74    Try get the length of the extent.
75
76    This method will return `Some` if the extent is a range, even if that range is empty. It will return `None` for point extents.
77    */
78    pub fn len(&self) -> Option<Duration> {
79        if self.is_range() {
80            self.range.end.duration_since(self.range.start)
81        } else {
82            None
83        }
84    }
85
86    /**
87    Whether the extent is a single point in time.
88    */
89    pub fn is_point(&self) -> bool {
90        !self.is_range()
91    }
92
93    /**
94    Whether the extent is a range.
95    */
96    pub fn is_range(&self) -> bool {
97        self.is_range
98    }
99}
100
101impl fmt::Debug for Extent {
102    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
103        if self.is_range() {
104            fmt::Debug::fmt(&self.range.start, f)?;
105            f.write_str("..")?;
106            fmt::Debug::fmt(&self.range.end, f)
107        } else {
108            fmt::Debug::fmt(&self.range.end, f)
109        }
110    }
111}
112
113impl fmt::Display for Extent {
114    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
115        if self.is_range() {
116            fmt::Display::fmt(&self.range.start, f)?;
117            f.write_str("..")?;
118            fmt::Display::fmt(&self.range.end, f)
119        } else {
120            fmt::Display::fmt(&self.range.end, f)
121        }
122    }
123}
124
125/**
126Try convert a value into an [`Extent`].
127*/
128pub trait ToExtent {
129    /**
130    Perform the conversion.
131    */
132    fn to_extent(&self) -> Option<Extent>;
133}
134
135impl<'a, T: ToExtent + ?Sized> ToExtent for &'a T {
136    fn to_extent(&self) -> Option<Extent> {
137        (**self).to_extent()
138    }
139}
140
141impl ToExtent for Empty {
142    fn to_extent(&self) -> Option<Extent> {
143        None
144    }
145}
146
147impl<T: ToExtent> ToExtent for Option<T> {
148    fn to_extent(&self) -> Option<Extent> {
149        self.as_ref().and_then(|ts| ts.to_extent())
150    }
151}
152
153impl ToExtent for Extent {
154    fn to_extent(&self) -> Option<Extent> {
155        Some(self.clone())
156    }
157}
158
159impl ToExtent for Timestamp {
160    fn to_extent(&self) -> Option<Extent> {
161        Some(Extent::point(*self))
162    }
163}
164
165impl ToExtent for Range<Timestamp> {
166    fn to_extent(&self) -> Option<Extent> {
167        Some(Extent::range(self.clone()))
168    }
169}
170
171impl ToExtent for Range<Option<Timestamp>> {
172    fn to_extent(&self) -> Option<Extent> {
173        match (self.start, self.end) {
174            (Some(start), Some(end)) => (start..end).to_extent(),
175            (Some(start), None) => start.to_extent(),
176            (None, Some(end)) => end.to_extent(),
177            (None, None) => None::<Timestamp>.to_extent(),
178        }
179    }
180}
181
182#[cfg(feature = "std")]
183impl ToExtent for std::time::SystemTime {
184    fn to_extent(&self) -> Option<Extent> {
185        Timestamp::from_system_time(*self).to_extent()
186    }
187}
188
189#[cfg(feature = "std")]
190impl ToExtent for Range<std::time::SystemTime> {
191    fn to_extent(&self) -> Option<Extent> {
192        (Timestamp::from_system_time(self.start)..Timestamp::from_system_time(self.end)).to_extent()
193    }
194}
195
196impl Props for Extent {
197    fn for_each<'kv, F: FnMut(Str<'kv>, Value<'kv>) -> ControlFlow<()>>(
198        &'kv self,
199        mut for_each: F,
200    ) -> ControlFlow<()> {
201        if let Some(range) = self.as_range() {
202            for_each(KEY_TS_START.to_str(), range.start.to_value())?;
203            for_each(KEY_TS.to_str(), range.end.to_value())
204        } else {
205            for_each(KEY_TS.to_str(), self.as_point().to_value())
206        }
207    }
208}
209
210#[cfg(test)]
211mod tests {
212    use super::*;
213
214    #[test]
215    fn point() {
216        let ts = Extent::point(Timestamp::MIN);
217
218        assert!(ts.is_point());
219        assert!(!ts.is_range());
220
221        assert_eq!(&Timestamp::MIN, ts.as_point());
222
223        assert_eq!(None, ts.as_range());
224        assert_eq!(None, ts.len());
225    }
226
227    #[test]
228    fn range() {
229        let ts = Extent::range(Timestamp::MIN..Timestamp::MIN + Duration::from_secs(1));
230
231        assert!(!ts.is_point());
232        assert!(ts.is_range());
233
234        assert_eq!(&(Timestamp::MIN + Duration::from_secs(1)), ts.as_point());
235
236        assert_eq!(
237            &(Timestamp::MIN..Timestamp::MIN + Duration::from_secs(1)),
238            ts.as_range().unwrap(),
239        );
240        assert_eq!(
241            Some(&(Timestamp::MIN..Timestamp::MIN + Duration::from_secs(1))),
242            ts.as_range()
243        );
244        assert_eq!(Some(Duration::from_secs(1)), ts.len());
245    }
246
247    #[test]
248    fn range_empty() {
249        let ts = Extent::range(Timestamp::MIN..Timestamp::MIN);
250
251        assert!(!ts.is_point());
252        assert!(ts.is_range());
253
254        assert_eq!(&Timestamp::MIN, ts.as_point());
255
256        assert_eq!(Some(Duration::from_secs(0)), ts.len());
257    }
258
259    #[test]
260    fn range_backwards() {
261        let ts = Extent::range(Timestamp::MAX..Timestamp::MIN);
262
263        assert!(!ts.is_point());
264        assert!(ts.is_range());
265
266        assert_eq!(&Timestamp::MIN, ts.as_point());
267
268        assert!(ts.len().is_none());
269    }
270
271    #[test]
272    #[cfg(all(
273        feature = "std",
274        not(all(
275            target_arch = "wasm32",
276            target_vendor = "unknown",
277            target_os = "unknown"
278        ))
279    ))]
280    fn point_system_time() {
281        let ts_sys = std::time::SystemTime::UNIX_EPOCH + Duration::from_secs(30);
282
283        let ts = ts_sys.to_extent().unwrap();
284
285        assert_eq!(ts_sys, ts.as_point().to_system_time());
286    }
287
288    #[test]
289    #[cfg(all(
290        feature = "std",
291        not(all(
292            target_arch = "wasm32",
293            target_vendor = "unknown",
294            target_os = "unknown"
295        ))
296    ))]
297    fn range_system_time() {
298        let ts_sys_start = std::time::SystemTime::UNIX_EPOCH + Duration::from_secs(0);
299        let ts_sys_end = std::time::SystemTime::UNIX_EPOCH + Duration::from_secs(30);
300
301        let ts = (ts_sys_start..ts_sys_end).to_extent().unwrap();
302        let ts_range = ts.as_range().unwrap();
303
304        assert_eq!(
305            ts_sys_start..ts_sys_end,
306            ts_range.start.to_system_time()..ts_range.end.to_system_time()
307        );
308    }
309
310    #[test]
311    fn as_props() {
312        let ts = Extent::point(Timestamp::MIN);
313
314        assert_eq!(Timestamp::MIN, ts.pull::<Timestamp, _>("ts").unwrap());
315
316        let ts = Extent::range(Timestamp::MIN..Timestamp::MAX);
317
318        assert_eq!(Timestamp::MAX, ts.pull::<Timestamp, _>("ts").unwrap());
319        assert_eq!(Timestamp::MIN, ts.pull::<Timestamp, _>("ts_start").unwrap());
320    }
321}