common_time/
timestamp_millis.rs

1// Copyright 2023 Greptime Team
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15use std::cmp::Ordering;
16
17use crate::util::div_ceil;
18use crate::Timestamp;
19
20/// Unix timestamp in millisecond resolution.
21///
22/// Negative timestamp is allowed, which represents timestamp before '1970-01-01T00:00:00'.
23#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
24pub struct TimestampMillis(i64);
25
26impl TimestampMillis {
27    /// Positive infinity.
28    pub const INF: TimestampMillis = TimestampMillis::new(i64::MAX);
29    /// Maximum value of a timestamp.
30    ///
31    /// The maximum value of i64 is reserved for infinity.
32    pub const MAX: TimestampMillis = TimestampMillis::new(i64::MAX - 1);
33    /// Minimum value of a timestamp.
34    pub const MIN: TimestampMillis = TimestampMillis::new(i64::MIN);
35
36    /// Create a new timestamp from unix timestamp in milliseconds.
37    pub const fn new(ms: i64) -> TimestampMillis {
38        TimestampMillis(ms)
39    }
40
41    /// Returns the timestamp value as i64.
42    pub fn as_i64(&self) -> i64 {
43        self.0
44    }
45}
46
47impl From<i64> for TimestampMillis {
48    fn from(ms: i64) -> TimestampMillis {
49        TimestampMillis::new(ms)
50    }
51}
52
53impl From<TimestampMillis> for i64 {
54    fn from(ts: TimestampMillis) -> Self {
55        ts.0
56    }
57}
58
59impl PartialEq<i64> for TimestampMillis {
60    fn eq(&self, other: &i64) -> bool {
61        self.0 == *other
62    }
63}
64
65impl PartialEq<TimestampMillis> for i64 {
66    fn eq(&self, other: &TimestampMillis) -> bool {
67        *self == other.0
68    }
69}
70
71impl PartialOrd<i64> for TimestampMillis {
72    fn partial_cmp(&self, other: &i64) -> Option<Ordering> {
73        Some(self.0.cmp(other))
74    }
75}
76
77impl PartialOrd<TimestampMillis> for i64 {
78    fn partial_cmp(&self, other: &TimestampMillis) -> Option<Ordering> {
79        Some(self.cmp(&other.0))
80    }
81}
82
83pub trait BucketAligned: Sized {
84    /// Aligns the value by `bucket_duration` or `None` if underflow occurred.
85    ///
86    /// # Panics
87    /// Panics if `bucket_duration <= 0`.
88    fn align_by_bucket(self, bucket_duration: i64) -> Option<Self>;
89
90    /// Aligns the value by `bucket_duration` to ceil or `None` if overflow occurred.
91    ///
92    /// # Panics
93    /// Panics if `bucket_duration <= 0`.
94    fn align_to_ceil_by_bucket(self, bucket_duration: i64) -> Option<Self>;
95}
96
97impl BucketAligned for i64 {
98    fn align_by_bucket(self, bucket_duration: i64) -> Option<Self> {
99        assert!(bucket_duration > 0, "{}", bucket_duration);
100        self.checked_div_euclid(bucket_duration)
101            .and_then(|val| val.checked_mul(bucket_duration))
102    }
103
104    fn align_to_ceil_by_bucket(self, bucket_duration: i64) -> Option<Self> {
105        assert!(bucket_duration > 0, "{}", bucket_duration);
106        div_ceil(self, bucket_duration).checked_mul(bucket_duration)
107    }
108}
109
110impl BucketAligned for Timestamp {
111    fn align_by_bucket(self, bucket_duration: i64) -> Option<Self> {
112        assert!(bucket_duration > 0, "{}", bucket_duration);
113        let unit = self.unit();
114        self.value()
115            .align_by_bucket(bucket_duration)
116            .map(|val| Timestamp::new(val, unit))
117    }
118
119    fn align_to_ceil_by_bucket(self, bucket_duration: i64) -> Option<Self> {
120        assert!(bucket_duration > 0, "{}", bucket_duration);
121        let unit = self.unit();
122        self.value()
123            .align_to_ceil_by_bucket(bucket_duration)
124            .map(|val| Timestamp::new(val, unit))
125    }
126}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131
132    #[test]
133    fn test_timestamp() {
134        let ts = 123456;
135        let timestamp = TimestampMillis::from(ts);
136        assert_eq!(timestamp, ts);
137        assert_eq!(ts, timestamp);
138        assert_eq!(ts, timestamp.as_i64());
139
140        assert_ne!(TimestampMillis::new(0), timestamp);
141        assert!(TimestampMillis::new(-123) < TimestampMillis::new(0));
142        assert!(TimestampMillis::new(10) < 20);
143        assert!(10 < TimestampMillis::new(20));
144
145        assert_eq!(i64::MAX, TimestampMillis::INF);
146        assert_eq!(i64::MAX - 1, TimestampMillis::MAX);
147        assert_eq!(i64::MIN, TimestampMillis::MIN);
148    }
149
150    #[test]
151    fn test_align_by_bucket() {
152        let bucket = 100;
153        assert_eq!(
154            Timestamp::new_millisecond(0),
155            Timestamp::new_millisecond(0)
156                .align_by_bucket(bucket)
157                .unwrap()
158        );
159        assert_eq!(
160            Timestamp::new_millisecond(0),
161            Timestamp::new_millisecond(1)
162                .align_by_bucket(bucket)
163                .unwrap()
164        );
165        assert_eq!(
166            Timestamp::new_millisecond(0),
167            Timestamp::new_millisecond(99)
168                .align_by_bucket(bucket)
169                .unwrap()
170        );
171        assert_eq!(
172            Timestamp::new_millisecond(100),
173            Timestamp::new_millisecond(100)
174                .align_by_bucket(bucket)
175                .unwrap()
176        );
177        assert_eq!(
178            Timestamp::new_millisecond(100),
179            Timestamp::new_millisecond(199)
180                .align_by_bucket(bucket)
181                .unwrap()
182        );
183
184        assert_eq!(
185            Timestamp::new_millisecond(0),
186            Timestamp::new_millisecond(i64::MAX - 1)
187                .align_by_bucket(i64::MAX)
188                .unwrap()
189        );
190
191        assert_eq!(
192            Timestamp::new_millisecond(i64::MAX),
193            Timestamp::new_millisecond(i64::MAX)
194                .align_by_bucket(i64::MAX)
195                .unwrap()
196        );
197
198        assert_eq!(
199            None,
200            Timestamp::new_millisecond(i64::MIN).align_by_bucket(bucket)
201        );
202    }
203
204    #[test]
205    fn test_align_to_ceil() {
206        assert_eq!(None, i64::MAX.align_to_ceil_by_bucket(10));
207        assert_eq!(
208            Some(i64::MAX - (i64::MAX % 10)),
209            (i64::MAX - (i64::MAX % 10)).align_to_ceil_by_bucket(10)
210        );
211        assert_eq!(Some(i64::MAX), i64::MAX.align_to_ceil_by_bucket(1));
212        assert_eq!(Some(i64::MAX), i64::MAX.align_to_ceil_by_bucket(1));
213        assert_eq!(Some(i64::MAX), i64::MAX.align_to_ceil_by_bucket(i64::MAX));
214
215        assert_eq!(
216            Some(i64::MIN - (i64::MIN % 10)),
217            i64::MIN.align_to_ceil_by_bucket(10)
218        );
219        assert_eq!(Some(i64::MIN), i64::MIN.align_to_ceil_by_bucket(1));
220
221        assert_eq!(Some(3), 1i64.align_to_ceil_by_bucket(3));
222        assert_eq!(Some(3), 3i64.align_to_ceil_by_bucket(3));
223        assert_eq!(Some(6), 4i64.align_to_ceil_by_bucket(3));
224        assert_eq!(Some(0), 0i64.align_to_ceil_by_bucket(3));
225        assert_eq!(Some(0), (-1i64).align_to_ceil_by_bucket(3));
226        assert_eq!(Some(0), (-2i64).align_to_ceil_by_bucket(3));
227        assert_eq!(Some(-3), (-3i64).align_to_ceil_by_bucket(3));
228        assert_eq!(Some(-3), (-4i64).align_to_ceil_by_bucket(3));
229    }
230}