1use core::{fmt, ops};
13
14#[cfg(feature = "serde-support")]
15use serde::{Deserialize, Serialize};
16
17#[repr(transparent)]
23#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
24#[cfg_attr(feature = "serde-support", derive(Serialize, Deserialize))]
25pub struct UtcTimeStamp(i64);
26
27impl fmt::Display for UtcTimeStamp {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 chrono::DateTime::<chrono::Utc>::from(*self).fmt(f)
31 }
32}
33
34impl fmt::Debug for UtcTimeStamp {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 write!(f, "UtcTimeStamp({})", self.0)
37 }
38}
39
40impl From<chrono::DateTime<chrono::Utc>> for UtcTimeStamp {
42 fn from(other: chrono::DateTime<chrono::Utc>) -> Self {
43 Self(other.timestamp_millis())
44 }
45}
46
47impl From<UtcTimeStamp> for chrono::DateTime<chrono::Utc> {
49 fn from(other: UtcTimeStamp) -> Self {
50 let sec = other.0 / 1000;
51 let ns = (other.0 % 1000 * 1_000_000) as u32;
52 let naive = chrono::NaiveDateTime::from_timestamp(sec, ns);
53 chrono::DateTime::<chrono::Utc>::from_utc(naive, chrono::Utc)
54 }
55}
56
57impl UtcTimeStamp {
58 #[inline]
60 pub const fn zero() -> Self {
61 UtcTimeStamp(0)
62 }
63
64 pub fn now() -> Self {
66 chrono::Utc::now().into()
67 }
68
69 #[inline]
71 pub const fn from_milliseconds(int: i64) -> Self {
72 UtcTimeStamp(int)
73 }
74
75 #[inline]
77 pub const fn from_seconds(int: i64) -> Self {
78 UtcTimeStamp(int * 1000)
79 }
80
81 #[inline]
83 pub const fn as_milliseconds(self) -> i64 {
84 self.0
85 }
86
87 pub const fn align_to(self, freq: TimeDelta) -> UtcTimeStamp {
89 self.align_to_anchored(UtcTimeStamp::zero(), freq)
90 }
91
92 pub const fn align_to_anchored(self, anchor: UtcTimeStamp, freq: TimeDelta) -> UtcTimeStamp {
94 UtcTimeStamp((self.0 - anchor.0) / freq.0 * freq.0 + anchor.0)
95 }
96
97 #[inline]
99 pub const fn is_zero(self) -> bool {
100 self.0 == 0
101 }
102}
103
104impl ops::Add<TimeDelta> for UtcTimeStamp {
106 type Output = UtcTimeStamp;
107
108 fn add(self, rhs: TimeDelta) -> Self::Output {
109 UtcTimeStamp(self.0 + rhs.0)
110 }
111}
112
113impl ops::AddAssign<TimeDelta> for UtcTimeStamp {
114 fn add_assign(&mut self, rhs: TimeDelta) {
115 *self = *self + rhs;
116 }
117}
118
119impl ops::Sub<TimeDelta> for UtcTimeStamp {
121 type Output = UtcTimeStamp;
122
123 fn sub(self, rhs: TimeDelta) -> Self::Output {
124 UtcTimeStamp(self.0 - rhs.0)
125 }
126}
127
128impl ops::SubAssign<TimeDelta> for UtcTimeStamp {
129 fn sub_assign(&mut self, rhs: TimeDelta) {
130 *self = *self - rhs;
131 }
132}
133
134impl ops::Sub<UtcTimeStamp> for UtcTimeStamp {
136 type Output = TimeDelta;
137
138 fn sub(self, rhs: UtcTimeStamp) -> Self::Output {
139 TimeDelta(self.0 - rhs.0)
140 }
141}
142
143#[repr(transparent)]
158#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
159#[cfg_attr(feature = "serde-support", derive(Serialize, Deserialize))]
160pub struct TimeDelta(i64);
161
162impl fmt::Display for TimeDelta {
164 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
165 chrono::Duration::from(*self).fmt(f)
166 }
167}
168
169impl From<chrono::Duration> for TimeDelta {
171 fn from(other: chrono::Duration) -> Self {
172 Self(other.num_milliseconds())
173 }
174}
175
176impl From<TimeDelta> for chrono::Duration {
178 fn from(other: TimeDelta) -> Self {
179 chrono::Duration::milliseconds(other.0)
180 }
181}
182
183impl ops::Add<TimeDelta> for TimeDelta {
184 type Output = TimeDelta;
185
186 fn add(self, rhs: TimeDelta) -> Self::Output {
187 TimeDelta(self.0 + rhs.0)
188 }
189}
190
191impl ops::Sub<TimeDelta> for TimeDelta {
192 type Output = TimeDelta;
193
194 fn sub(self, rhs: TimeDelta) -> Self::Output {
195 TimeDelta(self.0 - rhs.0)
196 }
197}
198
199impl ops::Mul<i64> for TimeDelta {
201 type Output = TimeDelta;
202
203 fn mul(self, rhs: i64) -> Self::Output {
204 TimeDelta(self.0 * rhs)
205 }
206}
207
208impl ops::Div<i64> for TimeDelta {
210 type Output = TimeDelta;
211
212 fn div(self, rhs: i64) -> Self::Output {
213 TimeDelta(self.0 / rhs)
214 }
215}
216
217impl ops::Div<TimeDelta> for TimeDelta {
219 type Output = i64;
220
221 fn div(self, rhs: TimeDelta) -> Self::Output {
222 self.0 / rhs.0
223 }
224}
225
226impl ops::Rem<TimeDelta> for TimeDelta {
228 type Output = TimeDelta;
229
230 fn rem(self, rhs: TimeDelta) -> Self::Output {
231 TimeDelta(self.0 % rhs.0)
232 }
233}
234
235impl TimeDelta {
237 #[inline]
238 pub const fn zero() -> Self {
239 TimeDelta(0)
240 }
241
242 #[inline]
243 pub const fn from_hours(int: i64) -> Self {
244 TimeDelta::from_minutes(int * 60)
245 }
246
247 #[inline]
248 pub const fn from_minutes(int: i64) -> Self {
249 TimeDelta::from_seconds(int * 60)
250 }
251
252 #[inline]
253 pub const fn from_seconds(int: i64) -> Self {
254 TimeDelta(int * 1000)
255 }
256
257 #[inline]
258 pub const fn from_milliseconds(int: i64) -> Self {
259 TimeDelta(int)
260 }
261
262 #[inline]
263 pub const fn as_milliseconds(self) -> i64 {
264 self.0
265 }
266
267 #[inline]
269 pub const fn is_zero(self) -> bool {
270 self.0 == 0
271 }
272
273 #[inline]
276 pub const fn is_positive(self) -> bool {
277 self.0 > 0
278 }
279
280 #[inline]
283 pub const fn is_negative(self) -> bool {
284 self.0 < 0
285 }
286}
287
288#[derive(Debug)]
317pub struct TimeRange {
318 cur: UtcTimeStamp,
319 end: UtcTimeStamp,
320 step: TimeDelta,
321 right_closed: bool,
322}
323
324impl TimeRange {
325 pub fn right_closed(
327 start: impl Into<UtcTimeStamp>,
328 end: impl Into<UtcTimeStamp>,
329 step: impl Into<TimeDelta>,
330 ) -> Self {
331 TimeRange {
332 cur: start.into(),
333 end: end.into(),
334 step: step.into(),
335 right_closed: true,
336 }
337 }
338
339 pub fn right_open(
341 start: impl Into<UtcTimeStamp>,
342 end: impl Into<UtcTimeStamp>,
343 step: impl Into<TimeDelta>,
344 ) -> Self {
345 TimeRange {
346 cur: start.into(),
347 end: end.into(),
348 step: step.into(),
349 right_closed: false,
350 }
351 }
352}
353
354impl Iterator for TimeRange {
355 type Item = UtcTimeStamp;
356
357 fn next(&mut self) -> Option<Self::Item> {
358 let exhausted = if self.right_closed {
359 self.cur > self.end
360 } else {
361 self.cur >= self.end
362 };
363
364 if exhausted {
365 None
366 } else {
367 let cur = self.cur;
368 self.cur += self.step;
369 Some(cur)
370 }
371 }
372}
373
374#[cfg(test)]
379mod tests {
380 use crate::*;
381 use chrono::{offset::TimeZone, Duration, Utc};
382
383 #[test]
384 fn open_time_range() {
385 let start = Utc.ymd(2019, 4, 14).and_hms(0, 0, 0);
386 let end = Utc.ymd(2019, 4, 16).and_hms(0, 0, 0);
387 let step = Duration::hours(12);
388 let tr: Vec<_> = Iterator::collect(TimeRange::right_closed(start, end, step));
389 assert_eq!(tr, vec![
390 Utc.ymd(2019, 4, 14).and_hms(0, 0, 0).into(),
391 Utc.ymd(2019, 4, 14).and_hms(12, 0, 0).into(),
392 Utc.ymd(2019, 4, 15).and_hms(0, 0, 0).into(),
393 Utc.ymd(2019, 4, 15).and_hms(12, 0, 0).into(),
394 Utc.ymd(2019, 4, 16).and_hms(0, 0, 0).into(),
395 ]);
396 }
397
398 #[test]
399 fn timestamp_and_delta_vs_chrono() {
400 let c_dt = Utc.ymd(2019, 3, 13).and_hms(16, 14, 9);
401 let c_td = Duration::milliseconds(123456);
402
403 let my_dt = UtcTimeStamp::from(c_dt.clone());
404 let my_td = TimeDelta::from_milliseconds(123456);
405 assert_eq!(TimeDelta::from(c_td.clone()), my_td);
406
407 let c_result = c_dt + c_td * 555;
408 let my_result = my_dt + my_td * 555;
409 assert_eq!(UtcTimeStamp::from(c_result.clone()), my_result);
410 }
411
412 #[test]
413 fn timestamp_ord_eq() {
414 let ts1: UtcTimeStamp = UtcTimeStamp::from_milliseconds(111);
415 let ts2: UtcTimeStamp = UtcTimeStamp::from_milliseconds(222);
416 let ts3: UtcTimeStamp = UtcTimeStamp::from_milliseconds(222);
417
418 assert!(ts1 < ts2);
419 assert!(ts2 > ts1);
420 assert!(ts1 <= ts2);
421 assert!(ts2 >= ts3);
422 assert!(ts2 <= ts3);
423 assert!(ts2 >= ts3);
424 assert_eq!(ts2, ts3);
425 assert_ne!(ts1, ts3);
426 }
427
428 #[test]
429 fn align_to_anchored() {
430 let day = Utc.ymd(2020, 9, 28);
431 let ts: UtcTimeStamp = day.and_hms(19, 32, 51).into();
432
433 assert_eq!(
434 ts.align_to_anchored(day.and_hms(0, 0, 0).into(), TimeDelta::from_seconds(60 * 5)),
435 day.and_hms(19, 30, 0).into(),
436 );
437
438 assert_eq!(
439 ts.align_to_anchored(
440 day.and_hms(9 , 1, 3).into(),
441 TimeDelta::from_seconds(60 * 5)
442 ),
443 day.and_hms(19, 31, 3).into(),
444 );
445 }
446
447 #[test]
448 fn align_to_anchored_eq() {
449 let day = Utc.ymd(2020, 1, 1);
450 let anchor: UtcTimeStamp = day.and_hms(0, 0, 0).into();
451 let freq = TimeDelta::from_seconds(5 * 60);
452
453 let ts1: UtcTimeStamp = day.and_hms(12, 1, 11).into();
454 let ts2: UtcTimeStamp = day.and_hms(12, 4, 11).into();
455 assert_eq!(
456 ts1.align_to_anchored(anchor, freq),
457 ts2.align_to_anchored(anchor, freq),
458 );
459 }
460}
461
462