Skip to content

Commit 4ddf9fc

Browse files
Guara92dguarascioabr-egn
authored
feat: Add integration with jiff::Timestamp (#587)
Co-authored-by: Daniele Guarascio <[email protected]> Co-authored-by: Abraham Egnor <[email protected]>
1 parent b0afc83 commit 4ddf9fc

File tree

9 files changed

+280
-1
lines changed

9 files changed

+280
-1
lines changed

Cargo.lock

Lines changed: 40 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ default = ["compat-3-0-0"]
3737
compat-3-0-0 = []
3838
# if enabled, include API for interfacing with chrono 0.4
3939
chrono-0_4 = ["dep:chrono"]
40+
# if enabled, include API for interfacing with jiff 0.2
41+
jiff-0_2 = ["dep:jiff"]
4042
# enable the large-dates feature for the time crate
4143
large_dates = ["time/large-dates"]
4244
# if enabled, include API for interfacing with uuid 1.x
@@ -56,6 +58,7 @@ name = "bson"
5658
[dependencies]
5759
ahash = "0.8.0"
5860
chrono = { version = "0.4.15", features = ["std"], default-features = false, optional = true }
61+
jiff = { version = "0.2", default-features = false, optional = true }
5962
rand = "0.9"
6063
serde = { version = "1.0", features = ["derive"], optional = true }
6164
serde_json = { version = "1.0", features = ["preserve_order"], optional = true }
@@ -87,6 +90,7 @@ serde_bytes = "0.11"
8790
serde_path_to_error = "0.1.16"
8891
serde_json = "1"
8992
chrono = { version = "0.4", features = ["serde", "clock", "std"], default-features = false }
93+
jiff = { version = "0.2", default-features = false, features = ["std"] }
9094

9195
[package.metadata.docs.rs]
9296
all-features = true

src/bson.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,13 @@ impl<T: chrono::TimeZone> From<chrono::DateTime<T>> for Bson {
408408
}
409409
}
410410

411+
#[cfg(feature = "jiff-0_2")]
412+
impl From<jiff::Timestamp> for Bson {
413+
fn from(a: jiff::Timestamp) -> Bson {
414+
Bson::DateTime(crate::DateTime::from(a))
415+
}
416+
}
417+
411418
#[cfg(feature = "uuid-1")]
412419
impl From<uuid::Uuid> for Bson {
413420
fn from(uuid: uuid::Uuid) -> Self {

src/datetime.rs

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use std::{
1212
use chrono::{LocalResult, TimeZone, Utc};
1313
#[cfg(all(
1414
feature = "serde_with-3",
15-
any(feature = "chrono-0_4", feature = "time-0_3")
15+
any(feature = "chrono-0_4", feature = "time-0_3", feature = "jiff-0_2")
1616
))]
1717
use serde::{Deserialize, Deserializer, Serialize};
1818
use time::format_description::well_known::Rfc3339;
@@ -215,6 +215,13 @@ impl crate::DateTime {
215215
Self::from_millis(dt.timestamp_millis())
216216
}
217217

218+
/// Convert the given [`jiff::Timestamp`] into a [`bson::DateTime`](DateTime), truncating it to
219+
/// millisecond precision.
220+
#[cfg(feature = "jiff-0_2")]
221+
pub fn from_jiff(ts: jiff::Timestamp) -> Self {
222+
Self::from_millis(ts.as_millisecond())
223+
}
224+
218225
/// Returns a builder used to construct a [`DateTime`] from a given year, month,
219226
/// day, and optionally, an hour, minute, second and millisecond, which default to
220227
/// 0 if not explicitly set.
@@ -253,6 +260,32 @@ impl crate::DateTime {
253260
}
254261
}
255262

263+
/// Convert this [`DateTime`] to a [`jiff::Timestamp`].
264+
///
265+
/// Note: Not every BSON datetime can be represented as a [`jiff::Timestamp`]. For such dates,
266+
/// [`jiff::Timestamp::MIN`] or [`jiff::Timestamp::MAX`] will be returned, whichever
267+
/// is closer.
268+
///
269+
/// ```
270+
/// let bson_dt = bson::DateTime::now();
271+
/// let jiff_ts = bson_dt.to_jiff();
272+
/// assert_eq!(bson_dt.timestamp_millis(), jiff_ts.as_millisecond());
273+
///
274+
/// let big = bson::DateTime::from_millis(i64::MAX);
275+
/// let jiff_big = big.to_jiff();
276+
/// assert_eq!(jiff_big, jiff::Timestamp::MAX)
277+
/// ```
278+
#[cfg(feature = "jiff-0_2")]
279+
pub fn to_jiff(self) -> jiff::Timestamp {
280+
jiff::Timestamp::from_millisecond(self.0).unwrap_or({
281+
if self.0 < 0 {
282+
jiff::Timestamp::MIN
283+
} else {
284+
jiff::Timestamp::MAX
285+
}
286+
})
287+
}
288+
256289
fn from_time_private(dt: time::OffsetDateTime) -> Self {
257290
let millis = dt.unix_timestamp_nanos() / 1_000_000;
258291
match millis.try_into() {
@@ -488,6 +521,45 @@ impl serde_with::SerializeAs<chrono::DateTime<Utc>> for crate::DateTime {
488521
}
489522
}
490523

524+
#[cfg(feature = "jiff-0_2")]
525+
impl From<crate::DateTime> for jiff::Timestamp {
526+
fn from(bson_dt: DateTime) -> Self {
527+
bson_dt.to_jiff()
528+
}
529+
}
530+
531+
#[cfg(feature = "jiff-0_2")]
532+
impl From<jiff::Timestamp> for crate::DateTime {
533+
fn from(x: jiff::Timestamp) -> Self {
534+
Self::from_jiff(x)
535+
}
536+
}
537+
538+
#[cfg(all(feature = "jiff-0_2", feature = "serde_with-3"))]
539+
impl<'de> serde_with::DeserializeAs<'de, jiff::Timestamp> for crate::DateTime {
540+
fn deserialize_as<D>(deserializer: D) -> std::result::Result<jiff::Timestamp, D::Error>
541+
where
542+
D: Deserializer<'de>,
543+
{
544+
let dt = DateTime::deserialize(deserializer)?;
545+
Ok(dt.to_jiff())
546+
}
547+
}
548+
549+
#[cfg(all(feature = "jiff-0_2", feature = "serde_with-3"))]
550+
impl serde_with::SerializeAs<jiff::Timestamp> for crate::DateTime {
551+
fn serialize_as<S>(
552+
source: &jiff::Timestamp,
553+
serializer: S,
554+
) -> std::result::Result<S::Ok, S::Error>
555+
where
556+
S: serde::Serializer,
557+
{
558+
let dt = DateTime::from_jiff(*source);
559+
dt.serialize(serializer)
560+
}
561+
}
562+
491563
#[cfg(feature = "time-0_3")]
492564
impl From<crate::DateTime> for time::OffsetDateTime {
493565
fn from(bson_dt: DateTime) -> Self {

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
//! | Feature | Description | Default |
6363
//! |:-------------|:-----------------------------------------------------------------------------------------------------|:--------|
6464
//! | `chrono-0_4` | Enable support for v0.4 of the [`chrono`](https://docs.rs/chrono/0.4) crate in the public API. | no |
65+
//! | `jiff-0_2` | Enable support for v0.2 of the [`jiff`](https://docs.rs/jiff/0.2) crate in the public API. | no |
6566
//! | `uuid-1` | Enable support for v1.x of the [`uuid`](https://docs.rs/uuid/1.x) crate in the public API. | no |
6667
//! | `time-0_3` | Enable support for v0.3 of the [`time`](https://docs.rs/time/0.3) crate in the public API. | no |
6768
//! | `serde_with-3` | Enable [`serde_with`](https://docs.rs/serde_with/3.x) 3.x integrations for [`DateTime`] and [`Uuid`]. | no |

src/serde_helpers.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,8 @@ pub mod object_id {
8181
/// - [`datetime::FromI64`] — converts an `i64` to and from a [`crate::DateTime`].
8282
/// - [`datetime::FromChrono04DateTime`] — converts a [`chrono::DateTime`] to and from a
8383
/// [`crate::DateTime`].
84+
/// - [`datetime::FromJiff02Timestamp`] — converts a [`jiff::Timestamp`] to and from a
85+
/// [`crate::DateTime`].
8486
/// - [`datetime::FromTime03OffsetDateTime`] — converts a [`time::OffsetDateTime`] to and from a
8587
/// [`crate::DateTime`].
8688
#[cfg(feature = "serde_with-3")]
@@ -200,6 +202,33 @@ pub mod datetime {
200202
}
201203
);
202204

205+
#[cfg(feature = "jiff-0_2")]
206+
serde_conv_doc!(
207+
/// Converts a [`jiff::Timestamp`] to and from a [`DateTime`].
208+
/// ```rust
209+
/// # #[cfg(all(feature = "jiff-0_2", feature = "serde_with-3"))]
210+
/// # {
211+
/// use bson::serde_helpers::datetime;
212+
/// use serde::{Serialize, Deserialize};
213+
/// use serde_with::serde_as;
214+
/// #[serde_as]
215+
/// #[derive(Serialize, Deserialize)]
216+
/// struct Event {
217+
/// #[serde_as(as = "datetime::FromJiff02Timestamp")]
218+
/// pub date: jiff::Timestamp,
219+
/// }
220+
/// # }
221+
/// ```
222+
pub FromJiff02Timestamp,
223+
jiff::Timestamp,
224+
|jiff_ts: &jiff::Timestamp| -> Result<DateTime, String> {
225+
Ok(DateTime::from_jiff(*jiff_ts))
226+
},
227+
|bson_date: DateTime| -> Result<jiff::Timestamp, String> {
228+
Ok(bson_date.to_jiff())
229+
}
230+
);
231+
203232
#[cfg(feature = "time-0_3")]
204233
serde_conv_doc!(
205234
/// Converts a [`time::OffsetDateTime`] to and from a [`DateTime`].

src/tests/modules/bson.rs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,15 @@ fn from_external_datetime() {
274274
let from_chrono = DateTime::from(now);
275275
assert_millisecond_precision(from_chrono);
276276
}
277+
#[cfg(feature = "jiff-0_2")]
278+
{
279+
let now = jiff::Timestamp::now();
280+
let bson = Bson::from(now);
281+
assert_millisecond_precision(bson.as_datetime().unwrap().to_owned());
282+
283+
let from_jiff = DateTime::from(now);
284+
assert_millisecond_precision(from_jiff);
285+
}
277286

278287
let no_subsec_millis = datetime!(2014-11-28 12:00:09 UTC);
279288
let dt = DateTime::from_time_0_3(no_subsec_millis);
@@ -298,6 +307,17 @@ fn from_external_datetime() {
298307
assert_millisecond_precision(bson.as_datetime().unwrap().to_owned());
299308
assert_subsec_millis(bson.as_datetime().unwrap().to_owned(), 0);
300309
}
310+
#[cfg(feature = "jiff-0_2")]
311+
{
312+
let no_subsec_millis: jiff::Timestamp = "2014-11-28T12:00:09Z".parse().unwrap();
313+
let dt = DateTime::from(no_subsec_millis);
314+
assert_millisecond_precision(dt);
315+
assert_subsec_millis(dt, 0);
316+
317+
let bson = Bson::from(dt);
318+
assert_millisecond_precision(bson.as_datetime().unwrap().to_owned());
319+
assert_subsec_millis(bson.as_datetime().unwrap().to_owned(), 0);
320+
}
301321

302322
for s in &[
303323
"2014-11-28T12:00:09.123Z",
@@ -327,6 +347,17 @@ fn from_external_datetime() {
327347
assert_millisecond_precision(bson.as_datetime().unwrap().to_owned());
328348
assert_subsec_millis(bson.as_datetime().unwrap().to_owned(), 123);
329349
}
350+
#[cfg(feature = "jiff-0_2")]
351+
{
352+
let jiff_ts: jiff::Timestamp = s.parse().unwrap();
353+
let dt = DateTime::from(jiff_ts);
354+
assert_millisecond_precision(dt);
355+
assert_subsec_millis(dt, 123);
356+
357+
let bson = Bson::from(jiff_ts);
358+
assert_millisecond_precision(bson.as_datetime().unwrap().to_owned());
359+
assert_subsec_millis(bson.as_datetime().unwrap().to_owned(), 123);
360+
}
330361
}
331362

332363
#[cfg(feature = "time-0_3")]
@@ -373,6 +404,26 @@ fn from_external_datetime() {
373404
let bdt = DateTime::MIN;
374405
assert_eq!(bdt.to_chrono(), chrono::DateTime::<Utc>::MIN_UTC);
375406
}
407+
#[cfg(feature = "jiff-0_2")]
408+
{
409+
let bdt = DateTime::from(jiff::Timestamp::MAX);
410+
assert_eq!(
411+
bdt.to_jiff().as_millisecond(),
412+
jiff::Timestamp::MAX.as_millisecond()
413+
);
414+
415+
let bdt = DateTime::from(jiff::Timestamp::MIN);
416+
assert_eq!(
417+
bdt.to_jiff().as_millisecond(),
418+
jiff::Timestamp::MIN.as_millisecond()
419+
);
420+
421+
let bdt = DateTime::MAX;
422+
assert_eq!(bdt.to_jiff(), jiff::Timestamp::MAX);
423+
424+
let bdt = DateTime::MIN;
425+
assert_eq!(bdt.to_jiff(), jiff::Timestamp::MIN);
426+
}
376427
}
377428

378429
#[test]

src/tests/modules/serializer_deserializer.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,10 @@ fn test_serialize_utc_date_time() {
326326
#[allow(unused)]
327327
let src = time::OffsetDateTime::from_unix_timestamp(1_286_705_410).unwrap();
328328
#[cfg(feature = "chrono-0_4")]
329+
#[allow(unused)]
329330
let src = chrono::Utc.timestamp_opt(1_286_705_410, 0).unwrap();
331+
#[cfg(feature = "jiff-0_2")]
332+
let src = jiff::Timestamp::from_second(1_286_705_410).unwrap();
330333
let dst = vec![
331334
18, 0, 0, 0, 9, 107, 101, 121, 0, 208, 111, 158, 149, 43, 1, 0, 0, 0,
332335
];

0 commit comments

Comments
 (0)