• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geo-engine / geoengine / 17140061479

21 Aug 2025 09:56PM UTC coverage: 88.65%. First build
17140061479

Pull #1075

github

web-flow
Merge 748d85092 into 0f970bb88
Pull Request #1075: feat(services): add progress to dataset writer task

110 of 112 new or added lines in 6 files covered. (98.21%)

112428 of 126822 relevant lines covered (88.65%)

79515.62 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

91.8
/datatypes/src/primitives/time_interval.rs
1
use crate::primitives::TimeInstance;
2
use crate::util::Result;
3
use crate::util::arrow::{ArrowTyped, downcast_array, padded_buffer_size};
4
use crate::{error, util::ranges::value_in_range};
5
use arrow::array::{Array, ArrayBuilder, BooleanArray, Int64Array};
6
use arrow::datatypes::{DataType, Field};
7
use arrow::error::ArrowError;
8

9
use postgres_types::{FromSql, ToSql};
10
use serde::{Deserialize, Serialize};
11
use snafu::ensure;
12
use std::fmt::{Debug, Display};
13
use std::sync::Arc;
14
use std::{cmp::Ordering, convert::TryInto};
15

16
/// Stores time intervals in ms in close-open semantic [start, end)
17
#[derive(Clone, Copy, Deserialize, Serialize, PartialEq, Eq, ToSql, FromSql)]
×
18
#[repr(C)]
19
pub struct TimeInterval {
20
    start: TimeInstance,
21
    end: TimeInstance,
22
}
23

24
impl Default for TimeInterval {
25
    /// The default time interval is always valid.
26
    ///
27
    /// It aligns with `chrono`'s minimum and maximum datetime.
28
    ///
29
    /// # Examples
30
    ///
31
    /// ```
32
    /// use geoengine_datatypes::primitives::{TimeInterval, TimeInstance};
33
    ///
34
    /// assert!(TimeInterval::default().contains(&TimeInterval::new_unchecked(0, 0)));
35
    /// assert!(TimeInterval::default().intersects(&TimeInterval::default()));
36
    /// assert_eq!(TimeInterval::default().union(&TimeInterval::default()).unwrap(), TimeInterval::default());
37
    /// ```
38
    fn default() -> Self {
10,525✔
39
        Self {
10,525✔
40
            start: TimeInstance::MIN,
10,525✔
41
            end: TimeInstance::MAX,
10,525✔
42
        }
10,525✔
43
    }
10,525✔
44
}
45

46
impl TimeInterval {
47
    /// Creates a new time interval from inputs implementing Into<TimeInstance>
48
    ///
49
    /// # Examples
50
    ///
51
    /// ```
52
    /// use geoengine_datatypes::primitives::{TimeInterval, TimeInstance};
53
    ///
54
    /// TimeInterval::new(0, 0).unwrap();
55
    /// TimeInterval::new(0, 1).unwrap();
56
    ///
57
    /// TimeInterval::new(1, 0).unwrap_err();
58
    /// ```
59
    ///
60
    /// # Errors
61
    ///
62
    /// This constructor fails if `end` is before `start`
63
    ///
64
    #[allow(clippy::trait_duplication_in_bounds)] // both `error::Error: From<A::Error> + From<B::Error>` is required
65
    pub fn new<A, B>(start: A, end: B) -> Result<Self>
638✔
66
    where
638✔
67
        A: TryInto<TimeInstance>,
638✔
68
        B: TryInto<TimeInstance>,
638✔
69
        error::Error: From<A::Error> + From<B::Error>,
638✔
70
    {
71
        let start_instant = start.try_into()?;
638✔
72
        let end_instant = end.try_into()?;
638✔
73

74
        ensure!(
638✔
75
            start_instant <= end_instant,
638✔
76
            error::TimeIntervalEndBeforeStart {
×
77
                start: start_instant,
×
78
                end: end_instant
×
79
            }
×
80
        );
81
        ensure!(
638✔
82
            start_instant >= TimeInstance::MIN && end_instant <= TimeInstance::MAX,
638✔
83
            error::TimeIntervalOutOfBounds {
×
84
                start: start_instant,
×
85
                end: end_instant,
×
86
                min: TimeInstance::MIN,
×
87
                max: TimeInstance::MAX,
×
88
            }
×
89
        );
90

91
        Ok(Self {
638✔
92
            start: start_instant,
638✔
93
            end: end_instant,
638✔
94
        })
638✔
95
    }
638✔
96

97
    /// Creates a new time interval from a single input that implements `Into<TimeInstance>`.
98
    /// After instanciation, start and end are equal.
99
    pub fn new_instant<A>(start_and_end: A) -> Result<Self>
228✔
100
    where
228✔
101
        A: TryInto<TimeInstance>,
228✔
102
        error::Error: From<A::Error>,
228✔
103
    {
104
        let start_and_end = start_and_end.try_into()?;
228✔
105
        Ok(Self {
228✔
106
            start: start_and_end,
228✔
107
            end: start_and_end,
228✔
108
        })
228✔
109
    }
228✔
110

111
    /// Creates a new time interval without bound checks from inputs implementing Into<TimeInstance>
112
    ///
113
    /// # Examples
114
    ///
115
    /// ```
116
    /// use geoengine_datatypes::primitives::{TimeInterval};
117
    ///
118
    /// let time_unchecked = TimeInterval::new_unchecked(0, 1);
119
    ///
120
    /// assert_eq!(time_unchecked, TimeInterval::new(0, 1).unwrap());
121
    /// ```
122
    ///
123
    /// # Panics
124
    /// Panics if start and end are not compatible to [chrono].
125
    ///
126
    pub fn new_unchecked<A, B>(start: A, end: B) -> Self
3,370✔
127
    where
3,370✔
128
        A: TryInto<TimeInstance>,
3,370✔
129
        B: TryInto<TimeInstance>,
3,370✔
130
        A::Error: Debug,
3,370✔
131
        B::Error: Debug,
3,370✔
132
    {
133
        let start = start
3,370✔
134
            .try_into()
3,370✔
135
            .expect("start's validity should be checked by the caller");
3,370✔
136
        let end = end
3,370✔
137
            .try_into()
3,370✔
138
            .expect("end's validity should be checked by the caller");
3,370✔
139
        debug_assert!(start <= end);
3,370✔
140
        Self { start, end }
3,369✔
141
    }
3,369✔
142

143
    /// Returns whether the other `TimeInterval` is contained (smaller or equal) within this interval
144
    ///
145
    /// # Examples
146
    ///
147
    /// ```
148
    /// use geoengine_datatypes::primitives::{TimeInterval, TimeInstance};
149
    ///
150
    /// let valid_pairs = vec![
151
    ///     ((0, 1), (0, 1)),
152
    ///     ((0, 3), (1, 2)),
153
    ///     ((0, 2), (0, 1)),
154
    ///     ((0, 2), (1, 2)),
155
    /// ];
156
    ///
157
    /// for ((t1, t2), (t3, t4)) in valid_pairs {
158
    ///     let i1 = TimeInterval::new(t1, t2).unwrap();
159
    ///     let i2 = TimeInterval::new(t3, t4).unwrap();
160
    ///     assert!(i1.contains(&i2), "{:?} should contain {:?}", i1, i2);
161
    /// }
162
    ///
163
    /// let invalid_pairs = vec![((0, 1), (-1, 2))];
164
    ///
165
    /// for ((t1, t2), (t3, t4)) in invalid_pairs {
166
    ///     let i1 = TimeInterval::new(t1, t2).unwrap();
167
    ///     let i2 = TimeInterval::new(t3, t4).unwrap();
168
    ///     assert!(!i1.contains(&i2), "{:?} should not contain {:?}", i1, i2);
169
    /// }
170
    /// ```
171
    ///
172
    pub fn contains(&self, other: &Self) -> bool {
458✔
173
        self == other || ((self.start..self.end).contains(&other.start) && (self.end >= other.end))
458✔
174
    }
458✔
175

176
    /// Returns whether the given interval intersects this interval
177
    ///
178
    /// # Examples
179
    ///
180
    /// ```
181
    /// use geoengine_datatypes::primitives::{TimeInterval, TimeInstance};
182
    ///
183
    /// let valid_pairs = vec![
184
    ///     ((0, 1), (0, 1)),
185
    ///     ((0, 3), (1, 2)),
186
    ///     ((0, 2), (1, 3)),
187
    ///     ((0, 1), (0, 2)),
188
    ///     ((0, 2), (-2, 1)),
189
    /// ];
190
    ///
191
    /// for ((t1, t2), (t3, t4)) in valid_pairs {
192
    ///     let i1 = TimeInterval::new(t1, t2).unwrap();
193
    ///     let i2 = TimeInterval::new(t3, t4).unwrap();
194
    ///     assert!(i1.intersects(&i2), "{:?} should intersect {:?}", i1, i2);
195
    /// }
196
    ///
197
    /// let invalid_pairs = vec![
198
    ///     ((0, 1), (-1, 0)), //
199
    ///     ((0, 1), (1, 2)),
200
    ///     ((0, 1), (2, 3)),
201
    /// ];
202
    ///
203
    /// for ((t1, t2), (t3, t4)) in invalid_pairs {
204
    ///     let i1 = TimeInterval::new(t1, t2).unwrap();
205
    ///     let i2 = TimeInterval::new(t3, t4).unwrap();
206
    ///     assert!(
207
    ///         !i1.intersects(&i2),
208
    ///         "{:?} should not intersect {:?}",
209
    ///         i1,
210
    ///         i2
211
    ///     );
212
    /// }
213
    /// ```
214
    ///
215
    pub fn intersects(&self, other: &Self) -> bool {
51,093✔
216
        other == self
51,093✔
217
            || value_in_range(self.start, other.start, other.end)
3,095✔
218
            || value_in_range(other.start, self.start, self.end)
2,126✔
219
    }
51,093✔
220

221
    /// Unites this interval with another one.
222
    ///
223
    /// # Examples
224
    ///
225
    /// ```
226
    /// use geoengine_datatypes::primitives::{TimeInterval, TimeInstance};
227
    ///
228
    /// let i1 = TimeInterval::new(0, 2).unwrap();
229
    /// let i2 = TimeInterval::new(1, 3).unwrap();
230
    /// let i3 = TimeInterval::new(2, 4).unwrap();
231
    /// let i4 = TimeInterval::new(3, 5).unwrap();
232
    ///
233
    /// assert_eq!(i1.union(&i2).unwrap(), TimeInterval::new(0, 3).unwrap());
234
    /// assert_eq!(i1.union(&i3).unwrap(), TimeInterval::new(0, 4).unwrap());
235
    /// i1.union(&i4).unwrap_err();
236
    /// ```
237
    ///
238
    /// # Errors
239
    /// This method fails if the other `TimeInterval` does not intersect or touch the current interval.
240
    ///
241
    pub fn union(&self, other: &Self) -> Result<Self> {
235✔
242
        ensure!(
235✔
243
            self.intersects(other) || self.start == other.end || self.end == other.start,
235✔
244
            error::TimeIntervalUnmatchedIntervals {
2✔
245
                i1: *self,
2✔
246
                i2: *other,
2✔
247
            }
2✔
248
        );
249
        Ok(Self {
233✔
250
            start: TimeInstance::min(self.start, other.start),
233✔
251
            end: TimeInstance::max(self.end, other.end),
233✔
252
        })
233✔
253
    }
235✔
254

255
    pub fn start(&self) -> TimeInstance {
64,443✔
256
        self.start
64,443✔
257
    }
64,443✔
258

259
    pub fn end(&self) -> TimeInstance {
58,130✔
260
        self.end
58,130✔
261
    }
58,130✔
262

263
    /// Creates a geo json event from a time interval
264
    ///
265
    /// according to `GeoJSON` event extension (<https://github.com/sgillies/geojson-events>)
266
    ///
267
    /// # Examples
268
    ///
269
    /// ```
270
    /// use geoengine_datatypes::primitives::{TimeInterval, TimeInstance};
271
    ///
272
    /// assert_eq!(
273
    ///     TimeInterval::new_unchecked(0, 1585069448 * 1000).as_geo_json_event(),
274
    ///     serde_json::json!({
275
    ///         "start": "1970-01-01T00:00:00+00:00",
276
    ///         "end": "2020-03-24T17:04:08+00:00",
277
    ///         "type": "Interval",
278
    ///     })
279
    /// );
280
    /// ```
281
    pub fn as_geo_json_event(&self) -> serde_json::Value {
33✔
282
        serde_json::json!({
33✔
283
            "start": self.start.as_datetime_string(),
33✔
284
            "end": self.end.as_datetime_string(),
33✔
285
            "type": "Interval"
33✔
286
        })
287
    }
33✔
288

289
    /// Return a new time interval that is the intersection with the `other` time interval, or
290
    /// `None` if the intervals are disjoint
291
    pub fn intersect(self, other: &Self) -> Option<TimeInterval> {
148✔
292
        if self.intersects(other) {
148✔
293
            let start = std::cmp::max(self.start, other.start);
143✔
294
            let end = std::cmp::min(self.end, other.end);
143✔
295
            Some(Self::new_unchecked(start, end))
143✔
296
        } else {
297
            None
5✔
298
        }
299
    }
148✔
300

301
    /// Returns the duration of the interval
302
    /// This is the difference between the start and end time.
303
    /// If the start and end time are equal i.e. the interval is an instant, the duration is 0.
304
    pub fn duration_ms(&self) -> u64 {
2,314✔
305
        self.end.inner().wrapping_sub(self.start.inner()) as u64
2,314✔
306
    }
2,314✔
307

308
    pub fn is_instant(&self) -> bool {
341✔
309
        self.start == self.end
341✔
310
    }
341✔
311

312
    /// Extends a time interval with the bounds of another time interval.
313
    /// The result has the smaller `start` and the larger `end`.
314
    #[must_use]
315
    pub fn extend(&self, other: &Self) -> TimeInterval {
102✔
316
        Self {
102✔
317
            start: self.start.min(other.start),
102✔
318
            end: self.end.max(other.end),
102✔
319
        }
102✔
320
    }
102✔
321
}
322

323
impl Debug for TimeInterval {
324
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
×
325
        write!(
×
326
            f,
×
327
            "TimeInterval [{}, {})",
×
328
            self.start.inner(),
×
329
            &self.end.inner()
×
330
        )
331
    }
×
332
}
333

334
impl Display for TimeInterval {
335
    /// Display the interval in its close-open form
336
    ///
337
    /// # Examples
338
    ///
339
    /// ```
340
    /// use geoengine_datatypes::primitives::TimeInterval;
341
    ///
342
    /// assert_eq!(format!("{}", TimeInterval::new(0, 1).unwrap()), "[0, 1)");
343
    /// ```
344
    ///
345
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
×
NEW
346
        write!(f, "[{}, {})", self.start.inner(), self.end.inner())
×
347
    }
×
348
}
349

350
impl PartialOrd for TimeInterval {
351
    /// Order intervals whether they are completely before, equal or after each other or in-between (unordered)
352
    ///
353
    /// # Examples
354
    ///
355
    /// ```
356
    /// use geoengine_datatypes::primitives::TimeInterval;
357
    ///
358
    /// assert_eq!(
359
    ///     TimeInterval::new(0, 1).unwrap(),
360
    ///     TimeInterval::new(0, 1).unwrap()
361
    /// );
362
    /// assert_ne!(
363
    ///     TimeInterval::new(0, 1).unwrap(),
364
    ///     TimeInterval::new(1, 2).unwrap()
365
    /// );
366
    ///
367
    /// assert!(TimeInterval::new(0, 1).unwrap() <= TimeInterval::new(0, 1).unwrap());
368
    /// assert!(TimeInterval::new(0, 1).unwrap() <= TimeInterval::new(1, 2).unwrap());
369
    /// assert!(TimeInterval::new(0, 1).unwrap() < TimeInterval::new(1, 2).unwrap());
370
    ///
371
    /// assert!(TimeInterval::new(0, 1).unwrap() >= TimeInterval::new(0, 1).unwrap());
372
    /// assert!(TimeInterval::new(1, 2).unwrap() >= TimeInterval::new(0, 1).unwrap());
373
    /// assert!(TimeInterval::new(1, 2).unwrap() > TimeInterval::new(0, 1).unwrap());
374
    ///
375
    /// assert!(TimeInterval::new(0, 2)
376
    ///     .unwrap()
377
    ///     .partial_cmp(&TimeInterval::new(1, 3).unwrap())
378
    ///     .is_none());
379
    ///
380
    /// assert!(TimeInterval::new(0, 1)
381
    ///     .unwrap()
382
    ///     .partial_cmp(&TimeInterval::new(0, 2).unwrap())
383
    ///     .is_none());
384
    /// ```
385
    ///
386
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
×
387
        if self.eq(other) {
×
388
            Some(Ordering::Equal)
×
389
        } else if self.end <= other.start {
×
390
            Some(Ordering::Less)
×
391
        } else if self.start >= other.end {
×
392
            Some(Ordering::Greater)
×
393
        } else {
394
            None
×
395
        }
396
    }
×
397
}
398

399
impl From<TimeInstance> for TimeInterval {
400
    fn from(time_instance: TimeInstance) -> Self {
816✔
401
        Self::new_unchecked(time_instance, time_instance)
816✔
402
    }
816✔
403
}
404

405
impl ArrowTyped for TimeInterval {
406
    type ArrowArray = arrow::array::FixedSizeListArray;
407
    // TODO: use date if dates out-of-range is fixed for us
408
    // type ArrowBuilder = arrow::array::FixedSizeListBuilder<arrow::array::Date64Builder>;
409
    type ArrowBuilder = arrow::array::FixedSizeListBuilder<arrow::array::Int64Builder>;
410

411
    fn arrow_data_type() -> arrow::datatypes::DataType {
821✔
412
        // TODO: use date if dates out-of-range is fixed for us
413
        // DataType::FixedSizeList(Box::new(Field::new("item", DataType::Date64(arrow::datatypes::DateUnit::Millisecond), nullable)), 2)
414

415
        let nullable = true; // TODO: should actually be false, but arrow's builders set it to `true` currently
821✔
416

417
        DataType::FixedSizeList(Arc::new(Field::new("item", DataType::Int64, nullable)), 2)
821✔
418
    }
821✔
419

420
    fn estimate_array_memory_size(builder: &mut Self::ArrowBuilder) -> usize {
7,635✔
421
        let size = std::mem::size_of::<Self::ArrowArray>() + std::mem::size_of::<Int64Array>();
7,635✔
422

423
        let buffer_bytes = builder.values().len() * std::mem::size_of::<i64>();
7,635✔
424
        size + padded_buffer_size(buffer_bytes, 64)
7,635✔
425
    }
7,635✔
426

427
    fn arrow_builder(capacity: usize) -> Self::ArrowBuilder {
958✔
428
        // TODO: use date if dates out-of-range is fixed for us
429
        // arrow::array::FixedSizeListBuilder::new(arrow::array::Date64Builder::new(2 * capacity), 2)
430

431
        arrow::array::FixedSizeListBuilder::with_capacity(
958✔
432
            arrow::array::Int64Builder::with_capacity(2 * capacity),
958✔
433
            2,
434
            capacity,
958✔
435
        )
436
    }
958✔
437

438
    fn concat(a: &Self::ArrowArray, b: &Self::ArrowArray) -> Result<Self::ArrowArray, ArrowError> {
18✔
439
        let new_length = a.len() + b.len();
18✔
440
        let mut new_time_intervals = TimeInterval::arrow_builder(new_length);
18✔
441

442
        {
18✔
443
            // TODO: use date if dates out-of-range is fixed for us
18✔
444
            // use arrow::array::Date64Array;
18✔
445

18✔
446
            let int_builder = new_time_intervals.values();
18✔
447

18✔
448
            let ints_a_ref = a.values();
18✔
449
            let ints_b_ref = b.values();
18✔
450

18✔
451
            let ints_a: &Int64Array = downcast_array(ints_a_ref);
18✔
452
            let ints_b: &Int64Array = downcast_array(ints_b_ref);
18✔
453

18✔
454
            int_builder.append_slice(ints_a.values());
18✔
455
            int_builder.append_slice(ints_b.values());
18✔
456
        }
18✔
457

458
        for _ in 0..new_length {
55✔
459
            new_time_intervals.append(true);
55✔
460
        }
55✔
461

462
        // we can use `finish` instead of `finish_cloned` since we can set the optimal capacity
463
        Ok(new_time_intervals.finish())
18✔
464
    }
18✔
465

466
    fn filter(
151✔
467
        time_intervals: &Self::ArrowArray,
151✔
468
        filter_array: &BooleanArray,
151✔
469
    ) -> Result<Self::ArrowArray, ArrowError> {
151✔
470
        // TODO: use date if dates out-of-range is fixed for us
471
        // use arrow::array::Date64Array;
472

473
        let mut new_time_intervals = Self::arrow_builder(0);
151✔
474

475
        for feature_index in 0..time_intervals.len() {
39,283✔
476
            if !filter_array.value(feature_index) {
39,283✔
477
                continue;
73✔
478
            }
39,210✔
479

480
            let old_timestamps_ref = time_intervals.value(feature_index);
39,210✔
481
            let old_timestamps: &Int64Array = downcast_array(&old_timestamps_ref);
39,210✔
482

483
            let date_builder = new_time_intervals.values();
39,210✔
484
            date_builder.append_slice(old_timestamps.values());
39,210✔
485

486
            new_time_intervals.append(true);
39,210✔
487
        }
488

489
        // `finish_cloned` instead of `finish` we cannot set the capacity before filtering, so we have to shrink the capacity afterwards
490
        Ok(new_time_intervals.finish_cloned())
151✔
491
    }
151✔
492

493
    fn from_vec(time_intervals: Vec<Self>) -> Result<Self::ArrowArray, ArrowError>
450✔
494
    where
450✔
495
        Self: Sized,
450✔
496
    {
497
        // TODO: build faster(?) without builder
498

499
        let mut builder = Self::arrow_builder(time_intervals.len());
450✔
500
        for time_interval in time_intervals {
41,369✔
501
            let date_builder = builder.values();
40,919✔
502
            date_builder.append_value(time_interval.start().into());
40,919✔
503
            date_builder.append_value(time_interval.end().into());
40,919✔
504
            builder.append(true);
40,919✔
505
        }
40,919✔
506

507
        // we can use `finish` instead of `finish_cloned` since we can set the optimal capacity
508
        Ok(builder.finish())
450✔
509
    }
450✔
510
}
511

512
/// Compute the extent of all input time intervals. If one time interval is None, the output will also be None
513
pub fn time_interval_extent<I: Iterator<Item = Option<TimeInterval>>>(
43✔
514
    mut times: I,
43✔
515
) -> Option<TimeInterval> {
43✔
516
    let Some(Some(mut extent)) = times.next() else {
43✔
517
        return None;
36✔
518
    };
519

520
    for time in times {
15✔
521
        if let Some(time) = time {
9✔
522
            extent = extent.extend(&time);
8✔
523
        } else {
8✔
524
            return None;
1✔
525
        }
526
    }
527

528
    Some(extent)
6✔
529
}
43✔
530

531
#[cfg(test)]
532
mod tests {
533
    use crate::primitives::DateTime;
534

535
    use super::*;
536

537
    #[test]
538
    fn to_geo_json_event() {
1✔
539
        let min_visualizable_value = -8_334_601_228_800_000;
1✔
540
        let max_visualizable_value = 8_210_266_876_799_999;
1✔
541

542
        assert_eq!(
1✔
543
            TimeInterval::new_unchecked(min_visualizable_value, max_visualizable_value)
1✔
544
                .as_geo_json_event(),
1✔
545
            serde_json::json!({
1✔
546
                "start": "-262143-01-01T00:00:00+00:00",
1✔
547
                "end": "+262142-12-31T23:59:59.999+00:00",
1✔
548
                "type": "Interval",
1✔
549
            })
550
        );
551
        assert_eq!(
1✔
552
            TimeInterval::new_unchecked(
1✔
553
                TimeInstance::from_millis_unchecked(min_visualizable_value - 1),
1✔
554
                TimeInstance::from_millis_unchecked(max_visualizable_value + 1)
1✔
555
            )
1✔
556
            .as_geo_json_event(),
1✔
557
            serde_json::json!({
1✔
558
                "start": "-262143-01-01T00:00:00+00:00",
1✔
559
                "end": "+262142-12-31T23:59:59.999+00:00",
1✔
560
                "type": "Interval",
1✔
561
            })
562
        );
563
        assert_eq!(
1✔
564
            TimeInterval::new_unchecked(
1✔
565
                TimeInstance::from_millis_unchecked(i64::MIN),
1✔
566
                TimeInstance::from_millis_unchecked(i64::MAX)
1✔
567
            )
1✔
568
            .as_geo_json_event(),
1✔
569
            serde_json::json!({
1✔
570
                "start": "-262143-01-01T00:00:00+00:00",
1✔
571
                "end": "+262142-12-31T23:59:59.999+00:00",
1✔
572
                "type": "Interval",
1✔
573
            })
574
        );
575
    }
1✔
576

577
    #[test]
578
    fn duration_millis() {
1✔
579
        assert_eq!(
1✔
580
            TimeInterval::default().duration_ms(),
1✔
581
            16_544_868_105_599_999
582
        );
583

584
        let time_interval = TimeInterval::new(
1✔
585
            TimeInstance::from(DateTime::new_utc(1990, 1, 1, 0, 0, 0)),
1✔
586
            TimeInstance::from(DateTime::new_utc(2000, 1, 1, 0, 0, 0)),
1✔
587
        )
588
        .unwrap();
1✔
589

590
        assert_eq!(time_interval.duration_ms(), 315_532_800_000);
1✔
591

592
        assert_eq!(
1✔
593
            TimeInterval::new(-1, TimeInstance::MAX)
1✔
594
                .unwrap()
1✔
595
                .duration_ms(),
1✔
596
            8_210_266_876_800_000
597
        );
598
        assert_eq!(
1✔
599
            TimeInterval::new(0, TimeInstance::MAX)
1✔
600
                .unwrap()
1✔
601
                .duration_ms(),
1✔
602
            8_210_266_876_799_999
603
        );
604

605
        assert_eq!(
1✔
606
            TimeInterval::new(TimeInstance::MIN, -1)
1✔
607
                .unwrap()
1✔
608
                .duration_ms(),
1✔
609
            8_334_601_228_799_999
610
        );
611
        assert_eq!(
1✔
612
            TimeInterval::new(TimeInstance::MIN, 0)
1✔
613
                .unwrap()
1✔
614
                .duration_ms(),
1✔
615
            8_334_601_228_800_000
616
        );
617
        assert_eq!(
1✔
618
            TimeInterval::new(TimeInstance::MIN, 1)
1✔
619
                .unwrap()
1✔
620
                .duration_ms(),
1✔
621
            8_334_601_228_800_001
622
        );
623
    }
1✔
624

625
    #[test]
626
    fn bounds() {
1✔
627
        let t = TimeInterval::default();
1✔
628

629
        assert_eq!(t.start(), TimeInstance::MIN);
1✔
630
        assert_eq!(t.end(), TimeInstance::MAX);
1✔
631
    }
1✔
632

633
    #[test]
634
    fn intersects_same() {
1✔
635
        let a = TimeInterval::new(2, 4).unwrap();
1✔
636
        let b = TimeInterval::new(2, 4).unwrap();
1✔
637

638
        assert!(a.intersects(&b));
1✔
639
    }
1✔
640

641
    #[test]
642
    fn intersects_before() {
1✔
643
        let a = TimeInterval::new(1, 2).unwrap();
1✔
644
        let b = TimeInterval::new(2, 3).unwrap();
1✔
645

646
        assert!(!a.intersects(&b));
1✔
647
    }
1✔
648

649
    #[test]
650
    fn intersects_overlap_left() {
1✔
651
        let a = TimeInterval::new(1, 3).unwrap();
1✔
652
        let b = TimeInterval::new(2, 4).unwrap();
1✔
653

654
        assert!(a.intersects(&b));
1✔
655
    }
1✔
656

657
    #[test]
658
    fn intersects_inside() {
1✔
659
        let a = TimeInterval::new(3, 4).unwrap();
1✔
660
        let b = TimeInterval::new(2, 4).unwrap();
1✔
661

662
        assert!(a.intersects(&b));
1✔
663
    }
1✔
664

665
    #[test]
666
    fn intersects_inside_instance_a() {
1✔
667
        let a = TimeInterval::new_instant(3).unwrap();
1✔
668
        let b = TimeInterval::new(2, 4).unwrap();
1✔
669

670
        assert!(a.intersects(&b));
1✔
671
    }
1✔
672

673
    #[test]
674
    fn intersects_inside_instance_b() {
1✔
675
        let a = TimeInterval::new_instant(3).unwrap();
1✔
676
        let b = TimeInterval::new(2, 4).unwrap();
1✔
677

678
        assert!(b.intersects(&a));
1✔
679
    }
1✔
680

681
    #[test]
682
    fn intersects_overlap_right() {
1✔
683
        let a = TimeInterval::new(1, 3).unwrap();
1✔
684
        let b = TimeInterval::new(2, 4).unwrap();
1✔
685

686
        assert!(b.intersects(&a));
1✔
687
    }
1✔
688

689
    #[test]
690
    fn intersects_after() {
1✔
691
        let a = TimeInterval::new(1, 2).unwrap();
1✔
692
        let b = TimeInterval::new(2, 3).unwrap();
1✔
693

694
        assert!(!b.intersects(&a));
1✔
695
    }
1✔
696

697
    #[test]
698
    fn extend() {
1✔
699
        let a = TimeInterval::new(1, 2).unwrap();
1✔
700
        let b = TimeInterval::new(2, 3).unwrap();
1✔
701

702
        assert_eq!(a.extend(&b), TimeInterval::new_unchecked(1, 3));
1✔
703
    }
1✔
704

705
    #[test]
706
    fn is_instant() {
1✔
707
        let a = TimeInterval::new(1, 1).unwrap();
1✔
708
        let b = TimeInterval::new(2, 3).unwrap();
1✔
709

710
        assert!(a.is_instant());
1✔
711
        assert!(!b.is_instant());
1✔
712
    }
1✔
713

714
    #[test]
715
    fn extent() {
1✔
716
        assert_eq!(time_interval_extent([None].into_iter()), None);
1✔
717
        assert_eq!(
1✔
718
            time_interval_extent(
1✔
719
                [
1✔
720
                    Some(TimeInterval::new(1, 2).unwrap()),
1✔
721
                    Some(TimeInterval::new(5, 6).unwrap())
1✔
722
                ]
1✔
723
                .into_iter()
1✔
724
            ),
725
            Some(TimeInterval::new_unchecked(1, 6))
1✔
726
        );
727
        assert_eq!(
1✔
728
            time_interval_extent([Some(TimeInterval::new(1, 2).unwrap()), None].into_iter()),
1✔
729
            None
730
        );
731
        assert_eq!(
1✔
732
            time_interval_extent([None, Some(TimeInterval::new(5, 6).unwrap())].into_iter()),
1✔
733
            None
734
        );
735
    }
1✔
736

737
    #[test]
738
    fn contains() {
1✔
739
        let time_interval_1 = TimeInterval::new(1, 2).unwrap();
1✔
740
        let time_interval_2 = TimeInterval::new(2, 3).unwrap();
1✔
741
        let time_interval_3 = TimeInterval::new(1, 3).unwrap();
1✔
742
        let time_interval_4 = TimeInterval::new_instant(1).unwrap();
1✔
743
        let time_interval_5 = TimeInterval::new_instant(3).unwrap();
1✔
744

745
        assert!(!time_interval_1.contains(&time_interval_2));
1✔
746
        assert!(time_interval_3.contains(&time_interval_1));
1✔
747
        assert!(time_interval_3.contains(&time_interval_2));
1✔
748
        assert!(time_interval_3.contains(&time_interval_3));
1✔
749
        assert!(time_interval_3.contains(&time_interval_4));
1✔
750
        assert!(!time_interval_3.contains(&time_interval_5));
1✔
751
    }
1✔
752

753
    #[test]
754
    fn arrow_builder_size() {
1✔
755
        for i in 0..10 {
11✔
756
            for capacity in [0, i] {
20✔
757
                let mut builder = TimeInterval::arrow_builder(capacity);
20✔
758

759
                for _ in 0..i {
90✔
760
                    builder.values().append_values(&[1, 2], &[true, true]);
90✔
761
                    builder.append(true);
90✔
762
                }
90✔
763

764
                assert_eq!(builder.value_length(), 2);
20✔
765
                assert_eq!(builder.len(), i);
20✔
766

767
                let builder_byte_size = TimeInterval::estimate_array_memory_size(&mut builder);
20✔
768

769
                let array = builder.finish_cloned();
20✔
770

771
                assert_eq!(builder_byte_size, array.get_array_memory_size(), "{i}");
20✔
772
            }
773
        }
774
    }
1✔
775
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc