questdb/ingress/
timestamp.rs

1/*******************************************************************************
2 *     ___                  _   ____  ____
3 *    / _ \ _   _  ___  ___| |_|  _ \| __ )
4 *   | | | | | | |/ _ \/ __| __| | | |  _ \
5 *   | |_| | |_| |  __/\__ \ |_| |_| | |_) |
6 *    \__\_\\__,_|\___||___/\__|____/|____/
7 *
8 *  Copyright (c) 2014-2019 Appsicle
9 *  Copyright (c) 2019-2025 QuestDB
10 *
11 *  Licensed under the Apache License, Version 2.0 (the "License");
12 *  you may not use this file except in compliance with the License.
13 *  You may obtain a copy of the License at
14 *
15 *  http://www.apache.org/licenses/LICENSE-2.0
16 *
17 *  Unless required by applicable law or agreed to in writing, software
18 *  distributed under the License is distributed on an "AS IS" BASIS,
19 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20 *  See the License for the specific language governing permissions and
21 *  limitations under the License.
22 *
23 ******************************************************************************/
24
25use crate::error;
26use std::time::{Duration, SystemTime, UNIX_EPOCH};
27
28#[cfg(feature = "chrono_timestamp")]
29use chrono::{DateTime, TimeZone};
30
31/// Convert a `SystemTime` to a `Duration` to/from the UNIX epoch.
32/// Returns a tuple of (is_negative, duration).
33#[inline]
34fn sys_time_to_duration(time: SystemTime, extract_fn: impl FnOnce(Duration) -> u128) -> i128 {
35    if time >= UNIX_EPOCH {
36        extract_fn(time.duration_since(UNIX_EPOCH).expect("time >= UNIX_EPOCH")) as i128
37    } else {
38        -(extract_fn(UNIX_EPOCH.duration_since(time).expect("time < UNIX_EPOCH")) as i128)
39    }
40}
41
42#[inline]
43fn sys_time_convert(
44    time: SystemTime,
45    extract_fn: impl FnOnce(Duration) -> u128,
46) -> crate::Result<i64> {
47    let number = sys_time_to_duration(time, extract_fn);
48    match i64::try_from(number) {
49        Ok(number) => Ok(number),
50        Err(_) => Err(error::fmt!(
51            InvalidTimestamp,
52            "Timestamp {:?} is out of range",
53            time
54        )),
55    }
56}
57
58#[inline]
59fn extract_current_timestamp(extract_fn: impl FnOnce(Duration) -> u128) -> crate::Result<i64> {
60    let time = SystemTime::now();
61    sys_time_convert(time, extract_fn)
62}
63
64/// A `i64` timestamp expressed as microseconds since the UNIX epoch (UTC).
65///
66/// # Examples
67///
68/// ```
69/// # use questdb::Result;
70/// use questdb::ingress::TimestampMicros;
71///
72/// # fn main() -> Result<()> {
73/// let ts = TimestampMicros::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/// let ts = TimestampMicros::new(1695312859886554);
86/// # Ok(())
87/// # }
88/// ```
89///
90/// or
91///
92/// ```
93/// # use questdb::Result;
94/// use questdb::ingress::TimestampMicros;
95///
96/// # fn main() -> Result<()> {
97/// let ts = TimestampMicros::from_systemtime(std::time::SystemTime::now())?;
98/// # Ok(())
99/// # }
100/// ```
101///
102/// or
103///
104/// ```
105/// # use questdb::Result;
106/// use questdb::ingress::TimestampMicros;
107///
108/// # fn main() -> Result<()> {
109/// #[cfg(feature = "chrono_timestamp")]
110/// let ts = TimestampMicros::from_datetime(chrono::Utc::now());
111/// # Ok(())
112/// # }
113/// ```
114#[derive(Copy, Clone, Debug)]
115pub struct TimestampMicros(i64);
116
117impl TimestampMicros {
118    /// Current UTC timestamp in microseconds.
119    pub fn now() -> Self {
120        Self(extract_current_timestamp(|d| d.as_micros()).expect("now in range of micros"))
121    }
122
123    /// Create a new timestamp from the given number of microseconds
124    /// since the UNIX epoch (UTC).
125    pub fn new(micros: i64) -> Self {
126        Self(micros)
127    }
128
129    #[cfg(feature = "chrono_timestamp")]
130    pub fn from_datetime<T: TimeZone>(dt: DateTime<T>) -> Self {
131        Self::new(dt.timestamp_micros())
132    }
133
134    pub fn from_systemtime(time: SystemTime) -> crate::Result<Self> {
135        sys_time_convert(time, |d| d.as_micros()).map(Self)
136    }
137
138    /// Get the numeric value of the timestamp.
139    pub fn as_i64(&self) -> i64 {
140        self.0
141    }
142}
143
144/// A `i64` timestamp expressed as nanoseconds since the UNIX epoch (UTC).
145///
146/// # Examples
147///
148/// ```
149/// # use questdb::Result;
150/// use questdb::ingress::TimestampNanos;
151///
152/// # fn main() -> Result<()> {
153/// let ts = TimestampNanos::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/// let ts = TimestampNanos::new(1659548315647406592);
166/// # Ok(())
167/// # }
168/// ```
169///
170/// or
171///
172/// ```
173/// # use questdb::Result;
174/// use questdb::ingress::TimestampNanos;
175///
176/// # fn main() -> Result<()> {
177/// let ts = TimestampNanos::from_systemtime(std::time::SystemTime::now())?;
178/// # Ok(())
179/// # }
180/// ```
181///
182/// or
183///
184/// ```
185/// # use questdb::Result;
186/// use questdb::ingress::TimestampNanos;
187///
188/// # fn main() -> Result<()> {
189/// # #[cfg(feature = "chrono_timestamp")]
190/// let ts = TimestampNanos::from_datetime(chrono::Utc::now());
191/// # Ok(())
192/// # }
193/// ```
194///
195#[derive(Copy, Clone, Debug)]
196pub struct TimestampNanos(i64);
197
198impl TimestampNanos {
199    /// Current UTC timestamp in nanoseconds.
200    pub fn now() -> Self {
201        Self(extract_current_timestamp(|d| d.as_nanos()).expect("now in range of nanos"))
202    }
203
204    /// Create a new timestamp from the given number of nanoseconds
205    /// since the UNIX epoch (UTC).
206    pub fn new(nanos: i64) -> Self {
207        Self(nanos)
208    }
209
210    #[cfg(feature = "chrono_timestamp")]
211    pub fn from_datetime<T: TimeZone>(dt: DateTime<T>) -> crate::Result<Self> {
212        match dt.timestamp_nanos_opt() {
213            Some(nanos) => Ok(Self::new(nanos)),
214            None => Err(error::fmt!(
215                InvalidTimestamp,
216                "Timestamp {:?} is out of range",
217                dt
218            )),
219        }
220    }
221
222    pub fn from_systemtime(time: SystemTime) -> crate::Result<Self> {
223        sys_time_convert(time, |d| d.as_nanos()).map(Self)
224    }
225
226    /// Get the numeric value of the timestamp.
227    pub fn as_i64(&self) -> i64 {
228        self.0
229    }
230}
231
232impl TryFrom<TimestampMicros> for TimestampNanos {
233    type Error = crate::Error;
234
235    fn try_from(ts: TimestampMicros) -> crate::Result<Self> {
236        let nanos = ts.as_i64().checked_mul(1000i64);
237        match nanos {
238            Some(nanos) => Ok(Self(nanos)),
239            None => Err(error::fmt!(
240                InvalidTimestamp,
241                "Timestamp {:?} is out of range",
242                ts
243            )),
244        }
245    }
246}
247
248impl From<TimestampNanos> for TimestampMicros {
249    fn from(ts: TimestampNanos) -> Self {
250        Self(ts.as_i64() / 1000i64)
251    }
252}
253
254/// A timestamp expressed as micros or nanos.
255/// You should seldom use this directly. Instead use one of:
256///   * `TimestampNanos`
257///   * `TimestampMicros`
258///
259/// Both of these types can `try_into()` the `Timestamp` type.
260///
261/// Both of these can be constructed from `std::time::SystemTime`,
262/// or from `chrono::DateTime`.
263#[derive(Copy, Clone, Debug)]
264pub enum Timestamp {
265    Micros(TimestampMicros),
266    Nanos(TimestampNanos),
267}
268
269impl From<TimestampMicros> for Timestamp {
270    fn from(ts: TimestampMicros) -> Self {
271        Self::Micros(ts)
272    }
273}
274
275impl From<TimestampNanos> for Timestamp {
276    fn from(ts: TimestampNanos) -> Self {
277        Self::Nanos(ts)
278    }
279}
280
281impl TryFrom<Timestamp> for TimestampMicros {
282    type Error = crate::Error;
283
284    fn try_from(ts: Timestamp) -> crate::Result<Self> {
285        match ts {
286            Timestamp::Micros(ts) => Ok(ts),
287            Timestamp::Nanos(ts) => Ok(ts.into()),
288        }
289    }
290}
291
292impl TryFrom<Timestamp> for TimestampNanos {
293    type Error = crate::Error;
294
295    fn try_from(ts: Timestamp) -> crate::Result<Self> {
296        match ts {
297            Timestamp::Micros(ts) => Ok(ts.try_into()?),
298            Timestamp::Nanos(ts) => Ok(ts),
299        }
300    }
301}