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 {}