Skip to main content

raphtory_api/core/utils/
time.rs

1use crate::core::storage::timeindex::{AsTime, EventTime};
2#[cfg(feature = "python")]
3use crate::python::error::adapt_err_value;
4use chrono::{DateTime, NaiveDate, NaiveDateTime, ParseError, TimeZone};
5#[cfg(feature = "python")]
6use pyo3::PyErr;
7use std::{convert::Infallible, num::ParseIntError};
8
9#[derive(thiserror::Error, Debug, Clone, PartialEq)]
10pub enum ParseTimeError {
11    #[error("The interval string doesn't contain a complete number of number-unit pairs.")]
12    InvalidPairs,
13    #[error(
14        "One of the tokens in the interval string supposed to be a number couldn't be parsed."
15    )]
16    ParseInt {
17        #[from]
18        source: ParseIntError,
19    },
20    #[error("'{0}' is not a valid unit. Valid units are year(s), month(s), week(s), day(s), hour(s), minute(s), second(s) and millisecond(s).")]
21    InvalidUnit(String),
22    #[error("'{0}' is not a valid unit. Valid units are year(s), month(s), week(s), day(s), hour(s), minute(s), second(s), millisecond(s), and unaligned.")]
23    InvalidAlignmentUnit(String),
24    #[error(transparent)]
25    ParseError(#[from] ParseError),
26    #[error("Negative interval is not supported.")]
27    NegativeInt,
28    #[error("0 size step is not supported.")]
29    ZeroSizeStep,
30    #[error("'{0}' is not a valid datetime. Valid formats are RFC3339, RFC2822, %Y-%m-%d, %Y-%m-%dT%H:%M:%S%.3f, %Y-%m-%dT%H:%M:%S%, %Y-%m-%d %H:%M:%S%.3f and %Y-%m-%d %H:%M:%S%")]
31    InvalidDateTimeString(String),
32}
33
34impl From<Infallible> for ParseTimeError {
35    fn from(value: Infallible) -> Self {
36        match value {}
37    }
38}
39
40#[cfg(feature = "python")]
41impl From<ParseTimeError> for PyErr {
42    fn from(value: ParseTimeError) -> Self {
43        adapt_err_value(&value)
44    }
45}
46
47pub trait IntoTime {
48    fn into_time(self) -> EventTime;
49}
50
51impl IntoTime for i64 {
52    fn into_time(self) -> EventTime {
53        EventTime::from(self)
54    }
55}
56
57impl<Tz: TimeZone> IntoTime for DateTime<Tz> {
58    fn into_time(self) -> EventTime {
59        EventTime::from(self.timestamp_millis())
60    }
61}
62
63impl IntoTime for NaiveDateTime {
64    fn into_time(self) -> EventTime {
65        EventTime::from(self.and_utc().timestamp_millis())
66    }
67}
68
69impl IntoTime for EventTime {
70    fn into_time(self) -> EventTime {
71        self
72    }
73}
74
75impl<T: IntoTime> IntoTime for (T, usize) {
76    fn into_time(self) -> EventTime {
77        self.0.into_time().set_event_id(self.1)
78    }
79}
80
81pub trait TryIntoTime {
82    fn try_into_time(self) -> Result<EventTime, ParseTimeError>;
83}
84
85impl<T: IntoTime> TryIntoTime for T {
86    fn try_into_time(self) -> Result<EventTime, ParseTimeError> {
87        Ok(self.into_time())
88    }
89}
90
91impl TryIntoTime for &str {
92    /// Tries to parse the timestamp as RFC3339 and then as ISO 8601 with local format and all
93    /// fields mandatory except for milliseconds and allows replacing the T with a space
94    fn try_into_time(self) -> Result<EventTime, ParseTimeError> {
95        let rfc_result = DateTime::parse_from_rfc3339(self);
96        if let Ok(datetime) = rfc_result {
97            return Ok(EventTime::from(datetime.timestamp_millis()));
98        }
99
100        let result = DateTime::parse_from_rfc2822(self);
101        if let Ok(datetime) = result {
102            return Ok(EventTime::from(datetime.timestamp_millis()));
103        }
104
105        let result = NaiveDate::parse_from_str(self, "%Y-%m-%d");
106        if let Ok(date) = result {
107            let timestamp = date
108                .and_hms_opt(00, 00, 00)
109                .unwrap()
110                .and_utc()
111                .timestamp_millis();
112            return Ok(EventTime::from(timestamp));
113        }
114
115        let result = NaiveDateTime::parse_from_str(self, "%Y-%m-%dT%H:%M:%S%.3f");
116        if let Ok(datetime) = result {
117            return Ok(EventTime::from(datetime.and_utc().timestamp_millis()));
118        }
119
120        let result = NaiveDateTime::parse_from_str(self, "%Y-%m-%dT%H:%M:%S%");
121        if let Ok(datetime) = result {
122            return Ok(EventTime::from(datetime.and_utc().timestamp_millis()));
123        }
124
125        let result = NaiveDateTime::parse_from_str(self, "%Y-%m-%d %H:%M:%S%.3f");
126        if let Ok(datetime) = result {
127            return Ok(EventTime::from(datetime.and_utc().timestamp_millis()));
128        }
129
130        let result = NaiveDateTime::parse_from_str(self, "%Y-%m-%d %H:%M:%S%");
131        if let Ok(datetime) = result {
132            return Ok(EventTime::from(datetime.and_utc().timestamp_millis()));
133        }
134
135        Err(ParseTimeError::InvalidDateTimeString(self.to_string()))
136    }
137}
138
139pub trait TryIntoTimeNeedsEventId: TryIntoTime {}
140
141impl TryIntoTimeNeedsEventId for i64 {}
142
143impl<Tz: TimeZone> TryIntoTimeNeedsEventId for DateTime<Tz> {}
144
145impl TryIntoTimeNeedsEventId for NaiveDateTime {}
146
147impl TryIntoTimeNeedsEventId for &str {}
148
149/// Used to handle automatic injection of event id if not explicitly provided.
150/// In many cases, we will want different behaviour if an event id was provided or not.
151#[derive(
152    Debug, Clone, Copy, Eq, PartialEq, Ord, PartialOrd, Hash, serde::Serialize, serde::Deserialize,
153)]
154pub enum InputTime {
155    Simple(i64),
156    Indexed(i64, usize),
157}
158
159impl InputTime {
160    pub fn set_index(self, index: usize) -> Self {
161        match self {
162            InputTime::Simple(time) => InputTime::Indexed(time, index),
163            InputTime::Indexed(time, _) => InputTime::Indexed(time, index),
164        }
165    }
166
167    pub fn as_time(&self) -> EventTime {
168        match self {
169            InputTime::Simple(t) => EventTime::new(*t, 0),
170            InputTime::Indexed(t, s) => EventTime::new(*t, *s),
171        }
172    }
173}
174
175/// Single time input only refers to the i64 component of an EventTime (no event id).
176pub trait AsSingleTimeInput {
177    fn try_into_input_time(self) -> Result<InputTime, ParseTimeError>;
178}
179
180impl<T: TryIntoTimeNeedsEventId> AsSingleTimeInput for T {
181    fn try_into_input_time(self) -> Result<InputTime, ParseTimeError> {
182        Ok(InputTime::Simple(self.try_into_time()?.t()))
183    }
184}
185
186pub trait TryIntoInputTime {
187    fn try_into_input_time(self) -> Result<InputTime, ParseTimeError>;
188}
189
190impl TryIntoInputTime for InputTime {
191    fn try_into_input_time(self) -> Result<InputTime, ParseTimeError> {
192        Ok(self)
193    }
194}
195
196impl<T: AsSingleTimeInput> TryIntoInputTime for T {
197    fn try_into_input_time(self) -> Result<InputTime, ParseTimeError> {
198        self.try_into_input_time()
199    }
200}
201
202impl TryIntoInputTime for EventTime {
203    fn try_into_input_time(self) -> Result<InputTime, ParseTimeError> {
204        Ok(InputTime::Indexed(self.t(), self.i()))
205    }
206}
207
208impl<T: AsSingleTimeInput> TryIntoInputTime for (T, usize) {
209    fn try_into_input_time(self) -> Result<InputTime, ParseTimeError> {
210        Ok(self.0.try_into_input_time()?.set_index(self.1))
211    }
212}
213
214pub trait IntoTimeWithFormat {
215    fn parse_time(&self, fmt: &str) -> Result<i64, ParseTimeError>;
216}
217
218impl IntoTimeWithFormat for &str {
219    fn parse_time(&self, fmt: &str) -> Result<i64, ParseTimeError> {
220        let timestamp = NaiveDateTime::parse_from_str(self, fmt)?
221            .and_utc()
222            .timestamp_millis();
223        Ok(timestamp)
224    }
225}