-
Notifications
You must be signed in to change notification settings - Fork 546
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
✨ Add a NaivePeriod data structure. #384
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -440,7 +440,7 @@ pub use oldtime::Duration; | |
#[cfg(feature="clock")] | ||
#[doc(no_inline)] pub use offset::Local; | ||
#[doc(no_inline)] pub use offset::{TimeZone, Offset, LocalResult, Utc, FixedOffset}; | ||
#[doc(no_inline)] pub use naive::{NaiveDate, IsoWeek, NaiveTime, NaiveDateTime}; | ||
#[doc(no_inline)] pub use naive::{NaiveDate, IsoWeek, NaiveTime, NaiveDateTime, NaivePeriod}; | ||
pub use date::{Date, MIN_DATE, MAX_DATE}; | ||
pub use datetime::{DateTime, SecondsFormat}; | ||
#[cfg(feature = "rustc-serialize")] | ||
|
@@ -455,7 +455,7 @@ pub mod prelude { | |
#[cfg(feature="clock")] | ||
#[doc(no_inline)] pub use Local; | ||
#[doc(no_inline)] pub use {Utc, FixedOffset}; | ||
#[doc(no_inline)] pub use {NaiveDate, NaiveTime, NaiveDateTime}; | ||
#[doc(no_inline)] pub use {NaiveDate, NaiveTime, NaiveDateTime, NaivePeriod}; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here keeping it out of the prelude at least at the beginning. |
||
#[doc(no_inline)] pub use Date; | ||
#[doc(no_inline)] pub use {DateTime, SecondsFormat}; | ||
#[doc(no_inline)] pub use SubsecRound; | ||
|
@@ -482,11 +482,14 @@ pub mod naive { | |
mod isoweek; | ||
mod time; | ||
mod datetime; | ||
mod period; | ||
|
||
pub use self::date::{NaiveDate, MIN_DATE, MAX_DATE}; | ||
pub use self::isoweek::IsoWeek; | ||
pub use self::time::NaiveTime; | ||
pub use self::datetime::NaiveDateTime; | ||
pub use self::period::NaivePeriod; | ||
|
||
#[cfg(feature = "rustc-serialize")] | ||
#[allow(deprecated)] | ||
pub use self::datetime::rustc_serialize::TsSeconds; | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,279 @@ | ||
use naive::NaiveDateTime; | ||
use oldtime::Duration; | ||
|
||
use std::cmp::{min, max}; | ||
|
||
/// A period of time between two ISO 8601 dates/times ([NaiveDateTime](NaiveDateTime)s). | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. fwiw I think docs links only work on nightly, and docs.rs builds on nightly, so all links could be changed from the [`NaiveDateTime`] |
||
/// This is similar to a [`Duration`](Duration), except that it has a fixed start | ||
/// date/time (and thus a fixed end date/time), allowing clients to determine if specific | ||
/// segments of time intersect. | ||
/// | ||
/// # Notes | ||
/// This period is not reliant on time zones. For the time being, offsets aren't considered at all. | ||
/// As such, if you need to interset two time periods that are of differing time zones, you're out | ||
/// of luck (patches accepted, though). | ||
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)] | ||
pub struct NaivePeriod { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we accomplish a bit more future compatibility and smaller api surface with a |
||
/// The date at which the period begins. | ||
pub start: NaiveDateTime, | ||
|
||
/// The date at which the period ends. This is inclusive, meaning that the period runs up to | ||
/// and including this date/time. | ||
pub end: NaiveDateTime | ||
Comment on lines
+18
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should keep these private, and require a constructor. |
||
} | ||
|
||
impl NaivePeriod { | ||
/// Create a new `NaivePeriod` from two [`NaiveDateTime`](NaiveDateTime) objects. | ||
/// | ||
/// # Arguments | ||
/// - `start`: A `NaiveDateTime` representing when the `NaivePeriod` will start. | ||
/// - `end`: A `NaiveDateTime` representing when the `NaivePeriod` will end. Note that this | ||
/// date/time will be included in the period itself. | ||
/// | ||
/// # Returns | ||
/// - A `NaivePeriod` object having the specified start and end `NaiveDateTime`s. | ||
/// | ||
/// # Example | ||
/// ``` | ||
/// # use chrono::{Duration, NaiveDate, NaiveDateTime, NaivePeriod, NaiveTime}; | ||
/// let start = NaiveDateTime::new(NaiveDate::from_ymd(2020, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
/// let end = NaiveDateTime::new(NaiveDate::from_ymd(2021, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
/// | ||
/// let np1 = NaivePeriod::new(start, end); | ||
/// | ||
/// assert_eq!(np1.duration(), Duration::days(366)); | ||
/// ``` | ||
#[inline] | ||
pub fn new(start: NaiveDateTime, end: NaiveDateTime) -> Self { | ||
NaivePeriod { start: start, end: end } | ||
Comment on lines
+47
to
+48
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it reasonable for us to verify that |
||
} | ||
|
||
/// Create a new `NaivePeriod` from a [`NaiveDateTime`](NaiveDateTime) object and a | ||
/// [`Duration`](Duration) object. | ||
/// | ||
/// # Arguments | ||
/// - `start`: A `NaiveDateTime` representing when the `NaivePeriod` will start. | ||
/// - `duration`: A `Duration` object representing the the length of time this `NaivePeriod` | ||
/// will cover. | ||
/// | ||
/// # Returns | ||
/// - A `NaivePeriod` object having the specified start `NaiveDateTime` and length of the | ||
/// specified `Duration`. | ||
/// | ||
/// # Example | ||
/// ``` | ||
/// # use chrono::{Duration, NaiveDate, NaiveDateTime, NaivePeriod, NaiveTime}; | ||
/// let start = NaiveDateTime::new(NaiveDate::from_ymd(2020, 1, 1), NaiveTime::from_hms(12, 0, 0)); | ||
/// | ||
/// let np = NaivePeriod::from_start_duration(start, Duration::days(366)); | ||
/// | ||
/// assert_eq!(Duration::days(366), np.duration()); | ||
/// ``` | ||
#[inline] | ||
pub fn from_start_duration(start: NaiveDateTime, duration: Duration) -> Self { | ||
NaivePeriod { start: start, end: start + duration } | ||
} | ||
|
||
/// Retrieve the [Duration](Duration) this `NaivePeriod` covers. | ||
#[inline] | ||
pub fn duration(&self) -> Duration { | ||
self.end - self.start | ||
} | ||
|
||
/// Retrieve the intersection of this `NaivePeriod` with another `NaivePeriod`, if one exists. | ||
/// | ||
/// # Arguments | ||
/// - `other`: A `NaivePeriod` to intersect with this `NaivePeriod` | ||
/// | ||
/// # Returns | ||
/// - An `Option` containing either the intersection of the two `NaivePeriod`s, if they | ||
/// overlap; `None`, otherwise. | ||
/// | ||
/// # Examples | ||
/// ``` | ||
/// # use chrono::{Duration, NaiveDate, NaiveDateTime, NaivePeriod, NaiveTime}; | ||
/// let start1 = NaiveDateTime::new(NaiveDate::from_ymd(2020, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
/// | ||
/// let np1 = NaivePeriod::from_start_duration(start1, Duration::days(366)); | ||
/// | ||
/// let start2 = NaiveDateTime::new(NaiveDate::from_ymd(2020, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
/// let end = NaiveDateTime::new(NaiveDate::from_ymd(2021, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
/// | ||
/// let np2 = NaivePeriod::new(start2, end); | ||
/// | ||
/// let intersection = np1.get_intersection_with(np2); | ||
/// | ||
/// assert!(intersection.is_some()); | ||
/// assert_eq!(Duration::days(366), intersection.unwrap().duration()) | ||
/// ``` | ||
/// | ||
/// ``` | ||
/// # use chrono::{Duration, NaiveDate, NaiveDateTime, NaivePeriod, NaiveTime}; | ||
/// | ||
/// let start1 = NaiveDateTime::new(NaiveDate::from_ymd(2020, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
/// let end1 = NaiveDateTime::new(NaiveDate::from_ymd(2021, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
/// | ||
/// let np1 = NaivePeriod::new(start1, end1); | ||
/// | ||
/// let start2 = NaiveDateTime::new(NaiveDate::from_ymd(2020, 12, 1), NaiveTime::from_hms(0, 0, 0)); | ||
/// let end2 = NaiveDateTime::new(NaiveDate::from_ymd(2021, 1, 14), NaiveTime::from_hms(0, 0, 0)); | ||
/// | ||
/// let np2 = NaivePeriod::new(start2, end2); | ||
/// | ||
/// let start3 = NaiveDateTime::new(NaiveDate::from_ymd(2020, 12, 1), NaiveTime::from_hms(0, 0, 0)); | ||
/// let end3 = NaiveDateTime::new(NaiveDate::from_ymd(2021, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
/// | ||
/// let np3 = NaivePeriod::new(start3, end3); | ||
/// | ||
/// let intersection = np1.get_intersection_with(np2); | ||
/// | ||
/// assert_eq!(np3, intersection.unwrap()); | ||
/// ``` | ||
pub fn get_intersection_with(&self, other: NaivePeriod) -> Option<NaivePeriod> { | ||
// If the start and end of other are both before self.start or both after self.end, | ||
// then there is no intersection. | ||
let other_start_ts = other.start.timestamp(); | ||
let other_end_ts = other.end.timestamp(); | ||
|
||
if (other_start_ts < self.start.timestamp() && other_end_ts < self.start.timestamp()) | ||
|| (other_end_ts > self.end.timestamp() && other_start_ts > self.end.timestamp()) { | ||
return None; | ||
} | ||
|
||
// The naive time period we want is from the maximum of other_start_ts and | ||
// self.start.timestamp() and the minimum of other_end_ts and self.end.timestamp(). | ||
let start_ts = max(other_start_ts, self.start.timestamp()); | ||
let end_ts = min(other_end_ts, self.end.timestamp()); | ||
|
||
Some(NaivePeriod { | ||
start: NaiveDateTime::from_timestamp(start_ts, 0), | ||
end: NaiveDateTime::from_timestamp(end_ts, 0) | ||
}) | ||
} | ||
|
||
/// Determine if this `NaivePeriod` intersects with another `NaivePeriod`. | ||
/// | ||
/// # Arguments | ||
/// - `other`: A `NaivePeriod` to intersect with this `NaivePeriod` | ||
/// | ||
/// # Returns | ||
/// - `true`, if this `NaivePeriod` and `other` overlap in some finite time period; `false`, | ||
/// otherwise | ||
/// | ||
/// # Example | ||
/// ``` | ||
/// # use chrono::{Duration, NaiveDate, NaiveDateTime, NaivePeriod, NaiveTime}; | ||
/// let start1 = NaiveDateTime::new(NaiveDate::from_ymd(2020, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
/// | ||
/// let np1 = NaivePeriod::from_start_duration(start1, Duration::days(366)); | ||
/// | ||
/// let start2 = NaiveDateTime::new(NaiveDate::from_ymd(2020, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
/// let end = NaiveDateTime::new(NaiveDate::from_ymd(2021, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
/// | ||
/// let np2 = NaivePeriod::new(start2, end); | ||
/// | ||
/// assert!(np1.intersects_with(np2)) | ||
/// ``` | ||
#[inline] | ||
pub fn intersects_with(&self, other: NaivePeriod) -> bool { | ||
self.get_intersection_with(other).is_some() | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use oldtime::Duration; | ||
use naive::{NaiveDate, NaiveDateTime, NaivePeriod, NaiveTime}; | ||
|
||
#[test] | ||
fn test_creation_of_naive_period() { | ||
let start = NaiveDateTime::new(NaiveDate::from_ymd(2020, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
let end = NaiveDateTime::new(NaiveDate::from_ymd(2021, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
|
||
let np1 = NaivePeriod::new(start, end); | ||
|
||
assert_eq!(np1.duration(), Duration::days(366)); | ||
} | ||
|
||
#[test] | ||
fn test_intersection_of_year_and_single_day() { | ||
let start1 = NaiveDateTime::new(NaiveDate::from_ymd(2020, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
let end1 = NaiveDateTime::new(NaiveDate::from_ymd(2021, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
|
||
let np1 = NaivePeriod::new(start1, end1); | ||
|
||
let start2 = NaiveDateTime::new(NaiveDate::from_ymd(2020, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
let end2 = NaiveDateTime::new(NaiveDate::from_ymd(2020, 1, 2), NaiveTime::from_hms(0, 0, 0)); | ||
|
||
let np2 = NaivePeriod::new(start2, end2); | ||
|
||
let intersection = np1.get_intersection_with(np2); | ||
|
||
assert_eq!(intersection.unwrap(), np2); | ||
|
||
// It should also be commutative | ||
assert_eq!(intersection, np2.get_intersection_with(np1)); | ||
} | ||
|
||
#[test] | ||
fn test_intersection_that_creates_a_new_period() { | ||
let start1 = NaiveDateTime::new(NaiveDate::from_ymd(2020, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
let end1 = NaiveDateTime::new(NaiveDate::from_ymd(2021, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
|
||
let np1 = NaivePeriod::new(start1, end1); | ||
|
||
let start2 = NaiveDateTime::new(NaiveDate::from_ymd(2020, 12, 1), NaiveTime::from_hms(0, 0, 0)); | ||
let end2 = NaiveDateTime::new(NaiveDate::from_ymd(2021, 1, 14), NaiveTime::from_hms(0, 0, 0)); | ||
|
||
let np2 = NaivePeriod::new(start2, end2); | ||
|
||
let start3 = NaiveDateTime::new(NaiveDate::from_ymd(2020, 12, 1), NaiveTime::from_hms(0, 0, 0)); | ||
let end3 = NaiveDateTime::new(NaiveDate::from_ymd(2021, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
|
||
let np3 = NaivePeriod::new(start3, end3); | ||
|
||
let intersection = np1.get_intersection_with(np2); | ||
|
||
assert_eq!(np3, intersection.unwrap()); | ||
} | ||
|
||
#[test] | ||
fn test_intersection_of_disjoint_periods() { | ||
let start1 = NaiveDateTime::new(NaiveDate::from_ymd(2020, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
let end1 = NaiveDateTime::new(NaiveDate::from_ymd(2020, 4, 12), NaiveTime::from_hms(0, 0, 0)); | ||
|
||
let np1 = NaivePeriod::new(start1, end1); | ||
|
||
let start2 = NaiveDateTime::new(NaiveDate::from_ymd(2020, 9, 1), NaiveTime::from_hms(0, 0, 0)); | ||
let end2 = NaiveDateTime::new(NaiveDate::from_ymd(2020, 9, 18), NaiveTime::from_hms(0, 0, 0)); | ||
|
||
let np2 = NaivePeriod::new(start2, end2); | ||
|
||
let intersection = np2.get_intersection_with(np1); | ||
|
||
assert!(intersection.is_none()) | ||
} | ||
|
||
#[test] | ||
fn test_creation_of_naive_period_from_duration() { | ||
let start = NaiveDateTime::new(NaiveDate::from_ymd(2020, 1, 1), NaiveTime::from_hms(12, 0, 0)); | ||
|
||
let np = NaivePeriod::from_start_duration(start, Duration::days(366)); | ||
|
||
assert_eq!(Duration::days(366), np.duration()); | ||
} | ||
|
||
#[test] | ||
fn test_intersects_with() { | ||
let start1 = NaiveDateTime::new(NaiveDate::from_ymd(2020, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
|
||
let np1 = NaivePeriod::from_start_duration(start1, Duration::days(366)); | ||
|
||
let start2 = NaiveDateTime::new(NaiveDate::from_ymd(2020, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
let end = NaiveDateTime::new(NaiveDate::from_ymd(2021, 1, 1), NaiveTime::from_hms(0, 0, 0)); | ||
|
||
let np2 = NaivePeriod::new(start2, end); | ||
|
||
assert!(np1.intersects_with(np2)) | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm tempted to keep this out of the root namespace and put the period module behind an
unstable
feature flag for a month or two before it's released with back-compat guarantees. I might change my mind much faster than that, though, or be convinced otherwise.