springql_core/stream_engine/time/
timestamp.rs1mod 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
22pub const MIN_TIMESTAMP: SpringTimestamp = SpringTimestamp(MIN_DATETIME);
24
25#[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 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 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 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}