bson2/
datetime.rs

1use std::{
2    error,
3    fmt::{self, Display},
4    result,
5    time::{Duration, SystemTime},
6};
7
8#[cfg(all(feature = "serde_with", feature = "chrono-0_4"))]
9use serde::{Deserialize, Deserializer, Serialize};
10#[cfg(all(feature = "serde_with", feature = "chrono-0_4"))]
11use serde_with::{DeserializeAs, SerializeAs};
12
13use chrono::{LocalResult, TimeZone, Utc};
14
15/// Struct representing a BSON datetime.
16/// Note: BSON datetimes have millisecond precision.
17///
18/// To enable conversions between this type and [`chrono::DateTime`], enable the `"chrono-0_4"`
19/// feature flag in your `Cargo.toml`.
20/// ```
21/// use chrono::prelude::*;
22/// # fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
23/// # #[cfg(feature = "chrono-0_4")]
24/// # {
25/// let chrono_dt: chrono::DateTime<Utc> = "2014-11-28T12:00:09Z".parse()?;
26/// let bson_dt: bson::DateTime = chrono_dt.into();
27/// let bson_dt = bson::DateTime::from_chrono(chrono_dt);
28/// let back_to_chrono: chrono::DateTime<Utc> = bson_dt.into();
29/// let back_to_chrono = bson_dt.to_chrono();
30/// # }
31/// # Ok(())
32/// # }
33/// ```
34///
35/// This type differs from [`chrono::DateTime`] in that it serializes to and deserializes from a
36/// BSON datetime rather than an RFC 3339 formatted string. Additionally, in non-BSON formats, it
37/// will serialize to and deserialize from that format's equivalent of the
38/// [extended JSON representation](https://docs.mongodb.com/manual/reference/mongodb-extended-json/) of a datetime.
39/// To serialize a [`chrono::DateTime`] as a BSON datetime, you can use
40/// [`crate::serde_helpers::chrono_datetime_as_bson_datetime`].
41///
42/// ```rust
43/// # #[cfg(feature = "chrono-0_4")]
44/// # {
45/// use serde::{Serialize, Deserialize};
46///
47/// #[derive(Serialize, Deserialize)]
48/// struct Foo {
49///     // serializes as a BSON datetime.
50///     date_time: bson::DateTime,
51///
52///     // serializes as an RFC 3339 / ISO-8601 string.
53///     chrono_datetime: chrono::DateTime<chrono::Utc>,
54///
55///     // serializes as a BSON datetime.
56///     // this requires the "chrono-0_4" feature flag
57///     #[serde(with = "bson::serde_helpers::chrono_datetime_as_bson_datetime")]
58///     chrono_as_bson: chrono::DateTime<chrono::Utc>,
59/// }
60/// # }
61/// ```
62/// ## The `serde_with` feature flag
63///
64/// The `serde_with` feature can be enabled to support more ergonomic serde attributes for
65/// (de)serializing `chrono::DateTime` from/to BSON via the [`serde_with`](https://docs.rs/serde_with/1.11.0/serde_with/)
66/// crate. The main benefit of this compared to the regular `serde_helpers` is that `serde_with` can
67/// handle nested `chrono::DateTime` values (e.g. in `Option`), whereas the former only works on
68/// fields that are exactly `chrono::DateTime`.
69/// ```
70/// # #[cfg(all(feature = "chrono-0_4", feature = "serde_with"))]
71/// # {
72/// use serde::{Deserialize, Serialize};
73/// use bson::doc;
74///
75/// #[serde_with::serde_as]
76/// #[derive(Deserialize, Serialize, PartialEq, Debug)]
77/// struct Foo {
78///   /// Serializes as a BSON datetime rather than using `chrono::DateTime`'s serialization
79///   #[serde_as(as = "Option<bson::DateTime>")]
80///   as_bson: Option<chrono::DateTime<chrono::Utc>>,
81/// }
82///
83/// let dt = chrono::Utc::now();
84/// let foo = Foo {
85///   as_bson: Some(dt),
86/// };
87///
88/// let expected = doc! {
89///   "as_bson": bson::DateTime::from_chrono(dt),
90/// };
91///
92/// assert_eq!(bson::to_document(&foo)?, expected);
93/// # }
94/// # Ok::<(), Box<dyn std::error::Error>>(())
95/// ```
96#[derive(Eq, PartialEq, Ord, PartialOrd, Hash, Copy, Clone)]
97pub struct DateTime(i64);
98
99impl crate::DateTime {
100    /// The latest possible date that can be represented in BSON.
101    pub const MAX: Self = Self::from_millis(i64::MAX);
102
103    /// The earliest possible date that can be represented in BSON.
104    pub const MIN: Self = Self::from_millis(i64::MIN);
105
106    /// Makes a new [`DateTime`] from the number of non-leap milliseconds since
107    /// January 1, 1970 0:00:00 UTC (aka "UNIX timestamp").
108    pub const fn from_millis(date: i64) -> Self {
109        Self(date)
110    }
111
112    /// Returns a [`DateTime`] which corresponds to the current date and time.
113    pub fn now() -> DateTime {
114        Self::from_system_time(SystemTime::now())
115    }
116
117    #[cfg(not(feature = "chrono-0_4"))]
118    pub(crate) fn from_chrono<T: chrono::TimeZone>(dt: chrono::DateTime<T>) -> Self {
119        Self::from_millis(dt.timestamp_millis())
120    }
121
122    /// Convert the given `chrono::DateTime` into a `bson::DateTime`, truncating it to millisecond
123    /// precision.
124    #[cfg(feature = "chrono-0_4")]
125    #[cfg_attr(docsrs, doc(cfg(feature = "chrono-0_4")))]
126    pub fn from_chrono<T: chrono::TimeZone>(dt: chrono::DateTime<T>) -> Self {
127        Self::from_millis(dt.timestamp_millis())
128    }
129
130    fn to_chrono_private(self) -> chrono::DateTime<Utc> {
131        match Utc.timestamp_millis_opt(self.0) {
132            LocalResult::Single(dt) => dt,
133            _ => {
134                if self.0 < 0 {
135                    chrono::MIN_DATETIME
136                } else {
137                    chrono::MAX_DATETIME
138                }
139            }
140        }
141    }
142
143    #[cfg(not(feature = "chrono-0_4"))]
144    #[allow(unused)]
145    pub(crate) fn to_chrono(self) -> chrono::DateTime<Utc> {
146        self.to_chrono_private()
147    }
148
149    /// Convert this [`DateTime`] to a [`chrono::DateTime<Utc>`].
150    ///
151    /// Note: Not every BSON datetime can be represented as a [`chrono::DateTime`]. For such dates,
152    /// [`chrono::MIN_DATETIME`] or [`chrono::MAX_DATETIME`] will be returned, whichever is closer.
153    ///
154    /// ```
155    /// let bson_dt = bson::DateTime::now();
156    /// let chrono_dt = bson_dt.to_chrono();
157    /// assert_eq!(bson_dt.timestamp_millis(), chrono_dt.timestamp_millis());
158    ///
159    /// let big = bson::DateTime::from_millis(i64::MAX);
160    /// let chrono_big = big.to_chrono();
161    /// assert_eq!(chrono_big, chrono::MAX_DATETIME)
162    /// ```
163    #[cfg(feature = "chrono-0_4")]
164    #[cfg_attr(docsrs, doc(cfg(feature = "chrono-0_4")))]
165    pub fn to_chrono(self) -> chrono::DateTime<Utc> {
166        self.to_chrono_private()
167    }
168
169    /// Convert the given [`std::time::SystemTime`] to a [`DateTime`].
170    ///
171    /// If the provided time is too far in the future or too far in the past to be represented
172    /// by a BSON datetime, either [`DateTime::MAX`] or [`DateTime::MIN`] will be
173    /// returned, whichever is closer.
174    pub fn from_system_time(st: SystemTime) -> Self {
175        match st.duration_since(SystemTime::UNIX_EPOCH) {
176            Ok(d) => {
177                if d.as_millis() <= i64::MAX as u128 {
178                    Self::from_millis(d.as_millis() as i64)
179                } else {
180                    Self::MAX
181                }
182            }
183            // handle SystemTime from before the Unix Epoch
184            Err(e) => {
185                let millis = e.duration().as_millis();
186                if millis > i64::MAX as u128 {
187                    Self::MIN
188                } else {
189                    Self::from_millis(-(millis as i64))
190                }
191            }
192        }
193    }
194
195    /// Convert this [`DateTime`] to a [`std::time::SystemTime`].
196    pub fn to_system_time(self) -> SystemTime {
197        if self.0 >= 0 {
198            SystemTime::UNIX_EPOCH + Duration::from_millis(self.0 as u64)
199        } else {
200            // need to convert to i128 before calculating absolute value since i64::MIN.abs()
201            // overflows and panics.
202            SystemTime::UNIX_EPOCH - Duration::from_millis((self.0 as i128).abs() as u64)
203        }
204    }
205
206    /// Returns the number of non-leap-milliseconds since January 1, 1970 UTC.
207    pub const fn timestamp_millis(self) -> i64 {
208        self.0
209    }
210
211    /// Convert this [`DateTime`] to an RFC 3339 formatted string.
212    pub fn to_rfc3339_string(self) -> String {
213        self.to_chrono()
214            .to_rfc3339_opts(chrono::SecondsFormat::AutoSi, true)
215    }
216
217    /// Convert the given RFC 3339 formatted string to a [`DateTime`], truncating it to millisecond
218    /// precision.
219    pub fn parse_rfc3339_str(s: impl AsRef<str>) -> Result<Self> {
220        let date = chrono::DateTime::<chrono::FixedOffset>::parse_from_rfc3339(s.as_ref())
221            .map_err(|e| Error::InvalidTimestamp {
222                message: e.to_string(),
223            })?;
224        Ok(Self::from_chrono(date))
225    }
226}
227
228impl fmt::Debug for crate::DateTime {
229    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
230        let mut tup = f.debug_tuple("DateTime");
231        match Utc.timestamp_millis_opt(self.0) {
232            LocalResult::Single(ref dt) => tup.field(dt),
233            _ => tup.field(&self.0),
234        };
235        tup.finish()
236    }
237}
238
239impl Display for crate::DateTime {
240    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241        match Utc.timestamp_millis_opt(self.0) {
242            LocalResult::Single(ref dt) => Display::fmt(dt, f),
243            _ => Display::fmt(&self.0, f),
244        }
245    }
246}
247
248impl From<SystemTime> for crate::DateTime {
249    fn from(st: SystemTime) -> Self {
250        Self::from_system_time(st)
251    }
252}
253
254impl From<crate::DateTime> for SystemTime {
255    fn from(dt: crate::DateTime) -> Self {
256        dt.to_system_time()
257    }
258}
259
260#[cfg(feature = "chrono-0_4")]
261#[cfg_attr(docsrs, doc(cfg(feature = "chrono-0_4")))]
262impl From<crate::DateTime> for chrono::DateTime<Utc> {
263    fn from(bson_dt: DateTime) -> Self {
264        bson_dt.to_chrono()
265    }
266}
267
268#[cfg(feature = "chrono-0_4")]
269#[cfg_attr(docsrs, doc(cfg(feature = "chrono-0_4")))]
270impl<T: chrono::TimeZone> From<chrono::DateTime<T>> for crate::DateTime {
271    fn from(x: chrono::DateTime<T>) -> Self {
272        Self::from_chrono(x)
273    }
274}
275
276#[cfg(all(feature = "chrono-0_4", feature = "serde_with"))]
277#[cfg_attr(docsrs, doc(cfg(all(feature = "chrono-0_4", feature = "serde_with"))))]
278impl<'de> DeserializeAs<'de, chrono::DateTime<Utc>> for crate::DateTime {
279    fn deserialize_as<D>(deserializer: D) -> std::result::Result<chrono::DateTime<Utc>, D::Error>
280    where
281        D: Deserializer<'de>,
282    {
283        let dt = DateTime::deserialize(deserializer)?;
284        Ok(dt.to_chrono())
285    }
286}
287
288#[cfg(all(feature = "chrono-0_4", feature = "serde_with"))]
289#[cfg_attr(docsrs, doc(cfg(all(feature = "chrono-0_4", feature = "chrono-0_4"))))]
290impl SerializeAs<chrono::DateTime<Utc>> for crate::DateTime {
291    fn serialize_as<S>(
292        source: &chrono::DateTime<Utc>,
293        serializer: S,
294    ) -> std::result::Result<S::Ok, S::Error>
295    where
296        S: serde::Serializer,
297    {
298        let dt = DateTime::from_chrono(*source);
299        dt.serialize(serializer)
300    }
301}
302
303/// Errors that can occur during [`DateTime`] construction and generation.
304#[derive(Clone, Debug)]
305#[non_exhaustive]
306pub enum Error {
307    /// Error returned when an invalid datetime format is provided to a conversion method.
308    #[non_exhaustive]
309    InvalidTimestamp { message: String },
310}
311
312/// Alias for `Result<T, DateTime::Error>`
313pub type Result<T> = result::Result<T, Error>;
314
315impl fmt::Display for Error {
316    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
317        match self {
318            Error::InvalidTimestamp { message } => {
319                write!(fmt, "{}", message)
320            }
321        }
322    }
323}
324
325impl error::Error for Error {}