Skip to content

Commit 0d373bf

Browse files
authored
RUST-815 Truncate bson::DateTimes to millisecond precision (#257)
1 parent 87f8056 commit 0d373bf

File tree

5 files changed

+55
-25
lines changed

5 files changed

+55
-25
lines changed

src/bson.rs

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
2424
use std::fmt::{self, Debug, Display};
2525

26-
use chrono::{Datelike, SecondsFormat, TimeZone, Utc};
26+
use chrono::{Datelike, NaiveDateTime, SecondsFormat, TimeZone, Utc};
2727
use serde_json::{json, Value};
2828

2929
pub use crate::document::Document;
@@ -287,7 +287,7 @@ impl From<oid::ObjectId> for Bson {
287287

288288
impl From<chrono::DateTime<Utc>> for Bson {
289289
fn from(a: chrono::DateTime<Utc>) -> Bson {
290-
Bson::DateTime(DateTime(a))
290+
Bson::DateTime(DateTime::from(a))
291291
}
292292
}
293293

@@ -1019,12 +1019,6 @@ impl crate::DateTime {
10191019
pub fn timestamp_millis(&self) -> i64 {
10201020
self.0.timestamp_millis()
10211021
}
1022-
1023-
/// If this DateTime is sub-millisecond precision. BSON only supports millisecond precision, so
1024-
/// this should be checked before serializing to BSON.
1025-
pub(crate) fn is_sub_millis_precision(&self) -> bool {
1026-
self.0.timestamp_subsec_micros() % 1000 != 0
1027-
}
10281022
}
10291023

10301024
impl Display for crate::DateTime {
@@ -1041,7 +1035,12 @@ impl From<crate::DateTime> for chrono::DateTime<Utc> {
10411035

10421036
impl<T: chrono::TimeZone> From<chrono::DateTime<T>> for crate::DateTime {
10431037
fn from(x: chrono::DateTime<T>) -> Self {
1044-
DateTime(x.with_timezone(&Utc))
1038+
let dt = x.with_timezone(&Utc);
1039+
1040+
DateTime(chrono::DateTime::<Utc>::from_utc(
1041+
NaiveDateTime::from_timestamp(dt.timestamp(), dt.timestamp_subsec_millis() * 1_000_000),
1042+
Utc,
1043+
))
10451044
}
10461045
}
10471046

src/ser/error.rs

Lines changed: 0 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ pub enum Error {
1414
/// A key could not be serialized to a BSON string.
1515
InvalidDocumentKey(Bson),
1616

17-
/// Attempted to serialize a sub-millisecond precision datetime, which BSON does not support.
18-
SubMillisecondPrecisionDateTime(crate::DateTime),
19-
2017
/// A general error that ocurred during serialization.
2118
/// See: https://docs.rs/serde/1.0.110/serde/ser/trait.Error.html#tymethod.custom
2219
#[non_exhaustive]
@@ -66,12 +63,6 @@ impl fmt::Display for Error {
6663
size.",
6764
value
6865
),
69-
Error::SubMillisecondPrecisionDateTime(dt) => write!(
70-
fmt,
71-
"BSON supports millisecond-precision datetimes, could not serialize datetime with \
72-
greater precision losslessly: {}",
73-
dt
74-
),
7566
}
7667
}
7768
}

src/ser/mod.rs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -153,12 +153,7 @@ pub(crate) fn serialize_bson<W: Write + ?Sized>(
153153

154154
writer.write_all(bytes).map_err(From::from)
155155
}
156-
Bson::DateTime(ref v) => {
157-
if v.is_sub_millis_precision() {
158-
return Err(Error::SubMillisecondPrecisionDateTime(*v));
159-
}
160-
write_i64(writer, v.timestamp_millis())
161-
}
156+
Bson::DateTime(ref v) => write_i64(writer, v.timestamp_millis()),
162157
Bson::Null => Ok(()),
163158
Bson::Symbol(ref v) => write_string(writer, &v),
164159
#[cfg(not(feature = "decimal128"))]

src/tests/modules/bson.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use crate::{
77
tests::LOCK,
88
Binary,
99
Bson,
10+
DateTime,
1011
Document,
1112
JavaScriptCodeWithScope,
1213
Regex,
@@ -169,3 +170,47 @@ fn timestamp_ordering() {
169170
assert!(ts1 < ts3);
170171
assert!(ts2 < ts3);
171172
}
173+
174+
#[test]
175+
fn from_chrono_datetime() {
176+
fn assert_precision(dt: DateTime) {
177+
assert_eq!(
178+
chrono::DateTime::<chrono::Utc>::from(dt).timestamp_subsec_micros() % 1000 != 0,
179+
false
180+
)
181+
}
182+
fn assert_millis(dt: DateTime, millis: u32) {
183+
assert_eq!(
184+
chrono::DateTime::<chrono::Utc>::from(dt).timestamp_subsec_millis(),
185+
millis
186+
)
187+
}
188+
189+
let now = chrono::Utc::now();
190+
let dt = DateTime::from(now);
191+
assert_precision(dt);
192+
let bson = Bson::from(now);
193+
assert_precision(bson.as_datetime().unwrap().to_owned());
194+
195+
let chrono_dt: chrono::DateTime<chrono::Utc> = "2014-11-28T12:00:09Z".parse().unwrap();
196+
let dt = DateTime::from(chrono_dt);
197+
assert_precision(dt);
198+
assert_millis(dt, 0);
199+
let bson = Bson::from(chrono_dt);
200+
assert_precision(bson.as_datetime().unwrap().to_owned());
201+
assert_millis(bson.as_datetime().unwrap().to_owned(), 0);
202+
203+
for s in &[
204+
"2014-11-28T12:00:09.123Z",
205+
"2014-11-28T12:00:09.123456Z",
206+
"2014-11-28T12:00:09.123456789Z",
207+
] {
208+
let chrono_dt: chrono::DateTime<chrono::Utc> = s.parse().unwrap();
209+
let dt = DateTime::from(chrono_dt);
210+
assert_precision(dt);
211+
assert_millis(dt, 123);
212+
let bson = Bson::from(chrono_dt);
213+
assert_precision(bson.as_datetime().unwrap().to_owned());
214+
assert_millis(bson.as_datetime().unwrap().to_owned(), 123);
215+
}
216+
}

src/tests/modules/macros.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ fn standard_format() {
4545
base64::encode("thingies"),
4646
base64::encode("secret"),
4747
hex::encode(id_string),
48-
date
48+
date.format("%Y-%m-%d %H:%M:%S%.3f %Z")
4949
);
5050

5151
assert_eq!(expected, format!("{}", doc));

0 commit comments

Comments
 (0)