diff --git a/Cargo.lock b/Cargo.lock index d8e2355e5b..6129b34e80 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -639,6 +639,10 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "common-time" +version = "0.1.0" + [[package]] name = "console-api" version = "0.2.0" diff --git a/Cargo.toml b/Cargo.toml index a0d58a55f8..2b13f34b41 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "src/common/function", "src/common/runtime", "src/common/telemetry", + "src/common/time", "src/common/query", "src/common/recordbatch", "src/cmd", diff --git a/src/common/time/Cargo.toml b/src/common/time/Cargo.toml new file mode 100644 index 0000000000..9652cb17c3 --- /dev/null +++ b/src/common/time/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "common-time" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] diff --git a/src/common/time/src/lib.rs b/src/common/time/src/lib.rs new file mode 100644 index 0000000000..84b9bfaf12 --- /dev/null +++ b/src/common/time/src/lib.rs @@ -0,0 +1,5 @@ +pub mod range; +pub mod timestamp; + +pub use range::RangeMillis; +pub use timestamp::TimestampMillis; diff --git a/src/common/time/src/range.rs b/src/common/time/src/range.rs new file mode 100644 index 0000000000..ed5222acd5 --- /dev/null +++ b/src/common/time/src/range.rs @@ -0,0 +1,95 @@ +use crate::timestamp::TimestampMillis; + +/// A half-open time range. +/// +/// The time range contains all timestamp `ts` that `ts >= start` and `ts < end`. It is +/// empty if `start == end`. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TimeRange { + start: T, + end: T, +} + +impl TimeRange { + /// Create a new range that contains timestamp in `[start, end)`. + /// + /// Returns `None` if `start` > `end`. + pub fn new>(start: U, end: U) -> Option> { + if start <= end { + let (start, end) = (start.into(), end.into()); + Some(TimeRange { start, end }) + } else { + None + } + } + + /// Returns the lower bound of the range (inclusive). + #[inline] + pub fn start(&self) -> &T { + &self.start + } + + /// Returns the upper bound of the range (exclusive). + #[inline] + pub fn end(&self) -> &T { + &self.end + } + + /// Returns true if `timestamp` is contained in the range. + pub fn contains>(&self, timestamp: &U) -> bool { + *timestamp >= self.start && *timestamp < self.end + } +} + +impl TimeRange { + /// Returns true if the range contains no timestamps. + #[inline] + pub fn is_empty(&self) -> bool { + self.start >= self.end + } +} + +/// Time range in milliseconds. +pub type RangeMillis = TimeRange; + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_new_range() { + let (start, end) = (TimestampMillis::new(0), TimestampMillis::new(100)); + let range = RangeMillis::new(start, end).unwrap(); + + assert_eq!(start, *range.start()); + assert_eq!(end, *range.end()); + + let range2 = RangeMillis::new(0, 100).unwrap(); + assert_eq!(range, range2); + + let range_eq = RangeMillis::new(123, 123).unwrap(); + assert_eq!(range_eq.start(), range_eq.end()); + + assert_eq!(None, RangeMillis::new(1, 0)); + } + + #[test] + fn test_range_contains() { + let range = RangeMillis::new(-10, 10).unwrap(); + assert!(!range.is_empty()); + assert!(range.contains(&-10)); + assert!(range.contains(&0)); + assert!(range.contains(&9)); + assert!(!range.contains(&10)); + + let range = RangeMillis::new(i64::MIN, i64::MAX).unwrap(); + assert!(!range.is_empty()); + assert!(range.contains(&TimestampMillis::MIN)); + assert!(range.contains(&TimestampMillis::MAX)); + assert!(!range.contains(&TimestampMillis::INF)); + + let range = RangeMillis::new(0, 0).unwrap(); + assert!(range.is_empty()); + assert!(!range.contains(&0)); + } +} diff --git a/src/common/time/src/timestamp.rs b/src/common/time/src/timestamp.rs new file mode 100644 index 0000000000..93cb079b0d --- /dev/null +++ b/src/common/time/src/timestamp.rs @@ -0,0 +1,73 @@ +use std::cmp::Ordering; + +/// Unix timestamp in millisecond resolution. +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TimestampMillis(i64); + +impl TimestampMillis { + /// Positive infinity. + pub const INF: TimestampMillis = TimestampMillis::new(i64::MAX); + /// Maximum value of a timestamp. + /// + /// The maximum value of i64 is reserved for infinity. + pub const MAX: TimestampMillis = TimestampMillis::new(i64::MAX - 1); + /// Minimum value of a timestamp. + pub const MIN: TimestampMillis = TimestampMillis::new(i64::MIN); + + /// Create a new timestamp from unix timestamp in milliseconds. + pub const fn new(ms: i64) -> TimestampMillis { + TimestampMillis(ms) + } +} + +impl From for TimestampMillis { + fn from(ms: i64) -> TimestampMillis { + TimestampMillis::new(ms) + } +} + +impl PartialEq for TimestampMillis { + fn eq(&self, other: &i64) -> bool { + self.0 == *other + } +} + +impl PartialEq for i64 { + fn eq(&self, other: &TimestampMillis) -> bool { + *self == other.0 + } +} + +impl PartialOrd for TimestampMillis { + fn partial_cmp(&self, other: &i64) -> Option { + Some(self.0.cmp(other)) + } +} + +impl PartialOrd for i64 { + fn partial_cmp(&self, other: &TimestampMillis) -> Option { + Some(self.cmp(&other.0)) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_timestamp() { + let ts = 123456; + let timestamp = TimestampMillis::from(ts); + assert_eq!(timestamp, ts); + assert_eq!(ts, timestamp); + + assert_ne!(TimestampMillis::new(0), timestamp); + assert!(TimestampMillis::new(-123) < TimestampMillis::new(0)); + assert!(TimestampMillis::new(10) < 20); + assert!(10 < TimestampMillis::new(20)); + + assert_eq!(i64::MAX, TimestampMillis::INF); + assert_eq!(i64::MAX - 1, TimestampMillis::MAX); + assert_eq!(i64::MIN, TimestampMillis::MIN); + } +}