Skip to main content

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::{Timezone, get_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    pub fn checked_negative(mut self) -> Option<Self> {
150        self.value = self.value.checked_neg()?;
151        Some(self)
152    }
153}
154
155impl From<i64> for Time {
156    fn from(v: i64) -> Self {
157        Self {
158            value: v,
159            unit: TimeUnit::Millisecond,
160        }
161    }
162}
163
164impl From<Time> for i64 {
165    fn from(t: Time) -> Self {
166        t.value
167    }
168}
169
170impl From<Time> for i32 {
171    fn from(t: Time) -> Self {
172        t.value as i32
173    }
174}
175
176impl From<Time> for serde_json::Value {
177    fn from(d: Time) -> Self {
178        serde_json::Value::String(d.to_iso8601_string())
179    }
180}
181
182impl PartialOrd for Time {
183    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
184        Some(self.cmp(other))
185    }
186}
187
188impl Ord for Time {
189    fn cmp(&self, other: &Self) -> Ordering {
190        // fast path: most comparisons use the same unit.
191        if self.unit == other.unit {
192            return self.value.cmp(&other.value);
193        }
194
195        let (s_sec, s_nsec) = self.split();
196        let (o_sec, o_nsec) = other.split();
197        match s_sec.cmp(&o_sec) {
198            Ordering::Less => Ordering::Less,
199            Ordering::Greater => Ordering::Greater,
200            Ordering::Equal => s_nsec.cmp(&o_nsec),
201        }
202    }
203}
204
205impl PartialEq for Time {
206    fn eq(&self, other: &Self) -> bool {
207        self.cmp(other) == Ordering::Equal
208    }
209}
210
211impl Eq for Time {}
212
213impl Hash for Time {
214    fn hash<H: Hasher>(&self, state: &mut H) {
215        let (sec, nsec) = self.split();
216        state.write_i64(sec);
217        state.write_u32(nsec);
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use std::collections::hash_map::DefaultHasher;
224
225    use serde_json::Value;
226
227    use super::*;
228    use crate::timezone::set_default_timezone;
229
230    #[test]
231    fn test_time() {
232        let t = Time::new(1, TimeUnit::Millisecond);
233        assert_eq!(TimeUnit::Millisecond, *t.unit());
234        assert_eq!(1, t.value());
235        assert_eq!(Time::new(1000, TimeUnit::Microsecond), t);
236        assert!(t > Time::new(999, TimeUnit::Microsecond));
237    }
238
239    #[test]
240    fn test_cmp_time() {
241        let t1 = Time::new(0, TimeUnit::Millisecond);
242        let t2 = Time::new(0, TimeUnit::Second);
243        assert_eq!(t2, t1);
244
245        let t1 = Time::new(100_100, TimeUnit::Millisecond);
246        let t2 = Time::new(100, TimeUnit::Second);
247        assert!(t1 > t2);
248
249        let t1 = Time::new(10_010_001, TimeUnit::Millisecond);
250        let t2 = Time::new(100, TimeUnit::Second);
251        assert!(t1 > t2);
252
253        let t1 = Time::new(10_010_001, TimeUnit::Nanosecond);
254        let t2 = Time::new(100, TimeUnit::Second);
255        assert!(t1 < t2);
256
257        let t1 = Time::new(i64::MAX / 1000 * 1000, TimeUnit::Millisecond);
258        let t2 = Time::new(i64::MAX / 1000, TimeUnit::Second);
259        assert_eq!(t2, t1);
260
261        let t1 = Time::new(i64::MAX, TimeUnit::Millisecond);
262        let t2 = Time::new(i64::MAX / 1000 + 1, TimeUnit::Second);
263        assert!(t2 > t1);
264
265        let t1 = Time::new(i64::MAX, TimeUnit::Millisecond);
266        let t2 = Time::new(i64::MAX / 1000, TimeUnit::Second);
267        assert!(t2 < t1);
268
269        let t1 = Time::new(10_010_001, TimeUnit::Millisecond);
270        let t2 = Time::new(100, TimeUnit::Second);
271        assert!(t1 > t2);
272
273        let t1 = Time::new(-100 * 10_001, TimeUnit::Millisecond);
274        let t2 = Time::new(-100, TimeUnit::Second);
275        assert!(t2 > t1);
276    }
277
278    fn check_hash_eq(t1: Time, t2: Time) {
279        let mut hasher = DefaultHasher::new();
280        t1.hash(&mut hasher);
281        let t1_hash = hasher.finish();
282
283        let mut hasher = DefaultHasher::new();
284        t2.hash(&mut hasher);
285        let t2_hash = hasher.finish();
286        assert_eq!(t2_hash, t1_hash);
287    }
288
289    #[test]
290    fn test_hash() {
291        check_hash_eq(
292            Time::new(0, TimeUnit::Millisecond),
293            Time::new(0, TimeUnit::Second),
294        );
295        check_hash_eq(
296            Time::new(1000, TimeUnit::Millisecond),
297            Time::new(1, TimeUnit::Second),
298        );
299        check_hash_eq(
300            Time::new(1_000_000, TimeUnit::Microsecond),
301            Time::new(1, TimeUnit::Second),
302        );
303        check_hash_eq(
304            Time::new(1_000_000_000, TimeUnit::Nanosecond),
305            Time::new(1, TimeUnit::Second),
306        );
307    }
308
309    #[test]
310    pub fn test_from_i64() {
311        let t: Time = 42.into();
312        assert_eq!(42, t.value());
313        assert_eq!(TimeUnit::Millisecond, *t.unit());
314    }
315
316    #[test]
317    fn test_to_iso8601_string() {
318        set_default_timezone(Some("+10:00")).unwrap();
319        let time_millis = 1000001;
320        let ts = Time::new_millisecond(time_millis);
321        assert_eq!("10:16:40.001+1000", ts.to_iso8601_string());
322
323        let time_millis = 1000;
324        let ts = Time::new_millisecond(time_millis);
325        assert_eq!("10:00:01+1000", ts.to_iso8601_string());
326
327        let time_millis = 1;
328        let ts = Time::new_millisecond(time_millis);
329        assert_eq!("10:00:00.001+1000", ts.to_iso8601_string());
330
331        let time_seconds = 9 * 3600;
332        let ts = Time::new_second(time_seconds);
333        assert_eq!("19:00:00+1000", ts.to_iso8601_string());
334
335        let time_seconds = 23 * 3600;
336        let ts = Time::new_second(time_seconds);
337        assert_eq!("09:00:00+1000", ts.to_iso8601_string());
338    }
339
340    #[test]
341    fn test_serialize_to_json_value() {
342        set_default_timezone(Some("+10:00")).unwrap();
343        assert_eq!(
344            "10:00:01+1000",
345            match serde_json::Value::from(Time::new(1, TimeUnit::Second)) {
346                Value::String(s) => s,
347                _ => unreachable!(),
348            }
349        );
350
351        assert_eq!(
352            "10:00:00.001+1000",
353            match serde_json::Value::from(Time::new(1, TimeUnit::Millisecond)) {
354                Value::String(s) => s,
355                _ => unreachable!(),
356            }
357        );
358
359        assert_eq!(
360            "10:00:00.000001+1000",
361            match serde_json::Value::from(Time::new(1, TimeUnit::Microsecond)) {
362                Value::String(s) => s,
363                _ => unreachable!(),
364            }
365        );
366
367        assert_eq!(
368            "10:00:00.000000001+1000",
369            match serde_json::Value::from(Time::new(1, TimeUnit::Nanosecond)) {
370                Value::String(s) => s,
371                _ => unreachable!(),
372            }
373        );
374    }
375
376    #[test]
377    fn test_to_timezone_aware_string() {
378        set_default_timezone(Some("+10:00")).unwrap();
379
380        assert_eq!(
381            "10:00:00.001",
382            Time::new(1, TimeUnit::Millisecond).to_timezone_aware_string(None)
383        );
384        unsafe {
385            std::env::set_var("TZ", "Asia/Shanghai");
386        }
387        assert_eq!(
388            "08:00:00.001",
389            Time::new(1, TimeUnit::Millisecond)
390                .to_timezone_aware_string(Some(&Timezone::from_tz_string("SYSTEM").unwrap()))
391        );
392        assert_eq!(
393            "08:00:00.001",
394            Time::new(1, TimeUnit::Millisecond)
395                .to_timezone_aware_string(Some(&Timezone::from_tz_string("+08:00").unwrap()))
396        );
397        assert_eq!(
398            "07:00:00.001",
399            Time::new(1, TimeUnit::Millisecond)
400                .to_timezone_aware_string(Some(&Timezone::from_tz_string("+07:00").unwrap()))
401        );
402        assert_eq!(
403            "23:00:00.001",
404            Time::new(1, TimeUnit::Millisecond)
405                .to_timezone_aware_string(Some(&Timezone::from_tz_string("-01:00").unwrap()))
406        );
407        assert_eq!(
408            "08:00:00.001",
409            Time::new(1, TimeUnit::Millisecond).to_timezone_aware_string(Some(
410                &Timezone::from_tz_string("Asia/Shanghai").unwrap()
411            ))
412        );
413        assert_eq!(
414            "00:00:00.001",
415            Time::new(1, TimeUnit::Millisecond)
416                .to_timezone_aware_string(Some(&Timezone::from_tz_string("UTC").unwrap()))
417        );
418        assert_eq!(
419            "03:00:00.001",
420            Time::new(1, TimeUnit::Millisecond).to_timezone_aware_string(Some(
421                &Timezone::from_tz_string("Europe/Moscow").unwrap()
422            ))
423        );
424    }
425}