questdb/ingress/
timestamp.rs

1use crate::error;
2use std::time::{Duration, SystemTime, UNIX_EPOCH};
3
4#[cfg(feature = "chrono_timestamp")]
5use chrono::{DateTime, TimeZone};
6
7/// Convert a `SystemTime` to a `Duration` to/from the UNIX epoch.
8/// Returns a tuple of (is_negative, duration).
9#[inline]
10fn sys_time_to_duration(time: SystemTime, extract_fn: impl FnOnce(Duration) -> u128) -> i128 {
11    if time >= UNIX_EPOCH {
12        extract_fn(time.duration_since(UNIX_EPOCH).expect("time >= UNIX_EPOCH")) as i128
13    } else {
14        -(extract_fn(UNIX_EPOCH.duration_since(time).expect("time < UNIX_EPOCH")) as i128)
15    }
16}
17
18#[inline]
19fn sys_time_convert(
20    time: SystemTime,
21    extract_fn: impl FnOnce(Duration) -> u128,
22) -> crate::Result<i64> {
23    let number = sys_time_to_duration(time, extract_fn);
24    match i64::try_from(number) {
25        Ok(number) => Ok(number),
26        Err(_) => Err(error::fmt!(
27            InvalidTimestamp,
28            "Timestamp {:?} is out of range",
29            time
30        )),
31    }
32}
33
34#[inline]
35fn extract_current_timestamp(extract_fn: impl FnOnce(Duration) -> u128) -> crate::Result<i64> {
36    let time = SystemTime::now();
37    sys_time_convert(time, extract_fn)
38}
39
40/// A `i64` timestamp expressed as microseconds since the UNIX epoch (UTC).
41///
42/// # Examples
43///
44/// ```
45/// # use questdb::Result;
46/// use questdb::ingress::TimestampMicros;
47///
48/// # fn main() -> Result<()> {
49/// let ts = TimestampMicros::now();
50/// # Ok(())
51/// # }
52/// ```
53///
54/// or
55///
56/// ```
57/// # use questdb::Result;
58/// use questdb::ingress::TimestampMicros;
59///
60/// # fn main() -> Result<()> {
61/// let ts = TimestampMicros::new(1695312859886554);
62/// # Ok(())
63/// # }
64/// ```
65///
66/// or
67///
68/// ```
69/// # use questdb::Result;
70/// use questdb::ingress::TimestampMicros;
71///
72/// # fn main() -> Result<()> {
73/// let ts = TimestampMicros::from_systemtime(std::time::SystemTime::now())?;
74/// # Ok(())
75/// # }
76/// ```
77///
78/// or
79///
80/// ```
81/// # use questdb::Result;
82/// use questdb::ingress::TimestampMicros;
83///
84/// # fn main() -> Result<()> {
85/// #[cfg(feature = "chrono_timestamp")]
86/// let ts = TimestampMicros::from_datetime(chrono::Utc::now());
87/// # Ok(())
88/// # }
89/// ```
90#[derive(Copy, Clone, Debug)]
91pub struct TimestampMicros(i64);
92
93impl TimestampMicros {
94    /// Current UTC timestamp in microseconds.
95    pub fn now() -> Self {
96        Self(extract_current_timestamp(|d| d.as_micros()).expect("now in range of micros"))
97    }
98
99    /// Create a new timestamp from the given number of microseconds
100    /// since the UNIX epoch (UTC).
101    pub fn new(micros: i64) -> Self {
102        Self(micros)
103    }
104
105    #[cfg(feature = "chrono_timestamp")]
106    pub fn from_datetime<T: TimeZone>(dt: DateTime<T>) -> Self {
107        Self::new(dt.timestamp_micros())
108    }
109
110    pub fn from_systemtime(time: SystemTime) -> crate::Result<Self> {
111        sys_time_convert(time, |d| d.as_micros()).map(Self)
112    }
113
114    /// Get the numeric value of the timestamp.
115    pub fn as_i64(&self) -> i64 {
116        self.0
117    }
118}
119
120/// A `i64` timestamp expressed as nanoseconds since the UNIX epoch (UTC).
121///
122/// # Examples
123///
124/// ```
125/// # use questdb::Result;
126/// use questdb::ingress::TimestampNanos;
127///
128/// # fn main() -> Result<()> {
129/// let ts = TimestampNanos::now();
130/// # Ok(())
131/// # }
132/// ```
133///
134/// or
135///
136/// ```
137/// # use questdb::Result;
138/// use questdb::ingress::TimestampNanos;
139///
140/// # fn main() -> Result<()> {
141/// let ts = TimestampNanos::new(1659548315647406592);
142/// # Ok(())
143/// # }
144/// ```
145///
146/// or
147///
148/// ```
149/// # use questdb::Result;
150/// use questdb::ingress::TimestampNanos;
151///
152/// # fn main() -> Result<()> {
153/// let ts = TimestampNanos::from_systemtime(std::time::SystemTime::now())?;
154/// # Ok(())
155/// # }
156/// ```
157///
158/// or
159///
160/// ```
161/// # use questdb::Result;
162/// use questdb::ingress::TimestampNanos;
163///
164/// # fn main() -> Result<()> {
165/// # #[cfg(feature = "chrono_timestamp")]
166/// let ts = TimestampNanos::from_datetime(chrono::Utc::now());
167/// # Ok(())
168/// # }
169/// ```
170///
171#[derive(Copy, Clone, Debug)]
172pub struct TimestampNanos(i64);
173
174impl TimestampNanos {
175    /// Current UTC timestamp in nanoseconds.
176    pub fn now() -> Self {
177        Self(extract_current_timestamp(|d| d.as_nanos()).expect("now in range of nanos"))
178    }
179
180    /// Create a new timestamp from the given number of nanoseconds
181    /// since the UNIX epoch (UTC).
182    pub fn new(nanos: i64) -> Self {
183        Self(nanos)
184    }
185
186    #[cfg(feature = "chrono_timestamp")]
187    pub fn from_datetime<T: TimeZone>(dt: DateTime<T>) -> crate::Result<Self> {
188        match dt.timestamp_nanos_opt() {
189            Some(nanos) => Ok(Self::new(nanos)),
190            None => Err(error::fmt!(
191                InvalidTimestamp,
192                "Timestamp {:?} is out of range",
193                dt
194            )),
195        }
196    }
197
198    pub fn from_systemtime(time: SystemTime) -> crate::Result<Self> {
199        sys_time_convert(time, |d| d.as_nanos()).map(Self)
200    }
201
202    /// Get the numeric value of the timestamp.
203    pub fn as_i64(&self) -> i64 {
204        self.0
205    }
206}
207
208impl TryFrom<TimestampMicros> for TimestampNanos {
209    type Error = crate::Error;
210
211    fn try_from(ts: TimestampMicros) -> crate::Result<Self> {
212        let nanos = ts.as_i64().checked_mul(1000i64);
213        match nanos {
214            Some(nanos) => Ok(Self(nanos)),
215            None => Err(error::fmt!(
216                InvalidTimestamp,
217                "Timestamp {:?} is out of range",
218                ts
219            )),
220        }
221    }
222}
223
224impl From<TimestampNanos> for TimestampMicros {
225    fn from(ts: TimestampNanos) -> Self {
226        Self(ts.as_i64() / 1000i64)
227    }
228}
229
230/// A timestamp expressed as micros or nanos.
231/// You should seldom use this directly. Instead use one of:
232///   * `TimestampNanos`
233///   * `TimestampMicros`
234///
235/// Both of these types can `try_into()` the `Timestamp` type.
236///
237/// Both of these can be constructed from `std::time::SystemTime`,
238/// or from `chrono::DateTime`.
239#[derive(Copy, Clone, Debug)]
240pub enum Timestamp {
241    Micros(TimestampMicros),
242    Nanos(TimestampNanos),
243}
244
245impl From<TimestampMicros> for Timestamp {
246    fn from(ts: TimestampMicros) -> Self {
247        Self::Micros(ts)
248    }
249}
250
251impl From<TimestampNanos> for Timestamp {
252    fn from(ts: TimestampNanos) -> Self {
253        Self::Nanos(ts)
254    }
255}
256
257impl TryFrom<Timestamp> for TimestampMicros {
258    type Error = crate::Error;
259
260    fn try_from(ts: Timestamp) -> crate::Result<Self> {
261        match ts {
262            Timestamp::Micros(ts) => Ok(ts),
263            Timestamp::Nanos(ts) => Ok(ts.into()),
264        }
265    }
266}
267
268impl TryFrom<Timestamp> for TimestampNanos {
269    type Error = crate::Error;
270
271    fn try_from(ts: Timestamp) -> crate::Result<Self> {
272        match ts {
273            Timestamp::Micros(ts) => Ok(ts.try_into()?),
274            Timestamp::Nanos(ts) => Ok(ts),
275        }
276    }
277}