Skip to content

Commit

Permalink
Merge pull request #46 from moia-oss/ensure-invariant
Browse files Browse the repository at this point in the history
Ensure invariant that TimeWindow::start < end
  • Loading branch information
fabian-braun authored Oct 18, 2023
2 parents 26c12fa + 8d0e12e commit 463af81
Show file tree
Hide file tree
Showing 2 changed files with 55 additions and 13 deletions.
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

0 comments on commit 463af81

Please sign in to comment.