springql_core/stream_engine/time/
timestamp.rs

1// This file is part of https://github.com/SpringQL/SpringQL which is licensed under MIT OR Apache-2.0. See file LICENSE-MIT or LICENSE-APACHE for full license details.
2
3//! Timestamp.
4
5mod system_timestamp;
6pub use system_timestamp::SystemTimestamp;
7
8use std::{
9    ops::{Add, Sub},
10    str::FromStr,
11};
12
13use anyhow::Context;
14use serde::{Deserialize, Serialize};
15
16use crate::{
17    api::error::{Result, SpringError},
18    mem_size::{chrono_naive_date_time_overhead_size, MemSize},
19    time::{DateTime, Duration, NaiveDateTime, MIN_DATETIME},
20};
21
22/// The minimum possible `Timestamp`.
23pub const MIN_TIMESTAMP: SpringTimestamp = SpringTimestamp(MIN_DATETIME);
24
25/// Timestamp in UTC. Serializable.
26#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash, Debug, Serialize, Deserialize, new)]
27pub struct SpringTimestamp(NaiveDateTime);
28
29impl MemSize for SpringTimestamp {
30    fn mem_size(&self) -> usize {
31        chrono_naive_date_time_overhead_size()
32    }
33}
34
35impl SpringTimestamp {
36    /// Note: `2262-04-11T23:47:16.854775804` is the maximum possible timestamp because it uses nano-sec unixtime internally.
37    pub fn floor(&self, resolution: Duration) -> Result<SpringTimestamp> {
38        let ts_nano = self.0.timestamp_nanos();
39        let resolution_nano = resolution.num_nanoseconds();
40        assert!(resolution_nano > 0);
41
42        let floor_ts_nano = (ts_nano / resolution_nano) * resolution_nano;
43
44        let floor_naive_date_time = {
45            let floor_ts_secs = floor_ts_nano / 1_000_000_000;
46            let floor_ts_nanos = floor_ts_nano % 1_000_000_000;
47            NaiveDateTime::from_timestamp(floor_ts_secs as i64, floor_ts_nanos as u32)
48                .map_err(SpringError::Time)?
49        };
50
51        Ok(SpringTimestamp(floor_naive_date_time))
52    }
53
54    /// Note: `2262-04-11T23:47:16.854775804` is the maximum possible timestamp because it uses nano-sec unixtime internally.
55    pub fn ceil(&self, resolution: Duration) -> Result<SpringTimestamp> {
56        let floor = self.floor(resolution)?;
57        if &floor == self {
58            Ok(floor)
59        } else {
60            Ok(floor + resolution)
61        }
62    }
63
64    fn try_parse_original(s: &str) -> Result<Self> {
65        let ndt = NaiveDateTime::parse_from_str(s)
66            .with_context(|| format!("failed to parse timestamp: {}", s))
67            .map_err(|e| SpringError::InvalidFormat {
68                s: s.to_string(),
69                source: e,
70            })?;
71        Ok(SpringTimestamp(ndt))
72    }
73    fn try_parse_rfc3339(s: &str) -> Result<Self> {
74        let dt = DateTime::parse_from_rfc3339(s)
75            .with_context(|| format!("failed to parse timestamp: {}", s))
76            .map_err(|e| SpringError::InvalidFormat {
77                s: s.to_string(),
78                source: e,
79            })?;
80        Ok(SpringTimestamp(dt.naive_utc()))
81    }
82}
83
84impl FromStr for SpringTimestamp {
85    type Err = SpringError;
86
87    /// Parse as RFC-3339 or `"%Y-%m-%d %H:%M:%S%.9f"` format.
88    fn from_str(s: &str) -> Result<Self> {
89        Self::try_parse_rfc3339(s).or_else(|_| Self::try_parse_original(s))
90    }
91}
92
93impl ToString for SpringTimestamp {
94    fn to_string(&self) -> String {
95        self.0.format()
96    }
97}
98
99impl Add<Duration> for SpringTimestamp {
100    type Output = Self;
101
102    fn add(self, rhs: Duration) -> Self::Output {
103        Self(self.0 + rhs)
104    }
105}
106impl Sub<Duration> for SpringTimestamp {
107    type Output = Self;
108
109    fn sub(self, rhs: Duration) -> Self::Output {
110        Self(self.0 - rhs)
111    }
112}
113
114impl Sub<SpringTimestamp> for SpringTimestamp {
115    type Output = Duration;
116
117    fn sub(self, rhs: SpringTimestamp) -> Self::Output {
118        self.0 - rhs.0
119    }
120}
121
122#[cfg(test)]
123mod tests {
124    use super::*;
125    use crate::api::error::Result;
126    use pretty_assertions::assert_eq;
127
128    #[test]
129    fn test_floor() {
130        fn t(ts: &str, resolution: Duration, expected: &str) {
131            let ts = SpringTimestamp::from_str(ts).unwrap();
132            let expected = SpringTimestamp::from_str(expected).unwrap();
133
134            let actual = ts.floor(resolution).unwrap();
135            assert_eq!(actual, expected);
136        }
137
138        t(
139            "2020-01-01 00:00:00.000000000",
140            Duration::seconds(1),
141            "2020-01-01 00:00:00.000000000",
142        );
143        t(
144            "2020-01-01 23:59:59.999999999",
145            Duration::seconds(1),
146            "2020-01-01 23:59:59.000000000",
147        );
148
149        t(
150            "2020-01-01 00:00:00.000000000",
151            Duration::minutes(1),
152            "2020-01-01 00:00:00.000000000",
153        );
154        t(
155            "2020-01-01 23:59:59.999999999",
156            Duration::minutes(1),
157            "2020-01-01 23:59:00.000000000",
158        );
159
160        t(
161            "2020-01-01 00:00:00.000000000",
162            Duration::hours(1),
163            "2020-01-01 00:00:00.000000000",
164        );
165        t(
166            "2020-01-01 23:59:59.999999999",
167            Duration::hours(1),
168            "2020-01-01 23:00:00.000000000",
169        );
170
171        t(
172            "2020-01-01 00:00:00.000000000",
173            Duration::days(1),
174            "2020-01-01 00:00:00.000000000",
175        );
176        t(
177            "2020-01-01 23:59:59.999999999",
178            Duration::days(1),
179            "2020-01-01 00:00:00.000000000",
180        );
181
182        t(
183            "2020-01-01 00:00:00.000000000",
184            Duration::milliseconds(1),
185            "2020-01-01 00:00:00.000000000",
186        );
187        t(
188            "2020-01-01 23:59:59.999999999",
189            Duration::milliseconds(1),
190            "2020-01-01 23:59:59.999000000",
191        );
192
193        t(
194            "2020-01-01 00:00:00.000000000",
195            Duration::microseconds(1),
196            "2020-01-01 00:00:00.000000000",
197        );
198        t(
199            "2020-01-01 23:59:59.999999999",
200            Duration::microseconds(1),
201            "2020-01-01 23:59:59.999999000",
202        );
203
204        t(
205            "2020-01-01 00:00:00.000000000",
206            Duration::nanoseconds(1),
207            "2020-01-01 00:00:00.000000000",
208        );
209        t(
210            "2020-01-01 23:59:59.999999999",
211            Duration::nanoseconds(1),
212            "2020-01-01 23:59:59.999999999",
213        );
214        t(
215            "2020-01-01 23:59:59.999999999",
216            Duration::nanoseconds(10),
217            "2020-01-01 23:59:59.999999990",
218        );
219    }
220
221    #[test]
222    fn test_ceil() {
223        fn t(ts: &str, resolution: Duration, expected: &str) {
224            let ts = SpringTimestamp::from_str(ts).unwrap();
225            let expected = SpringTimestamp::from_str(expected).unwrap();
226
227            let actual = ts.ceil(resolution).unwrap();
228            assert_eq!(actual, expected);
229        }
230
231        t(
232            "2020-01-01 00:00:00.000000000",
233            Duration::seconds(1),
234            "2020-01-01 00:00:00.000000000",
235        );
236        t(
237            "2020-01-01 00:00:00.000000001",
238            Duration::seconds(1),
239            "2020-01-01 00:00:01.000000000",
240        );
241        t(
242            "2020-01-01 23:59:59.999999999",
243            Duration::seconds(1),
244            "2020-01-02 00:00:00.000000000",
245        );
246
247        t(
248            "2020-01-01 00:00:00.000000000",
249            Duration::minutes(1),
250            "2020-01-01 00:00:00.000000000",
251        );
252        t(
253            "2020-01-01 00:00:00.000000001",
254            Duration::minutes(1),
255            "2020-01-01 00:01:00.000000000",
256        );
257
258        t(
259            "2020-01-01 00:00:00.000000000",
260            Duration::hours(1),
261            "2020-01-01 00:00:00.000000000",
262        );
263        t(
264            "2020-01-01 00:00:00.000000001",
265            Duration::hours(1),
266            "2020-01-01 01:00:00.000000000",
267        );
268
269        t(
270            "2020-01-01 00:00:00.000000000",
271            Duration::days(1),
272            "2020-01-01 00:00:00.000000000",
273        );
274        t(
275            "2020-01-01 00:00:00.000000001",
276            Duration::days(1),
277            "2020-01-02 00:00:00.000000000",
278        );
279
280        t(
281            "2020-01-01 00:00:00.000000000",
282            Duration::milliseconds(1),
283            "2020-01-01 00:00:00.000000000",
284        );
285        t(
286            "2020-01-01 00:00:00.000000001",
287            Duration::milliseconds(1),
288            "2020-01-01 00:00:00.001000000",
289        );
290
291        t(
292            "2020-01-01 00:00:00.000000000",
293            Duration::microseconds(1),
294            "2020-01-01 00:00:00.000000000",
295        );
296        t(
297            "2020-01-01 00:00:00.000000001",
298            Duration::microseconds(1),
299            "2020-01-01 00:00:00.000001000",
300        );
301
302        t(
303            "2020-01-01 00:00:00.000000000",
304            Duration::nanoseconds(1),
305            "2020-01-01 00:00:00.000000000",
306        );
307        t(
308            "2020-01-01 00:00:00.000000001",
309            Duration::nanoseconds(1),
310            "2020-01-01 00:00:00.000000001",
311        );
312        t(
313            "2020-01-01 00:00:00.000000001",
314            Duration::nanoseconds(10),
315            "2020-01-01 00:00:00.000000010",
316        );
317    }
318
319    #[test]
320    fn test_timestamp_ser_de() -> Result<()> {
321        let ts = vec![
322            "2021-10-22 14:00:14.000000000",
323            "2021-10-22 14:00:14.000000009",
324        ]
325        .into_iter()
326        .map(|s| s.parse())
327        .collect::<Result<Vec<_>>>()?;
328
329        for t in ts {
330            let ser = serde_json::to_string(&t).unwrap();
331            let de: SpringTimestamp = serde_json::from_str(&ser).unwrap();
332            assert_eq!(de, t);
333        }
334
335        Ok(())
336    }
337
338    #[test]
339    fn test_timestamp_parse_rfc3339() -> Result<()> {
340        let ts_rfc3339: SpringTimestamp = "2020-01-01T09:12:34.56789+09:00".parse()?;
341        let ts: SpringTimestamp = "2020-01-01 00:12:34.567890000".parse()?;
342        assert_eq!(ts_rfc3339, ts);
343
344        Ok(())
345    }
346}