use std::error::Error;
use std::fmt::Display;
use std::time::Duration as StdDuration;
use jiff::tz::{AmbiguousZoned, TimeZone};
use jiff::{SignedDuration, Timestamp, Zoned};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum PrecisionMode {
ToNearest,
ToFuture,
ToPast,
}
impl PrecisionMode {
#[must_use]
pub fn unchecked_with_precision(self, precision: StdDuration) -> Precision {
Precision::unchecked_new(precision, self)
}
pub fn with_precision(self, precision: StdDuration) -> Result<Precision, PrecisionCreationPrecisionIsZeroError> {
Precision::new(precision, self)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub struct Precision {
precision: StdDuration,
mode: PrecisionMode,
}
impl Precision {
#[must_use]
pub fn unchecked_new(precision: StdDuration, mode: PrecisionMode) -> Self {
Precision {
precision,
mode,
}
}
pub fn new(precision: StdDuration, mode: PrecisionMode) -> Result<Self, PrecisionCreationPrecisionIsZeroError> {
if precision.is_zero() {
return Err(PrecisionCreationPrecisionIsZeroError);
}
Ok(Self::unchecked_new(precision, mode))
}
#[must_use]
pub fn precision(&self) -> StdDuration {
self.precision
}
#[must_use]
pub fn mode(&self) -> PrecisionMode {
self.mode
}
#[must_use]
pub fn precise_unsigned_nanos(&self, duration: u128) -> u128 {
let precision_nanos = self.precision().as_nanos();
let timestamp_rem = duration % precision_nanos;
if timestamp_rem == 0 {
return duration;
}
let truncated_timestamp = duration - timestamp_rem;
match self.mode() {
PrecisionMode::ToNearest => {
let precision_midpoint = precision_nanos / 2;
if timestamp_rem.cmp(&precision_midpoint).is_ge() {
truncated_timestamp.saturating_add(precision_nanos)
} else {
truncated_timestamp
}
},
PrecisionMode::ToFuture => truncated_timestamp.saturating_add(precision_nanos),
PrecisionMode::ToPast => truncated_timestamp,
}
}
#[must_use]
pub fn precise_signed_nanos(&self, duration: i128) -> i128 {
let precision_nanos = self.precision().as_nanos();
let timestamp_rem = duration.unsigned_abs() % precision_nanos;
if timestamp_rem == 0 {
return duration;
}
let timestamp_diff_to_past = match duration.signum() {
1 => timestamp_rem,
0 => 0, -1 => precision_nanos - timestamp_rem,
_ => unreachable!("core::num::signum is guaranteed to return only in the range -1..=1"),
};
let truncated_timestamp = duration.saturating_sub_unsigned(timestamp_diff_to_past);
match self.mode() {
PrecisionMode::ToNearest => {
let precision_midpoint = precision_nanos / 2;
if timestamp_diff_to_past.cmp(&precision_midpoint).is_ge() {
truncated_timestamp.saturating_add_unsigned(precision_nanos)
} else {
truncated_timestamp
}
},
PrecisionMode::ToFuture => truncated_timestamp.saturating_add_unsigned(precision_nanos),
PrecisionMode::ToPast => truncated_timestamp,
}
}
pub fn precise_duration(&self, duration: StdDuration) -> Result<StdDuration, PrecisionOutOfRangeError> {
let new_timestamp = self.precise_unsigned_nanos(duration.as_nanos());
let nanos_per_sec = StdDuration::from_secs(1).as_nanos();
let secs_component = u64::try_from(new_timestamp / nanos_per_sec).or(Err(PrecisionOutOfRangeError))?;
let nanos_component = u32::try_from(new_timestamp % nanos_per_sec).or(Err(PrecisionOutOfRangeError))?;
Ok(StdDuration::new(secs_component, nanos_component))
}
pub fn precise_duration_with_base_offset(
&self,
duration: StdDuration,
base: StdDuration,
) -> Result<StdDuration, PrecisionOutOfRangeError> {
base.checked_add(self.precise_duration(duration.checked_sub(base).ok_or(PrecisionOutOfRangeError)?)?)
.ok_or(PrecisionOutOfRangeError)
}
pub fn precise_duration_with_base_offset_via_signed(
&self,
duration: StdDuration,
base: StdDuration,
) -> Result<StdDuration, PrecisionOutOfRangeError> {
let signed_duration =
SignedDuration::try_from_nanos_i128(i128::try_from(duration.as_nanos()).or(Err(PrecisionOutOfRangeError))?)
.ok_or(PrecisionOutOfRangeError)?;
let signed_base =
SignedDuration::try_from_nanos_i128(i128::try_from(base.as_nanos()).or(Err(PrecisionOutOfRangeError))?)
.ok_or(PrecisionOutOfRangeError)?;
let unsigned_precised_duration = u128::try_from(
self.precise_signed_duration_with_base_offset(signed_duration, signed_base)?
.as_nanos(),
)
.or(Err(PrecisionOutOfRangeError))?;
let nanos_per_sec = StdDuration::from_secs(1).as_nanos();
let secs_component =
u64::try_from(unsigned_precised_duration / nanos_per_sec).or(Err(PrecisionOutOfRangeError))?;
let nanos_component =
u32::try_from(unsigned_precised_duration % nanos_per_sec).or(Err(PrecisionOutOfRangeError))?;
Ok(StdDuration::new(secs_component, nanos_component))
}
pub fn precise_signed_duration(
&self,
signed_duration: SignedDuration,
) -> Result<SignedDuration, PrecisionOutOfRangeError> {
SignedDuration::try_from_nanos_i128(self.precise_signed_nanos(signed_duration.as_nanos()))
.ok_or(PrecisionOutOfRangeError)
}
pub fn precise_signed_duration_with_base_offset(
&self,
duration: SignedDuration,
base: SignedDuration,
) -> Result<SignedDuration, PrecisionOutOfRangeError> {
base.checked_add(self.precise_signed_duration(duration.checked_sub(base).ok_or(PrecisionOutOfRangeError)?)?)
.ok_or(PrecisionOutOfRangeError)
}
pub fn precise_time(&self, time: &Zoned) -> Result<AmbiguousZoned, PrecisionOutOfRangeError> {
let utc_day_start = time
.datetime()
.start_of_day()
.to_zoned(TimeZone::UTC)
.or(Err(PrecisionOutOfRangeError))?;
let duration_diff = time
.datetime()
.to_zoned(TimeZone::UTC)
.or(Err(PrecisionOutOfRangeError))?
.timestamp()
.duration_since(utc_day_start.timestamp());
let precised_datetime = utc_day_start
.checked_add(self.precise_signed_duration(duration_diff)?)
.or(Err(PrecisionOutOfRangeError))?
.datetime();
Ok(time.time_zone().to_ambiguous_zoned(precised_datetime))
}
pub fn precise_time_with_base_time(
&self,
time: &Zoned,
base: Timestamp,
) -> Result<Zoned, PrecisionOutOfRangeError> {
let base = base.to_zoned(time.time_zone().clone());
base.checked_add(self.precise_signed_duration(time.duration_since(&base))?)
.map_err(|_| PrecisionOutOfRangeError)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PrecisionCreationPrecisionIsZeroError;
impl Display for PrecisionCreationPrecisionIsZeroError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Duration given as a precision is zero")
}
}
impl Error for PrecisionCreationPrecisionIsZeroError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct PrecisionOutOfRangeError;
impl Display for PrecisionOutOfRangeError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Operation produced an out-of-range value")
}
}
impl Error for PrecisionOutOfRangeError {}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum RunningResult<R, D = R> {
Running(R),
Done(D),
}
impl<R, D> RunningResult<R, D> {
pub fn is_running(&self) -> bool {
matches!(self, Self::Running(_))
}
pub fn is_done(&self) -> bool {
matches!(self, Self::Done(_))
}
#[must_use]
pub fn running(self) -> Option<R> {
match self {
Self::Running(r) => Some(r),
Self::Done(_) => None,
}
}
#[must_use]
pub fn done(self) -> Option<D> {
match self {
Self::Running(_) => None,
Self::Done(d) => Some(d),
}
}
pub fn map_running<F, T>(self, f: F) -> RunningResult<T, D>
where
F: FnOnce(R) -> T,
{
match self {
Self::Running(r) => RunningResult::Running((f)(r)),
Self::Done(d) => RunningResult::Done(d),
}
}
pub fn map_done<F, T>(self, f: F) -> RunningResult<R, T>
where
F: FnOnce(D) -> T,
{
match self {
Self::Running(r) => RunningResult::Running(r),
Self::Done(d) => RunningResult::Done((f)(d)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum ComplementResult<C> {
Single(C),
Split(C, C),
}
impl<C> ComplementResult<C> {
pub fn is_single(&self) -> bool {
matches!(self, Self::Single(_))
}
pub fn is_split(&self) -> bool {
matches!(self, Self::Split(..))
}
#[must_use]
pub fn single(self) -> Option<C> {
match self {
Self::Single(s) => Some(s),
Self::Split(..) => None,
}
}
#[must_use]
pub fn split(self) -> Option<(C, C)> {
match self {
Self::Single(_) => None,
Self::Split(s1, s2) => Some((s1, s2)),
}
}
pub fn map<F, T>(self, mut f: F) -> ComplementResult<T>
where
F: FnMut(C) -> T,
{
match self {
Self::Single(c) => ComplementResult::Single((f)(c)),
Self::Split(c1, c2) => ComplementResult::Split((f)(c1), (f)(c2)),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum UnionResult<U> {
United(U),
Separate,
}
impl<U> UnionResult<U> {
pub fn is_united(&self) -> bool {
matches!(self, Self::United(_))
}
pub fn is_separate(&self) -> bool {
matches!(self, Self::Separate)
}
#[must_use]
pub fn united(self) -> Option<U> {
match self {
Self::United(u) => Some(u),
Self::Separate => None,
}
}
pub fn map_united<F, T>(self, f: F) -> UnionResult<T>
where
F: FnOnce(U) -> T,
{
match self {
UnionResult::United(u) => UnionResult::United((f)(u)),
UnionResult::Separate => UnionResult::Separate,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum IntersectionResult<I> {
Intersected(I),
Separate,
}
impl<I> IntersectionResult<I> {
pub fn is_intersected(&self) -> bool {
matches!(self, Self::Intersected(_))
}
pub fn is_separate(&self) -> bool {
matches!(self, Self::Separate)
}
#[must_use]
pub fn intersected(self) -> Option<I> {
match self {
Self::Intersected(i) => Some(i),
Self::Separate => None,
}
}
pub fn map_intersected<F, T>(self, f: F) -> IntersectionResult<T>
where
F: FnOnce(I) -> T,
{
match self {
Self::Intersected(i) => IntersectionResult::Intersected((f)(i)),
Self::Separate => IntersectionResult::Separate,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum DifferenceResult<D> {
Single(D),
Split(D, D),
Separate,
}
impl<D> DifferenceResult<D> {
pub fn is_difference(&self) -> bool {
matches!(self, Self::Single(_) | Self::Split(..))
}
pub fn is_difference_single(&self) -> bool {
matches!(self, Self::Single(_))
}
pub fn is_difference_split(&self) -> bool {
matches!(self, Self::Split(..))
}
pub fn is_separate(&self) -> bool {
matches!(self, Self::Separate)
}
#[must_use]
pub fn single(self) -> Option<D> {
match self {
Self::Single(s) => Some(s),
Self::Split(..) | Self::Separate => None,
}
}
#[must_use]
pub fn split(self) -> Option<(D, D)> {
match self {
Self::Split(s1, s2) => Some((s1, s2)),
Self::Single(_) | Self::Separate => None,
}
}
pub fn map_difference<F, T>(self, mut f: F) -> DifferenceResult<T>
where
F: FnMut(D) -> T,
{
match self {
Self::Single(d) => DifferenceResult::Single((f)(d)),
Self::Split(d1, d2) => DifferenceResult::Split((f)(d1), (f)(d2)),
Self::Separate => DifferenceResult::Separate,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
pub enum SymmetricDifferenceResult<D> {
Single(D),
Split(D, D),
Separate,
}
impl<D> SymmetricDifferenceResult<D> {
pub fn is_symmetric_difference(&self) -> bool {
matches!(self, Self::Single(_) | Self::Split(..))
}
pub fn is_single(&self) -> bool {
matches!(self, Self::Single(_))
}
pub fn is_split(&self) -> bool {
matches!(self, Self::Split(..))
}
pub fn is_separate(&self) -> bool {
matches!(self, Self::Separate)
}
#[must_use]
pub fn single(self) -> Option<D> {
match self {
Self::Single(s) => Some(s),
Self::Split(..) | Self::Separate => None,
}
}
#[must_use]
pub fn split(self) -> Option<(D, D)> {
match self {
Self::Split(s1, s2) => Some((s1, s2)),
Self::Single(_) | Self::Separate => None,
}
}
pub fn map_symmetric_difference<F, T>(self, mut f: F) -> SymmetricDifferenceResult<T>
where
F: FnMut(D) -> T,
{
match self {
Self::Single(d) => SymmetricDifferenceResult::Single((f)(d)),
Self::Split(d1, d2) => SymmetricDifferenceResult::Split((f)(d1), (f)(d2)),
Self::Separate => SymmetricDifferenceResult::Separate,
}
}
}