Skip to content
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

Ensure invariant that TimeWindow::start < end #46

Merged
merged 2 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ derive_more = "0.99.17"
lazy_static = "1.4.0"
regex = "1.7.1"
serde = { version = "1.0.152", features = ["derive"], default-features = false }
thiserror = "1.0.49"

[dev-dependencies]
serde_test = "1.0.152"
Expand Down
67 changes: 54 additions & 13 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ use regex::Regex;
use serde::de::Visitor;
use serde::Deserialize;
use serde::Serialize;
use thiserror::Error;

/// A point in time.
///
Expand All @@ -76,6 +77,7 @@ use serde::Serialize;
Eq, PartialEq, Hash, Ord, PartialOrd, Copy, Clone, Debug, Default, Serialize, Deref, From, Into,
)]
pub struct Time(i64);

impl Time {
pub const MAX: Self = Self(i64::MAX);
pub const EPOCH: Self = Self(0);
Expand Down Expand Up @@ -271,6 +273,7 @@ impl<'de> Deserialize<'de> for Time {
}

struct TimeVisitor;

impl<'de> Visitor<'de> for TimeVisitor {
type Value = Time;

Expand Down Expand Up @@ -317,17 +320,54 @@ impl From<Time> for std::time::SystemTime {
}
}

#[derive(Error, Debug, Eq, PartialEq)]
pub enum TimeWindowError {
#[error("time window start is after end")]
StartAfterEnd,
}

/// An interval or range of time: `[start,end)`.
/// Debug-asserts ensure that start <= end.
/// If compiled in release mode, the invariant of start <= end is maintained, by
/// correcting invalid use of the API (and setting end to start).
#[derive(Clone, Debug, Eq, PartialEq, Default, Copy, Serialize, Deserialize, From, Into, Hash)]
pub struct TimeWindow {
start: Time,
end: Time,
}

impl TimeWindow {
/// constructs a new TimeWindow.
/// debug_asserts that start < end. Sets end to start in release mode if
/// start > end.
pub fn new(start: Time, end: Time) -> Self {
debug_assert!(start <= end);
TimeWindow { start, end }
TimeWindow {
start,
end: end.max(start),
}
}

/// Constructs a new TimeWindow. Validates that start <= end and
/// returns an error if not.
/// # Examples
///
/// ```
/// # use tinytime::{Duration, TimeWindowError};
/// # use tinytime::Time;
/// # use tinytime::TimeWindow;
/// assert!(TimeWindow::new_checked(Time::hours(1), Time::hours(2)).is_ok());
/// assert_eq!(
/// Err(TimeWindowError::StartAfterEnd),
/// TimeWindow::new_checked(Time::hours(2), Time::hours(1))
/// );
/// ```
pub fn new_checked(start: Time, end: Time) -> Result<Self, TimeWindowError> {
if start <= end {
Ok(TimeWindow { start, end })
} else {
Err(TimeWindowError::StartAfterEnd)
}
}

/// Returns [`TimeWindow`] with range [[`Time::EPOCH`], `end`)
Expand Down Expand Up @@ -403,14 +443,12 @@ impl TimeWindow {
self.end - self.start
}

pub fn set_start(&mut self, start: Time) {
debug_assert!(start <= self.end);
self.start = start;
pub fn with_new_start(self, start: Time) -> TimeWindow {
TimeWindow::new(start, self.end)
}

pub fn set_end(&mut self, end: Time) {
debug_assert!(self.start <= end);
self.end = end;
pub fn with_new_end(self, end: Time) -> TimeWindow {
TimeWindow::new(self.start, end)
}

/// Extends time window start to the given value. Is a No-Op when given
Expand All @@ -431,7 +469,7 @@ impl TimeWindow {
pub fn extend_start(&mut self, new_start: Time) -> Option<Duration> {
(new_start < self.start).then(|| {
let diff = self.start - new_start;
self.set_start(new_start);
*self = self.with_new_start(new_start);
diff
})
}
Expand All @@ -454,7 +492,7 @@ impl TimeWindow {
pub fn extend_end(&mut self, new_end: Time) -> Option<Duration> {
(new_end > self.end).then(|| {
let diff = new_end - self.end;
self.set_end(new_end);
*self = self.with_new_end(new_end);
diff
})
}
Expand Down Expand Up @@ -516,9 +554,9 @@ impl TimeWindow {
pub fn shrink_towards_end(&mut self, new_start: Time) {
if new_start > self.start {
if new_start > self.end {
self.set_start(self.end);
*self = self.with_new_start(self.end);
} else {
self.set_start(new_start);
*self = self.with_new_start(new_start);
}
}
}
Expand All @@ -541,9 +579,9 @@ impl TimeWindow {
pub fn shrink_towards_start(&mut self, new_end: Time) {
if new_end < self.end {
if new_end < self.start {
self.set_end(self.start);
*self = self.with_new_end(self.start);
} else {
self.set_end(new_end);
*self = self.with_new_end(new_end);
}
}
}
Expand Down Expand Up @@ -858,6 +896,7 @@ impl PartialOrd<std::time::Duration> for Duration {
}

struct DurationVisitor;

impl<'de> Visitor<'de> for DurationVisitor {
type Value = Duration;

Expand Down Expand Up @@ -1197,6 +1236,7 @@ impl From<Duration> for std::time::Duration {
std::time::Duration::new(secs, nanos)
}
}

impl From<std::time::Duration> for Duration {
fn from(input: std::time::Duration) -> Self {
debug_assert!(
Expand Down Expand Up @@ -1266,6 +1306,7 @@ impl FromStr for Duration {
pub enum DurationParseError {
UnrecognizedFormat,
}

impl Error for DurationParseError {}

impl Display for DurationParseError {
Expand Down
Loading