Skip to content

Commit d60816d

Browse files
authored
RUST-977 Support RFC 3339 to bson::DateTime conversion without chrono feature flag (#317)
1 parent a5c1e20 commit d60816d

File tree

6 files changed

+69
-6
lines changed

6 files changed

+69
-6
lines changed

src/bson.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -423,7 +423,7 @@ impl Bson {
423423
Bson::ObjectId(v) => json!({"$oid": v.to_hex()}),
424424
Bson::DateTime(v) if v.timestamp_millis() >= 0 && v.to_chrono().year() <= 99999 => {
425425
json!({
426-
"$date": v.to_rfc3339(),
426+
"$date": v.to_rfc3339_string(),
427427
})
428428
}
429429
Bson::DateTime(v) => json!({
@@ -573,7 +573,7 @@ impl Bson {
573573
}
574574
Bson::DateTime(v) if v.timestamp_millis() >= 0 && v.to_chrono().year() <= 9999 => {
575575
doc! {
576-
"$date": v.to_rfc3339(),
576+
"$date": v.to_rfc3339_string(),
577577
}
578578
}
579579
Bson::DateTime(v) => doc! {

src/datetime.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
use std::{
2+
error,
23
fmt::{self, Display},
4+
result,
35
time::{Duration, SystemTime},
46
};
57

@@ -167,10 +169,21 @@ impl crate::DateTime {
167169
self.0
168170
}
169171

170-
pub(crate) fn to_rfc3339(self) -> String {
172+
/// Convert this [`DateTime`] to an RFC 3339 formatted string.
173+
pub fn to_rfc3339_string(self) -> String {
171174
self.to_chrono()
172175
.to_rfc3339_opts(chrono::SecondsFormat::AutoSi, true)
173176
}
177+
178+
/// Convert the given RFC 3339 formatted string to a [`DateTime`], truncating it to millisecond
179+
/// precision.
180+
pub fn parse_rfc3339_str(s: impl AsRef<str>) -> Result<Self> {
181+
let date = chrono::DateTime::<chrono::FixedOffset>::parse_from_rfc3339(s.as_ref())
182+
.map_err(|e| Error::InvalidTimestamp {
183+
message: e.to_string(),
184+
})?;
185+
Ok(Self::from_chrono(date))
186+
}
174187
}
175188

176189
impl fmt::Debug for crate::DateTime {
@@ -220,3 +233,27 @@ impl<T: chrono::TimeZone> From<chrono::DateTime<T>> for crate::DateTime {
220233
Self::from_chrono(x)
221234
}
222235
}
236+
237+
/// Errors that can occur during [`DateTime`] construction and generation.
238+
#[derive(Clone, Debug)]
239+
#[non_exhaustive]
240+
pub enum Error {
241+
/// Error returned when an invalid datetime format is provided to a conversion method.
242+
#[non_exhaustive]
243+
InvalidTimestamp { message: String },
244+
}
245+
246+
/// Alias for `Result<T, DateTime::Error>`
247+
pub type Result<T> = result::Result<T, Error>;
248+
249+
impl fmt::Display for Error {
250+
fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
251+
match self {
252+
Error::InvalidTimestamp { message } => {
253+
write!(fmt, "{}", message)
254+
}
255+
}
256+
}
257+
}
258+
259+
impl error::Error for Error {}

src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ pub use self::{
288288
#[macro_use]
289289
mod macros;
290290
mod bson;
291-
mod datetime;
291+
pub mod datetime;
292292
pub mod de;
293293
pub mod decimal128;
294294
pub mod document;

src/serde_helpers.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -252,7 +252,7 @@ pub mod rfc3339_string_as_bson_datetime {
252252
D: Deserializer<'de>,
253253
{
254254
let date = DateTime::deserialize(deserializer)?;
255-
Ok(date.to_rfc3339())
255+
Ok(date.to_rfc3339_string())
256256
}
257257

258258
/// Serializes an ISO string as a DateTime.
@@ -297,7 +297,7 @@ pub mod bson_datetime_as_rfc3339_string {
297297

298298
/// Serializes a [`crate::DateTime`] as an RFC 3339 (ISO 8601) formatted string.
299299
pub fn serialize<S: Serializer>(val: &DateTime, serializer: S) -> Result<S::Ok, S::Error> {
300-
serializer.serialize_str(&val.to_rfc3339())
300+
serializer.serialize_str(&val.to_rfc3339_string())
301301
}
302302
}
303303

src/tests/datetime.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use crate::tests::LOCK;
2+
use std::str::FromStr;
3+
4+
#[test]
5+
fn rfc3339_to_datetime() {
6+
let _guard = LOCK.run_concurrently();
7+
8+
let rfc = "2020-06-09T10:58:07.095Z";
9+
let date = chrono::DateTime::<chrono::Utc>::from_str(rfc).unwrap();
10+
let parsed = crate::DateTime::parse_rfc3339_str(rfc).unwrap();
11+
assert_eq!(parsed, crate::DateTime::from_chrono(date));
12+
assert_eq!(crate::DateTime::to_rfc3339_string(parsed), rfc);
13+
}
14+
15+
#[test]
16+
fn invalid_rfc3339_to_datetime() {
17+
let _guard = LOCK.run_concurrently();
18+
19+
let a = "2020-06-09T10:58:07-095Z";
20+
let b = "2020-06-09T10:58:07.095";
21+
let c = "2020-06-09T10:62:07.095Z";
22+
assert!(crate::DateTime::parse_rfc3339_str(a).is_err());
23+
assert!(crate::DateTime::parse_rfc3339_str(b).is_err());
24+
assert!(crate::DateTime::parse_rfc3339_str(c).is_err());
25+
}

src/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
mod binary_subtype;
2+
mod datetime;
23
mod modules;
34
mod serde;
45
mod spec;

0 commit comments

Comments
 (0)