Skip to content

Commit e2c925e

Browse files
authored
RUST-1198 Support creating bson::DateTime from a given year/month/date/hour/minute/second/millisecond (#361)
Added a builder to support creating a `DateTime` from specified date and time arguments.
1 parent 2944e77 commit e2c925e

File tree

3 files changed

+253
-0
lines changed

3 files changed

+253
-0
lines changed

src/datetime.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//! BSON DateTime
2+
13
use std::{
24
convert::TryInto,
35
error,
@@ -6,6 +8,8 @@ use std::{
68
time::{Duration, SystemTime},
79
};
810

11+
pub(crate) mod builder;
12+
pub use crate::datetime::builder::DateTimeBuilder;
913
use time::format_description::well_known::Rfc3339;
1014

1115
#[cfg(feature = "chrono-0_4")]
@@ -41,6 +45,18 @@ use serde_with::{DeserializeAs, SerializeAs};
4145
/// # }
4246
/// ```
4347
///
48+
/// You may also construct this type from a given `year`, `month`, `day`, and optionally,
49+
/// an `hour`, `minute`, `second` and `millisecond`, which default to 0 if not explicitly set.
50+
///
51+
/// ```
52+
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
53+
/// let dt = bson::DateTime::builder().year(1998).month(2).day(12).minute(1).millisecond(23).build()?;
54+
/// let expected = bson::DateTime::parse_rfc3339_str("1998-02-12T00:01:00.023Z")?;
55+
/// assert_eq!(dt, expected);
56+
/// # Ok(())
57+
/// # }
58+
/// ```
59+
///
4460
/// This type differs from [`chrono::DateTime`] in that it serializes to and deserializes from a
4561
/// BSON datetime rather than an RFC 3339 formatted string. Additionally, in non-BSON formats, it
4662
/// will serialize to and deserialize from that format's equivalent of the
@@ -131,6 +147,15 @@ impl crate::DateTime {
131147
Self::from_millis(dt.timestamp_millis())
132148
}
133149

150+
/// Returns a builder used to construct a [`DateTime`] from a given year, month,
151+
/// day, and optionally, an hour, minute, second and millisecond, which default to
152+
/// 0 if not explicitly set.
153+
///
154+
/// Note: You cannot call `build()` before setting at least the year, month and day.
155+
pub fn builder() -> DateTimeBuilder {
156+
DateTimeBuilder::default()
157+
}
158+
134159
/// Convert this [`DateTime`] to a [`chrono::DateTime<Utc>`].
135160
///
136161
/// Note: Not every BSON datetime can be represented as a [`chrono::DateTime`]. For such dates,

src/datetime/builder.rs

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,187 @@
1+
use super::*;
2+
use std::convert::TryFrom;
3+
use time::Date;
4+
5+
/// Builder for constructing a BSON [`DateTime`]
6+
pub struct DateTimeBuilder<Y = NoYear, M = NoMonth, D = NoDay> {
7+
pub(crate) year: Y,
8+
pub(crate) month: M,
9+
pub(crate) day: D,
10+
11+
pub(crate) hour: Option<u8>,
12+
pub(crate) minute: Option<u8>,
13+
pub(crate) second: Option<u8>,
14+
pub(crate) millisecond: Option<u16>,
15+
}
16+
17+
impl Default for DateTimeBuilder {
18+
fn default() -> Self {
19+
Self {
20+
year: NoYear,
21+
month: NoMonth,
22+
day: NoDay,
23+
hour: None,
24+
minute: None,
25+
second: None,
26+
millisecond: None,
27+
}
28+
}
29+
}
30+
31+
pub struct Year(i32);
32+
pub struct NoYear;
33+
34+
pub struct Month(u8);
35+
pub struct NoMonth;
36+
37+
pub struct Day(u8);
38+
pub struct NoDay;
39+
40+
impl<M, D> DateTimeBuilder<NoYear, M, D> {
41+
/// Sets the year for the builder instance. Years between ±9999 inclusive are valid.
42+
/// If the specified value is out of range, calling the `build()` method will return
43+
/// an error.
44+
///
45+
/// Note: This is a required method. You will not be able to call `build()` before calling
46+
/// this method.
47+
pub fn year(self, y: i32) -> DateTimeBuilder<Year, M, D> {
48+
let Self {
49+
year: _,
50+
month,
51+
day,
52+
hour,
53+
minute,
54+
second,
55+
millisecond,
56+
} = self;
57+
DateTimeBuilder {
58+
year: Year(y),
59+
month,
60+
day,
61+
hour,
62+
minute,
63+
second,
64+
millisecond,
65+
}
66+
}
67+
}
68+
69+
impl<Y, D> DateTimeBuilder<Y, NoMonth, D> {
70+
/// Sets the month for the builder instance. Maps months as 1-January to 12-December.
71+
/// If the specified value is out of range, calling the `build()` method will return
72+
/// an error.
73+
///
74+
/// Note: This is a required method. You will not be able to call `build()` before calling
75+
/// this method.
76+
pub fn month(self, m: u8) -> DateTimeBuilder<Y, Month, D> {
77+
let Self {
78+
year,
79+
month: _,
80+
day,
81+
hour,
82+
minute,
83+
second,
84+
millisecond,
85+
} = self;
86+
DateTimeBuilder {
87+
year,
88+
month: Month(m),
89+
day,
90+
hour,
91+
minute,
92+
second,
93+
millisecond,
94+
}
95+
}
96+
}
97+
98+
impl<Y, M> DateTimeBuilder<Y, M, NoDay> {
99+
/// Sets the day for the builder instance. Values in the range `1..=31` are valid.
100+
/// If the specified value does not exist for the provided month/year or is out of range,
101+
/// calling the `build()` method will return an error.
102+
///
103+
/// Note: This is a required method. You will not be able to call `build()` before calling
104+
/// this method.
105+
pub fn day(self, d: u8) -> DateTimeBuilder<Y, M, Day> {
106+
let Self {
107+
year,
108+
month,
109+
day: _,
110+
hour,
111+
minute,
112+
second,
113+
millisecond,
114+
} = self;
115+
DateTimeBuilder {
116+
year,
117+
month,
118+
day: Day(d),
119+
hour,
120+
minute,
121+
second,
122+
millisecond,
123+
}
124+
}
125+
}
126+
127+
impl<Y, M, D> DateTimeBuilder<Y, M, D> {
128+
/// Sets the hour (24-hour format) for the builder instance. Values must be in the range
129+
/// `0..=23`. If the specified value is out of range, calling the `build()` method will
130+
/// return an error.
131+
///
132+
/// Note: This is an optional method. The hour will default to 0 if not explicitly set.
133+
pub fn hour(mut self, hour: u8) -> DateTimeBuilder<Y, M, D> {
134+
self.hour = Some(hour);
135+
self
136+
}
137+
138+
/// Sets the minute for the builder instance. Values must be in the range `0..=59`.
139+
/// If the specified value is out of range, calling the `build()` method will return an error.
140+
///
141+
/// Note: This is an optional method. The minute will default to 0 if not explicitly set.
142+
pub fn minute(mut self, minute: u8) -> DateTimeBuilder<Y, M, D> {
143+
self.minute = Some(minute);
144+
self
145+
}
146+
147+
/// Sets the second for the builder instance. Values must be in range `0..=59`.
148+
/// If the specified value is out of range, calling the `build()` method will return an error.
149+
///
150+
/// Note: This is an optional method. The second will default to 0 if not explicitly set.
151+
pub fn second(mut self, second: u8) -> DateTimeBuilder<Y, M, D> {
152+
self.second = Some(second);
153+
self
154+
}
155+
156+
/// Sets the millisecond for the builder instance. Values must be in the range `0..=999`.
157+
/// If the specified value is out of range, calling the `build()` method will return an error.
158+
///
159+
/// Note: This is an optional method. The millisecond will default to 0 if not explicitly set.
160+
pub fn millisecond(mut self, millisecond: u16) -> DateTimeBuilder<Y, M, D> {
161+
self.millisecond = Some(millisecond);
162+
self
163+
}
164+
}
165+
166+
impl DateTimeBuilder<Year, Month, Day> {
167+
/// Convert a builder with a specified year, month, day, and optionally, an hour, minute, second
168+
/// and millisecond to a [`DateTime`].
169+
///
170+
/// Note: You cannot call `build()` before setting at least the year, month and day.
171+
pub fn build(self) -> Result<DateTime> {
172+
let err = |e: time::error::ComponentRange| Error::InvalidTimestamp {
173+
message: e.to_string(),
174+
};
175+
let month = time::Month::try_from(self.month.0).map_err(err)?;
176+
let dt = Date::from_calendar_date(self.year.0, month, self.day.0)
177+
.map_err(err)?
178+
.with_hms_milli(
179+
self.hour.unwrap_or(0),
180+
self.minute.unwrap_or(0),
181+
self.second.unwrap_or(0),
182+
self.millisecond.unwrap_or(0),
183+
)
184+
.map_err(err)?;
185+
Ok(DateTime::from_time_private(dt.assume_utc()))
186+
}
187+
}

src/tests/modules/bson.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use crate::{
1616
Regex,
1717
Timestamp,
1818
};
19+
1920
use serde_json::{json, Value};
2021

2122
#[test]
@@ -363,6 +364,46 @@ fn from_external_datetime() {
363364
}
364365
}
365366

367+
#[test]
368+
fn from_datetime_builder() {
369+
{
370+
let dt = DateTime::builder()
371+
.year(2022)
372+
.month(9)
373+
.day(15)
374+
.minute(2)
375+
.millisecond(1)
376+
.build();
377+
assert!(dt.is_ok());
378+
assert_eq!(
379+
DateTime::from_time_0_3(time::macros::datetime!(2022 - 09 - 15 00:02:00.001 UTC)),
380+
dt.unwrap()
381+
);
382+
}
383+
384+
{
385+
let dt = DateTime::builder()
386+
.year(2022)
387+
.month(18)
388+
.day(15)
389+
.minute(2)
390+
.millisecond(1)
391+
.build();
392+
assert!(dt.is_err());
393+
}
394+
395+
{
396+
let dt = DateTime::builder()
397+
.year(2022)
398+
.day(15)
399+
.month(18)
400+
.minute(83)
401+
.millisecond(1)
402+
.build();
403+
assert!(dt.is_err());
404+
}
405+
}
406+
366407
#[test]
367408
fn system_time() {
368409
let _guard = LOCK.run_concurrently();

0 commit comments

Comments
 (0)