common_time/
time.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;
16use std::hash::{Hash, Hasher};
17
18use chrono::{NaiveDateTime, NaiveTime, TimeZone as ChronoTimeZone, Utc};
19use serde::{Deserialize, Serialize};
20
21use crate::timestamp::TimeUnit;
22use crate::timezone::{get_timezone, Timezone};
23
24/// Time value, represents the elapsed time since midnight in the unit of `TimeUnit`.
25#[derive(Debug, Clone, Default, Copy, Serialize, Deserialize)]
26pub struct Time {
27    value: i64,
28    unit: TimeUnit,
29}
30
31impl Time {
32    /// Creates the time by value and `TimeUnit`.
33    pub fn new(value: i64, unit: TimeUnit) -> Self {
34        Self { value, unit }
35    }
36
37    /// Creates the time in nanosecond.
38    pub fn new_nanosecond(value: i64) -> Self {
39        Self {
40            value,
41            unit: TimeUnit::Nanosecond,
42        }
43    }
44
45    /// Creates the time in second.
46    pub fn new_second(value: i64) -> Self {
47        Self {
48            value,
49            unit: TimeUnit::Second,
50        }
51    }
52
53    /// Creates the time in millisecond.
54    pub fn new_millisecond(value: i64) -> Self {
55        Self {
56            value,
57            unit: TimeUnit::Millisecond,
58        }
59    }
60
61    /// Creates the time in microsecond.
62    pub fn new_microsecond(value: i64) -> Self {
63        Self {
64            value,
65            unit: TimeUnit::Microsecond,
66        }
67    }
68
69    /// Returns the `TimeUnit` of the time.
70    pub fn unit(&self) -> &TimeUnit {
71        &self.unit
72    }
73
74    /// Returns the value of the time.
75    pub fn value(&self) -> i64 {
76        self.value
77    }
78
79    /// Convert a time to given time unit.
80    /// Return `None` if conversion causes overflow.
81    pub fn convert_to(&self, unit: TimeUnit) -> Option<Time> {
82        if self.unit().factor() >= unit.factor() {
83            let mul = self.unit().factor() / unit.factor();
84            let value = self.value.checked_mul(mul as i64)?;
85            Some(Time::new(value, unit))
86        } else {
87            let mul = unit.factor() / self.unit().factor();
88            Some(Time::new(self.value.div_euclid(mul as i64), unit))
89        }
90    }
91
92    /// Split a [Time] into seconds part and nanoseconds part.
93    /// Notice the seconds part of split result is always rounded down to floor.
94    fn split(&self) -> (i64, u32) {
95        let sec_mul = (TimeUnit::Second.factor() / self.unit.factor()) as i64;
96        let nsec_mul = (self.unit.factor() / TimeUnit::Nanosecond.factor()) as i64;
97
98        let sec_div = self.value.div_euclid(sec_mul);
99        let sec_mod = self.value.rem_euclid(sec_mul);
100        // safety:  the max possible value of `sec_mod` is 999,999,999
101        let nsec = u32::try_from(sec_mod * nsec_mul).unwrap();
102        (sec_div, nsec)
103    }
104
105    /// Format Time to ISO8601 string. If the time exceeds what chrono time can
106    /// represent, this function simply print the time unit and value in plain string.
107    pub fn to_iso8601_string(&self) -> String {
108        self.as_formatted_string("%H:%M:%S%.f%z", None)
109    }
110
111    /// Format Time for given timezone.
112    /// When timezone is None, using system timezone by default.
113    pub fn to_timezone_aware_string(&self, tz: Option<&Timezone>) -> String {
114        self.as_formatted_string("%H:%M:%S%.f", tz)
115    }
116
117    fn as_formatted_string(self, pattern: &str, timezone: Option<&Timezone>) -> String {
118        if let Some(time) = self.to_chrono_time() {
119            let date = Utc::now().date_naive();
120            let datetime = NaiveDateTime::new(date, time);
121            match get_timezone(timezone) {
122                Timezone::Offset(offset) => {
123                    format!("{}", offset.from_utc_datetime(&datetime).format(pattern))
124                }
125                Timezone::Named(tz) => {
126                    format!("{}", tz.from_utc_datetime(&datetime).format(pattern))
127                }
128            }
129        } else {
130            format!("[Time{}: {}]", self.unit, self.value)
131        }
132    }
133
134    /// Cast the [Time] into chrono NaiveDateTime
135    pub fn to_chrono_time(&self) -> Option<NaiveTime> {
136        let (sec, nsec) = self.split();
137        if let Ok(sec) = u32::try_from(sec) {
138            NaiveTime::from_num_seconds_from_midnight_opt(sec, nsec)
139        } else {
140            None
141        }
142    }
143
144    pub fn negative(mut self) -> Self {
145        self.value = -self.value;
146        self
147    }
148}
149
150impl From<i64> for Time {
151    fn from(v: i64) -> Self {
152        Self {
153            value: v,
154            unit: TimeUnit::Millisecond,
155        }
156    }
157}
158
159impl From<Time> for i64 {
160    fn from(t: Time) -> Self {
161        t.value
162    }
163}
164
165impl From<Time> for i32 {
166    fn from(t: Time) -> Self {
167        t.value as i32
168    }
169}
170
171impl From<Time> for serde_json::Value {
172    fn from(d: Time) -> Self {
173        serde_json::Value::String(d.to_iso8601_string())
174    }
175}
176
177impl PartialOrd for Time {
178    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
179        Some(self.cmp(other))
180    }
181}
182
183impl Ord for Time {
184    fn cmp(&self, other: &Self) -> Ordering {
185        // fast path: most comparisons use the same unit.
186        if self.unit == other.unit {
187            return self.value.cmp(&other.value);
188        }
189
190        let (s_sec, s_nsec) = self.split();
191        let (o_sec, o_nsec) = other.split();
192        match s_sec.cmp(&o_sec) {
193            Ordering::Less => Ordering::Less,
194            Ordering::Greater => Ordering::Greater,
195            Ordering::Equal => s_nsec.cmp(&o_nsec),
196        }
197    }
198}
199
200impl PartialEq for Time {
201    fn eq(&self, other: &Self) -> bool {
202        self.cmp(other) == Ordering::Equal
203    }
204}
205
206impl Eq for Time {}
207
208impl Hash for Time {
209    fn hash<H: Hasher>(&self, state: &mut H) {
210        let (sec, nsec) = self.split();
211        state.write_i64(sec);
212        state.write_u32(nsec);
213    }
214}
215
216#[cfg(test)]
217mod tests {
218    use std::collections::hash_map::DefaultHasher;
219
220    use serde_json::Value;
221
222    use super::*;
223    use crate::timezone::set_default_timezone;
224
225    #[test]
226    fn test_time() {
227        let t = Time::new(1, TimeUnit::Millisecond);
228        assert_eq!(TimeUnit::Millisecond, *t.unit());
229        assert_eq!(1, t.value());
230        assert_eq!(Time::new(1000, TimeUnit::Microsecond), t);
231        assert!(t > Time::new(999, TimeUnit::Microsecond));
232    }
233
234    #[test]
235    fn test_cmp_time() {
236        let t1 = Time::new(0, TimeUnit::Millisecond);
237        let t2 = Time::new(0, TimeUnit::Second);
238        assert_eq!(t2, t1);
239
240        let t1 = Time::new(100_100, TimeUnit::Millisecond);
241        let t2 = Time::new(100, TimeUnit::Second);
242        assert!(t1 > t2);
243
244        let t1 = Time::new(10_010_001, TimeUnit::Millisecond);
245        let t2 = Time::new(100, TimeUnit::Second);
246        assert!(t1 > t2);
247
248        let t1 = Time::new(10_010_001, TimeUnit::Nanosecond);
249        let t2 = Time::new(100, TimeUnit::Second);
250        assert!(t1 < t2);
251
252        let t1 = Time::new(i64::MAX / 1000 * 1000, TimeUnit::Millisecond);
253        let t2 = Time::new(i64::MAX / 1000, TimeUnit::Second);
254        assert_eq!(t2, t1);
255
256        let t1 = Time::new(i64::MAX, TimeUnit::Millisecond);
257        let t2 = Time::new(i64::MAX / 1000 + 1, TimeUnit::Second);
258        assert!(t2 > t1);
259
260        let t1 = Time::new(i64::MAX, TimeUnit::Millisecond);
261        let t2 = Time::new(i64::MAX / 1000, TimeUnit::Second);
262        assert!(t2 < t1);
263
264        let t1 = Time::new(10_010_001, TimeUnit::Millisecond);
265        let t2 = Time::new(100, TimeUnit::Second);
266        assert!(t1 > t2);
267
268        let t1 = Time::new(-100 * 10_001, TimeUnit::Millisecond);
269        let t2 = Time::new(-100, TimeUnit::Second);
270        assert!(t2 > t1);
271    }
272
273    fn check_hash_eq(t1: Time, t2: Time) {
274        let mut hasher = DefaultHasher::new();
275        t1.hash(&mut hasher);
276        let t1_hash = hasher.finish();
277
278        let mut hasher = DefaultHasher::new();
279        t2.hash(&mut hasher);
280        let t2_hash = hasher.finish();
281        assert_eq!(t2_hash, t1_hash);
282    }
283
284    #[test]
285    fn test_hash() {
286        check_hash_eq(
287            Time::new(0, TimeUnit::Millisecond),
288            Time::new(0, TimeUnit::Second),
289        );
290        check_hash_eq(
291            Time::new(1000, TimeUnit::Millisecond),
292            Time::new(1, TimeUnit::Second),
293        );
294        check_hash_eq(
295            Time::new(1_000_000, TimeUnit::Microsecond),
296            Time::new(1, TimeUnit::Second),
297        );
298        check_hash_eq(
299            Time::new(1_000_000_000, TimeUnit::Nanosecond),
300            Time::new(1, TimeUnit::Second),
301        );
302    }
303
304    #[test]
305    pub fn test_from_i64() {
306        let t: Time = 42.into();
307        assert_eq!(42, t.value());
308        assert_eq!(TimeUnit::Millisecond, *t.unit());
309    }
310
311    #[test]
312    fn test_to_iso8601_string() {
313        set_default_timezone(Some("+10:00")).unwrap();
314        let time_millis = 1000001;
315        let ts = Time::new_millisecond(time_millis);
316        assert_eq!("10:16:40.001+1000", ts.to_iso8601_string());
317
318        let time_millis = 1000;
319        let ts = Time::new_millisecond(time_millis);
320        assert_eq!("10:00:01+1000", ts.to_iso8601_string());
321
322        let time_millis = 1;
323        let ts = Time::new_millisecond(time_millis);
324        assert_eq!("10:00:00.001+1000", ts.to_iso8601_string());
325
326        let time_seconds = 9 * 3600;
327        let ts = Time::new_second(time_seconds);
328        assert_eq!("19:00:00+1000", ts.to_iso8601_string());
329
330        let time_seconds = 23 * 3600;
331        let ts = Time::new_second(time_seconds);
332        assert_eq!("09:00:00+1000", ts.to_iso8601_string());
333    }
334
335    #[test]
336    fn test_serialize_to_json_value() {
337        set_default_timezone(Some("+10:00")).unwrap();
338        assert_eq!(
339            "10:00:01+1000",
340            match serde_json::Value::from(Time::new(1, TimeUnit::Second)) {
341                Value::String(s) => s,
342                _ => unreachable!(),
343            }
344        );
345
346        assert_eq!(
347            "10:00:00.001+1000",
348            match serde_json::Value::from(Time::new(1, TimeUnit::Millisecond)) {
349                Value::String(s) => s,
350                _ => unreachable!(),
351            }
352        );
353
354        assert_eq!(
355            "10:00:00.000001+1000",
356            match serde_json::Value::from(Time::new(1, TimeUnit::Microsecond)) {
357                Value::String(s) => s,
358                _ => unreachable!(),
359            }
360        );
361
362        assert_eq!(
363            "10:00:00.000000001+1000",
364            match serde_json::Value::from(Time::new(1, TimeUnit::Nanosecond)) {
365                Value::String(s) => s,
366                _ => unreachable!(),
367            }
368        );
369    }
370
371    #[test]
372    fn test_to_timezone_aware_string() {
373        set_default_timezone(Some("+10:00")).unwrap();
374
375        assert_eq!(
376            "10:00:00.001",
377            Time::new(1, TimeUnit::Millisecond).to_timezone_aware_string(None)
378        );
379        std::env::set_var("TZ", "Asia/Shanghai");
380        assert_eq!(
381            "08:00:00.001",
382            Time::new(1, TimeUnit::Millisecond)
383                .to_timezone_aware_string(Some(&Timezone::from_tz_string("SYSTEM").unwrap()))
384        );
385        assert_eq!(
386            "08:00:00.001",
387            Time::new(1, TimeUnit::Millisecond)
388                .to_timezone_aware_string(Some(&Timezone::from_tz_string("+08:00").unwrap()))
389        );
390        assert_eq!(
391            "07:00:00.001",
392            Time::new(1, TimeUnit::Millisecond)
393                .to_timezone_aware_string(Some(&Timezone::from_tz_string("+07:00").unwrap()))
394        );
395        assert_eq!(
396            "23:00:00.001",
397            Time::new(1, TimeUnit::Millisecond)
398                .to_timezone_aware_string(Some(&Timezone::from_tz_string("-01:00").unwrap()))
399        );
400        assert_eq!(
401            "08:00:00.001",
402            Time::new(1, TimeUnit::Millisecond).to_timezone_aware_string(Some(
403                &Timezone::from_tz_string("Asia/Shanghai").unwrap()
404            ))
405        );
406        assert_eq!(
407            "00:00:00.001",
408            Time::new(1, TimeUnit::Millisecond)
409                .to_timezone_aware_string(Some(&Timezone::from_tz_string("UTC").unwrap()))
410        );
411        assert_eq!(
412            "03:00:00.001",
413            Time::new(1, TimeUnit::Millisecond).to_timezone_aware_string(Some(
414                &Timezone::from_tz_string("Europe/Moscow").unwrap()
415            ))
416        );
417    }
418}