use core::{cmp::Ordering, time::Duration as UnsignedDuration};
use alloc::borrow::Cow;
use crate::{
civil::{Date, DateTime, Time},
duration::{Duration, SDuration},
error::{err, Error, ErrorContext},
fmt::temporal::{DEFAULT_SPAN_PARSER, DEFAULT_SPAN_PRINTER},
tz::TimeZone,
util::{
rangeint::{ri64, ri8, RFrom, RInto, TryRFrom, TryRInto},
round::increment,
t::{self, Constant, NoUnits, NoUnits128, Sign, C},
},
RoundMode, SignedDuration, Timestamp, Zoned,
};
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct Span {
sign: Sign,
years: t::SpanYears,
months: t::SpanMonths,
weeks: t::SpanWeeks,
days: t::SpanDays,
hours: t::SpanHours,
minutes: t::SpanMinutes,
seconds: t::SpanSeconds,
milliseconds: t::SpanMilliseconds,
microseconds: t::SpanMicroseconds,
nanoseconds: t::SpanNanoseconds,
}
impl Span {
pub fn new() -> Span {
Span::default()
}
#[inline]
pub fn years<I: Into<i64>>(self, years: I) -> Span {
self.try_years(years).expect("value for years is out of bounds")
}
#[inline]
pub fn months<I: Into<i64>>(self, months: I) -> Span {
self.try_months(months).expect("value for months is out of bounds")
}
#[inline]
pub fn weeks<I: Into<i64>>(self, weeks: I) -> Span {
self.try_weeks(weeks).expect("value for weeks is out of bounds")
}
#[inline]
pub fn days<I: Into<i64>>(self, days: I) -> Span {
self.try_days(days).expect("value for days is out of bounds")
}
#[inline]
pub fn hours<I: Into<i64>>(self, hours: I) -> Span {
self.try_hours(hours).expect("value for hours is out of bounds")
}
#[inline]
pub fn minutes<I: Into<i64>>(self, minutes: I) -> Span {
self.try_minutes(minutes).expect("value for minutes is out of bounds")
}
#[inline]
pub fn seconds<I: Into<i64>>(self, seconds: I) -> Span {
self.try_seconds(seconds).expect("value for seconds is out of bounds")
}
#[inline]
pub fn milliseconds<I: Into<i64>>(self, milliseconds: I) -> Span {
self.try_milliseconds(milliseconds)
.expect("value for milliseconds is out of bounds")
}
#[inline]
pub fn microseconds<I: Into<i64>>(self, microseconds: I) -> Span {
self.try_microseconds(microseconds)
.expect("value for microseconds is out of bounds")
}
#[inline]
pub fn nanoseconds<I: Into<i64>>(self, nanoseconds: I) -> Span {
self.try_nanoseconds(nanoseconds)
.expect("value for nanoseconds is out of bounds")
}
}
impl Span {
#[inline]
pub fn try_years<I: Into<i64>>(self, years: I) -> Result<Span, Error> {
let years = t::SpanYears::try_new("years", years)?;
Ok(self.years_ranged(years))
}
#[inline]
pub fn try_months<I: Into<i64>>(self, months: I) -> Result<Span, Error> {
type Range = ri64<{ t::SpanMonths::MIN }, { t::SpanMonths::MAX }>;
let months = Range::try_new("months", months)?;
Ok(self.months_ranged(months))
}
#[inline]
pub fn try_weeks<I: Into<i64>>(self, weeks: I) -> Result<Span, Error> {
type Range = ri64<{ t::SpanWeeks::MIN }, { t::SpanWeeks::MAX }>;
let weeks = Range::try_new("weeks", weeks)?;
Ok(self.weeks_ranged(weeks))
}
#[inline]
pub fn try_days<I: Into<i64>>(self, days: I) -> Result<Span, Error> {
type Range = ri64<{ t::SpanDays::MIN }, { t::SpanDays::MAX }>;
let days = Range::try_new("days", days)?;
Ok(self.days_ranged(days))
}
#[inline]
pub fn try_hours<I: Into<i64>>(self, hours: I) -> Result<Span, Error> {
type Range = ri64<{ t::SpanHours::MIN }, { t::SpanHours::MAX }>;
let hours = Range::try_new("hours", hours)?;
Ok(self.hours_ranged(hours))
}
#[inline]
pub fn try_minutes<I: Into<i64>>(self, minutes: I) -> Result<Span, Error> {
type Range = ri64<{ t::SpanMinutes::MIN }, { t::SpanMinutes::MAX }>;
let minutes = Range::try_new("minutes", minutes.into())?;
Ok(self.minutes_ranged(minutes))
}
#[inline]
pub fn try_seconds<I: Into<i64>>(self, seconds: I) -> Result<Span, Error> {
type Range = ri64<{ t::SpanSeconds::MIN }, { t::SpanSeconds::MAX }>;
let seconds = Range::try_new("seconds", seconds.into())?;
Ok(self.seconds_ranged(seconds))
}
#[inline]
pub fn try_milliseconds<I: Into<i64>>(
self,
milliseconds: I,
) -> Result<Span, Error> {
type Range =
ri64<{ t::SpanMilliseconds::MIN }, { t::SpanMilliseconds::MAX }>;
let milliseconds =
Range::try_new("milliseconds", milliseconds.into())?;
Ok(self.milliseconds_ranged(milliseconds))
}
#[inline]
pub fn try_microseconds<I: Into<i64>>(
self,
microseconds: I,
) -> Result<Span, Error> {
type Range =
ri64<{ t::SpanMicroseconds::MIN }, { t::SpanMicroseconds::MAX }>;
let microseconds =
Range::try_new("microseconds", microseconds.into())?;
Ok(self.microseconds_ranged(microseconds))
}
#[inline]
pub fn try_nanoseconds<I: Into<i64>>(
self,
nanoseconds: I,
) -> Result<Span, Error> {
type Range =
ri64<{ t::SpanNanoseconds::MIN }, { t::SpanNanoseconds::MAX }>;
let nanoseconds = Range::try_new("nanoseconds", nanoseconds.into())?;
Ok(self.nanoseconds_ranged(nanoseconds))
}
}
impl Span {
#[inline]
pub fn get_years(&self) -> i16 {
self.get_years_ranged().get()
}
#[inline]
pub fn get_months(&self) -> i32 {
self.get_months_ranged().get()
}
#[inline]
pub fn get_weeks(&self) -> i32 {
self.get_weeks_ranged().get()
}
#[inline]
pub fn get_days(&self) -> i32 {
self.get_days_ranged().get()
}
#[inline]
pub fn get_hours(&self) -> i32 {
self.get_hours_ranged().get()
}
#[inline]
pub fn get_minutes(&self) -> i64 {
self.get_minutes_ranged().get()
}
#[inline]
pub fn get_seconds(&self) -> i64 {
self.get_seconds_ranged().get()
}
#[inline]
pub fn get_milliseconds(&self) -> i64 {
self.get_milliseconds_ranged().get()
}
#[inline]
pub fn get_microseconds(&self) -> i64 {
self.get_microseconds_ranged().get()
}
#[inline]
pub fn get_nanoseconds(&self) -> i64 {
self.get_nanoseconds_ranged().get()
}
}
impl Span {
#[inline]
pub fn abs(self) -> Span {
if self.is_zero() {
return self;
}
Span { sign: ri8::N::<1>(), ..self }
}
#[inline]
pub fn negate(self) -> Span {
Span { sign: -self.sign, ..self }
}
#[inline]
pub fn signum(self) -> i8 {
self.sign.signum().get()
}
#[inline]
pub fn is_positive(self) -> bool {
self.get_sign_ranged() > 0
}
#[inline]
pub fn is_negative(self) -> bool {
self.get_sign_ranged() < 0
}
#[inline]
pub fn is_zero(self) -> bool {
self.sign == 0
}
#[inline]
pub fn checked_mul(mut self, rhs: i64) -> Result<Span, Error> {
if rhs == 0 {
return Ok(Span::default());
} else if rhs == 1 {
return Ok(self);
}
self.sign *= t::Sign::try_new("span factor", rhs.signum())
.expect("signum fits in ri8");
if self.years != 0 {
let rhs = t::SpanYears::try_new("years multiple", rhs)?;
self.years = self.years.try_checked_mul("years", rhs.abs())?;
}
if self.months != 0 {
let rhs = t::SpanMonths::try_new("months multiple", rhs)?;
self.months = self.months.try_checked_mul("months", rhs.abs())?;
}
if self.weeks != 0 {
let rhs = t::SpanWeeks::try_new("weeks multiple", rhs)?;
self.weeks = self.weeks.try_checked_mul("weeks", rhs.abs())?;
}
if self.days != 0 {
let rhs = t::SpanDays::try_new("days multiple", rhs)?;
self.days = self.days.try_checked_mul("days", rhs.abs())?;
}
if self.hours != 0 {
let rhs = t::SpanHours::try_new("hours multiple", rhs)?;
self.hours = self.hours.try_checked_mul("hours", rhs.abs())?;
}
if self.minutes != 0 {
let rhs = t::SpanMinutes::try_new("minutes multiple", rhs)?;
self.minutes =
self.minutes.try_checked_mul("minutes", rhs.abs())?;
}
if self.seconds != 0 {
let rhs = t::SpanSeconds::try_new("seconds multiple", rhs)?;
self.seconds =
self.seconds.try_checked_mul("seconds", rhs.abs())?;
}
if self.milliseconds != 0 {
let rhs =
t::SpanMilliseconds::try_new("milliseconds multiple", rhs)?;
self.milliseconds = self
.milliseconds
.try_checked_mul("milliseconds", rhs.abs())?;
}
if self.microseconds != 0 {
let rhs =
t::SpanMicroseconds::try_new("microseconds multiple", rhs)?;
self.microseconds = self
.microseconds
.try_checked_mul("microseconds", rhs.abs())?;
}
if self.nanoseconds != 0 {
let rhs =
t::SpanNanoseconds::try_new("nanoseconds multiple", rhs)?;
self.nanoseconds =
self.nanoseconds.try_checked_mul("nanoseconds", rhs.abs())?;
}
Ok(self)
}
#[inline]
pub fn checked_add<'a, A: Into<SpanArithmetic<'a>>>(
&self,
options: A,
) -> Result<Span, Error> {
let options: SpanArithmetic<'_> = options.into();
options.checked_add(*self)
}
#[inline]
fn checked_add_span<'a>(
&self,
relative: Option<SpanRelativeTo<'a>>,
span: &Span,
) -> Result<Span, Error> {
let (span1, span2) = (*self, *span);
let unit = span1.largest_unit().max(span2.largest_unit());
let start = match relative {
Some(r) => {
if !r.is_variable(unit) {
return span1.checked_add_invariant(unit, &span2);
}
r.to_relative()?
}
None => {
if unit.is_definitively_variable() {
return Err(err!(
"using largest unit (which is '{unit}') in given span \
requires that a relative reference time be given, \
but none was provided",
unit = unit.singular(),
));
}
return span1.checked_add_invariant(unit, &span2);
}
};
let mid = start.checked_add(span1)?;
let end = mid.checked_add(span2)?;
start.until(unit, &end)
}
#[inline]
fn checked_add_duration<'a>(
&self,
relative: Option<SpanRelativeTo<'a>>,
duration: SignedDuration,
) -> Result<Span, Error> {
let (span1, dur2) = (*self, duration);
let unit = span1.largest_unit();
let start = match relative {
Some(r) => {
if !r.is_variable(unit) {
return span1.checked_add_invariant_duration(unit, dur2);
}
r.to_relative()?
}
None => {
if unit.is_definitively_variable() {
return Err(err!(
"using largest unit (which is '{unit}') in given span \
requires that a relative reference time be given, \
but none was provided",
unit = unit.singular(),
));
}
return span1.checked_add_invariant_duration(unit, dur2);
}
};
let mid = start.checked_add(span1)?;
let end = mid.checked_add_duration(dur2)?;
start.until(unit, &end)
}
#[inline]
fn checked_add_invariant(
&self,
unit: Unit,
span: &Span,
) -> Result<Span, Error> {
assert!(unit <= Unit::Day);
let nanos1 = self.to_invariant_nanoseconds();
let nanos2 = span.to_invariant_nanoseconds();
let sum = nanos1 + nanos2;
Span::from_invariant_nanoseconds(unit, sum)
}
#[inline]
fn checked_add_invariant_duration(
&self,
unit: Unit,
duration: SignedDuration,
) -> Result<Span, Error> {
assert!(unit <= Unit::Day);
let nanos1 = self.to_invariant_nanoseconds();
let nanos2 = t::NoUnits96::new_unchecked(duration.as_nanos());
let sum = nanos1 + nanos2;
Span::from_invariant_nanoseconds(unit, sum)
}
#[inline]
pub fn checked_sub<'a, A: Into<SpanArithmetic<'a>>>(
&self,
options: A,
) -> Result<Span, Error> {
let mut options: SpanArithmetic<'_> = options.into();
options.duration = options.duration.checked_neg()?;
options.checked_add(*self)
}
#[inline]
pub fn compare<'a, C: Into<SpanCompare<'a>>>(
&self,
options: C,
) -> Result<Ordering, Error> {
let options: SpanCompare<'_> = options.into();
options.compare(*self)
}
#[inline]
pub fn total<'a, T: Into<SpanTotal<'a>>>(
&self,
options: T,
) -> Result<f64, Error> {
let options: SpanTotal<'_> = options.into();
options.total(*self)
}
#[inline]
pub fn round<'a, R: Into<SpanRound<'a>>>(
self,
options: R,
) -> Result<Span, Error> {
let options: SpanRound<'a> = options.into();
options.round(self)
}
#[inline]
pub fn to_jiff_duration<'a, R: Into<SpanRelativeTo<'a>>>(
&self,
relative: R,
) -> Result<SignedDuration, Error> {
let max_unit = self.largest_unit();
let relative: SpanRelativeTo<'a> = relative.into();
if !relative.is_variable(max_unit) {
return Ok(self.to_jiff_duration_invariant());
}
let relspan = relative
.to_relative()
.and_then(|r| r.into_relative_span(Unit::Second, *self))
.with_context(|| {
err!(
"could not compute normalized relative span \
from datetime {relative} and span {self}",
relative = relative.kind,
)
})?;
debug_assert!(relspan.span.largest_unit() <= Unit::Second);
Ok(relspan.span.to_jiff_duration_invariant())
}
#[inline]
fn to_jiff_duration_invariant(&self) -> SignedDuration {
const _FITS_IN_U64: () = {
debug_assert!(
i64::MAX as i128
> ((t::SpanDays::MAX * t::SECONDS_PER_CIVIL_DAY.bound())
+ (t::SpanHours::MAX * t::SECONDS_PER_HOUR.bound())
+ (t::SpanMinutes::MAX
* t::SECONDS_PER_MINUTE.bound())
+ t::SpanSeconds::MAX
+ (t::SpanMilliseconds::MAX
/ t::MILLIS_PER_SECOND.bound())
+ (t::SpanMicroseconds::MAX
/ t::MICROS_PER_SECOND.bound())
+ (t::SpanNanoseconds::MAX
/ t::NANOS_PER_SECOND.bound())),
);
()
};
let nanos = self.to_invariant_nanoseconds();
debug_assert!(
self.largest_unit() <= Unit::Day,
"units must be days or lower"
);
let seconds = nanos / t::NANOS_PER_SECOND;
let seconds = i64::from(seconds);
let subsec_nanos = nanos % t::NANOS_PER_SECOND;
let subsec_nanos = i32::try_from(subsec_nanos).unwrap();
SignedDuration::new(seconds, subsec_nanos)
}
}
impl Span {
#[deprecated(since = "0.1.5", note = "use Span::to_jiff_duration instead")]
#[inline]
pub fn to_duration<'a, R: Into<SpanRelativeTo<'a>>>(
&self,
relative: R,
) -> Result<UnsignedDuration, Error> {
if self.is_negative() {
return Err(err!(
"cannot convert negative span {self:?} \
to unsigned std::time::Duration",
));
}
let max_unit = self.largest_unit();
let relative: SpanRelativeTo<'a> = relative.into();
if !relative.is_variable(max_unit) {
return Ok(self.to_duration_invariant());
}
let relspan = relative
.to_relative()
.and_then(|r| r.into_relative_span(Unit::Second, *self))
.with_context(|| {
err!(
"could not compute normalized relative span \
from datetime {relative} and span {self}",
relative = relative.kind,
)
})?;
debug_assert!(relspan.span.largest_unit() <= Unit::Second);
Ok(relspan.span.to_duration_invariant())
}
#[inline]
fn to_duration_invariant(&self) -> UnsignedDuration {
const _FITS_IN_U64: () = {
debug_assert!(
u64::MAX as u128
> ((t::SpanDays::MAX * t::SECONDS_PER_CIVIL_DAY.bound())
+ (t::SpanHours::MAX * t::SECONDS_PER_HOUR.bound())
+ (t::SpanMinutes::MAX
* t::SECONDS_PER_MINUTE.bound())
+ t::SpanSeconds::MAX
+ (t::SpanMilliseconds::MAX
/ t::MILLIS_PER_SECOND.bound())
+ (t::SpanMicroseconds::MAX
/ t::MICROS_PER_SECOND.bound())
+ (t::SpanNanoseconds::MAX
/ t::NANOS_PER_SECOND.bound()))
as u128,
);
()
};
let nanos = self.to_invariant_nanoseconds();
debug_assert!(nanos >= 0, "span must be positive");
debug_assert!(
self.largest_unit() <= Unit::Day,
"units must be days or lower"
);
let seconds = nanos / t::NANOS_PER_SECOND;
let seconds = i64::from(seconds);
let seconds = u64::try_from(seconds).unwrap();
let subsec_nanos = nanos % t::NANOS_PER_SECOND;
let subsec_nanos = i32::try_from(subsec_nanos).unwrap();
let subsec_nanos = u32::try_from(subsec_nanos).unwrap();
UnsignedDuration::new(seconds, subsec_nanos)
}
}
impl Span {
#[inline]
pub(crate) fn years_ranged(self, years: impl RInto<t::SpanYears>) -> Span {
let years = years.rinto();
let mut span = Span { years: years.abs(), ..self };
span.sign = self.resign(years, &span);
span
}
#[inline]
pub(crate) fn months_ranged(
self,
months: impl RInto<t::SpanMonths>,
) -> Span {
let months = months.rinto();
let mut span = Span { months: months.abs(), ..self };
span.sign = self.resign(months, &span);
span
}
#[inline]
pub(crate) fn weeks_ranged(self, weeks: impl RInto<t::SpanWeeks>) -> Span {
let weeks = weeks.rinto();
let mut span = Span { weeks: weeks.abs(), ..self };
span.sign = self.resign(weeks, &span);
span
}
#[inline]
pub(crate) fn days_ranged(self, days: impl RInto<t::SpanDays>) -> Span {
let days = days.rinto();
let mut span = Span { days: days.abs(), ..self };
span.sign = self.resign(days, &span);
span
}
#[inline]
pub(crate) fn hours_ranged(self, hours: impl RInto<t::SpanHours>) -> Span {
let hours = hours.rinto();
let mut span = Span { hours: hours.abs(), ..self };
span.sign = self.resign(hours, &span);
span
}
#[inline]
pub(crate) fn minutes_ranged(
self,
minutes: impl RInto<t::SpanMinutes>,
) -> Span {
let minutes = minutes.rinto();
let mut span = Span { minutes: minutes.abs(), ..self };
span.sign = self.resign(minutes, &span);
span
}
#[inline]
pub(crate) fn seconds_ranged(
self,
seconds: impl RInto<t::SpanSeconds>,
) -> Span {
let seconds = seconds.rinto();
let mut span = Span { seconds: seconds.abs(), ..self };
span.sign = self.resign(seconds, &span);
span
}
#[inline]
fn milliseconds_ranged(
self,
milliseconds: impl RInto<t::SpanMilliseconds>,
) -> Span {
let milliseconds = milliseconds.rinto();
let mut span = Span { milliseconds: milliseconds.abs(), ..self };
span.sign = self.resign(milliseconds, &span);
span
}
#[inline]
fn microseconds_ranged(
self,
microseconds: impl RInto<t::SpanMicroseconds>,
) -> Span {
let microseconds = microseconds.rinto();
let mut span = Span { microseconds: microseconds.abs(), ..self };
span.sign = self.resign(microseconds, &span);
span
}
#[inline]
pub(crate) fn nanoseconds_ranged(
self,
nanoseconds: impl RInto<t::SpanNanoseconds>,
) -> Span {
let nanoseconds = nanoseconds.rinto();
let mut span = Span { nanoseconds: nanoseconds.abs(), ..self };
span.sign = self.resign(nanoseconds, &span);
span
}
#[inline]
fn try_years_ranged(
self,
years: impl TryRInto<t::SpanYears>,
) -> Result<Span, Error> {
let years = years.try_rinto("years")?;
Ok(self.years_ranged(years))
}
#[inline]
fn try_months_ranged(
self,
months: impl TryRInto<t::SpanMonths>,
) -> Result<Span, Error> {
let months = months.try_rinto("months")?;
Ok(self.months_ranged(months))
}
#[inline]
fn try_weeks_ranged(
self,
weeks: impl TryRInto<t::SpanWeeks>,
) -> Result<Span, Error> {
let weeks = weeks.try_rinto("weeks")?;
Ok(self.weeks_ranged(weeks))
}
#[inline]
fn try_days_ranged(
self,
days: impl TryRInto<t::SpanDays>,
) -> Result<Span, Error> {
let days = days.try_rinto("days")?;
Ok(self.days_ranged(days))
}
#[inline]
pub(crate) fn try_hours_ranged(
self,
hours: impl TryRInto<t::SpanHours>,
) -> Result<Span, Error> {
let hours = hours.try_rinto("hours")?;
Ok(self.hours_ranged(hours))
}
#[inline]
pub(crate) fn try_minutes_ranged(
self,
minutes: impl TryRInto<t::SpanMinutes>,
) -> Result<Span, Error> {
let minutes = minutes.try_rinto("minutes")?;
Ok(self.minutes_ranged(minutes))
}
#[inline]
pub(crate) fn try_seconds_ranged(
self,
seconds: impl TryRInto<t::SpanSeconds>,
) -> Result<Span, Error> {
let seconds = seconds.try_rinto("seconds")?;
Ok(self.seconds_ranged(seconds))
}
#[inline]
pub(crate) fn try_milliseconds_ranged(
self,
milliseconds: impl TryRInto<t::SpanMilliseconds>,
) -> Result<Span, Error> {
let milliseconds = milliseconds.try_rinto("milliseconds")?;
Ok(self.milliseconds_ranged(milliseconds))
}
#[inline]
pub(crate) fn try_microseconds_ranged(
self,
microseconds: impl TryRInto<t::SpanMicroseconds>,
) -> Result<Span, Error> {
let microseconds = microseconds.try_rinto("microseconds")?;
Ok(self.microseconds_ranged(microseconds))
}
#[inline]
pub(crate) fn try_nanoseconds_ranged(
self,
nanoseconds: impl TryRInto<t::SpanNanoseconds>,
) -> Result<Span, Error> {
let nanoseconds = nanoseconds.try_rinto("nanoseconds")?;
Ok(self.nanoseconds_ranged(nanoseconds))
}
#[inline]
pub(crate) fn try_units_ranged(
self,
unit: Unit,
value: impl RInto<NoUnits>,
) -> Result<Span, Error> {
let value = value.rinto();
match unit {
Unit::Year => self.try_years_ranged(value),
Unit::Month => self.try_months_ranged(value),
Unit::Week => self.try_weeks_ranged(value),
Unit::Day => self.try_days_ranged(value),
Unit::Hour => self.try_hours_ranged(value),
Unit::Minute => self.try_minutes_ranged(value),
Unit::Second => self.try_seconds_ranged(value),
Unit::Millisecond => self.try_milliseconds_ranged(value),
Unit::Microsecond => self.try_microseconds_ranged(value),
Unit::Nanosecond => self.try_nanoseconds_ranged(value),
}
}
#[inline]
pub(crate) fn get_years_ranged(&self) -> t::SpanYears {
self.years * self.sign
}
#[inline]
pub(crate) fn get_months_ranged(&self) -> t::SpanMonths {
self.months * self.sign
}
#[inline]
pub(crate) fn get_weeks_ranged(&self) -> t::SpanWeeks {
self.weeks * self.sign
}
#[inline]
pub(crate) fn get_days_ranged(&self) -> t::SpanDays {
self.days * self.sign
}
#[inline]
pub(crate) fn get_hours_ranged(&self) -> t::SpanHours {
self.hours * self.sign
}
#[inline]
pub(crate) fn get_minutes_ranged(&self) -> t::SpanMinutes {
self.minutes * self.sign
}
#[inline]
pub(crate) fn get_seconds_ranged(&self) -> t::SpanSeconds {
self.seconds * self.sign
}
#[inline]
pub(crate) fn get_milliseconds_ranged(&self) -> t::SpanMilliseconds {
self.milliseconds * self.sign
}
#[inline]
pub(crate) fn get_microseconds_ranged(&self) -> t::SpanMicroseconds {
self.microseconds * self.sign
}
#[inline]
pub(crate) fn get_nanoseconds_ranged(&self) -> t::SpanNanoseconds {
self.nanoseconds * self.sign
}
#[inline]
fn get_sign_ranged(&self) -> ri8<-1, 1> {
self.sign
}
#[inline]
fn get_units_ranged(&self, unit: Unit) -> NoUnits {
match unit {
Unit::Year => self.get_years_ranged().rinto(),
Unit::Month => self.get_months_ranged().rinto(),
Unit::Week => self.get_weeks_ranged().rinto(),
Unit::Day => self.get_days_ranged().rinto(),
Unit::Hour => self.get_hours_ranged().rinto(),
Unit::Minute => self.get_minutes_ranged().rinto(),
Unit::Second => self.get_seconds_ranged().rinto(),
Unit::Millisecond => self.get_milliseconds_ranged().rinto(),
Unit::Microsecond => self.get_microseconds_ranged().rinto(),
Unit::Nanosecond => self.get_nanoseconds_ranged().rinto(),
}
}
}
impl Span {
pub(crate) fn from_invariant_nanoseconds(
largest: Unit,
nanos: impl RInto<NoUnits128>,
) -> Result<Span, Error> {
let nanos = nanos.rinto();
let mut span = Span::new();
match largest {
Unit::Year | Unit::Month | Unit::Week | Unit::Day => {
let micros = nanos.div_ceil(t::NANOS_PER_MICRO);
span = span.try_nanoseconds_ranged(
nanos.rem_ceil(t::NANOS_PER_MICRO),
)?;
let millis = micros.div_ceil(t::MICROS_PER_MILLI);
span = span.try_microseconds_ranged(
micros.rem_ceil(t::MICROS_PER_MILLI),
)?;
let secs = millis.div_ceil(t::MILLIS_PER_SECOND);
span = span.try_milliseconds_ranged(
millis.rem_ceil(t::MILLIS_PER_SECOND),
)?;
let mins = secs.div_ceil(t::SECONDS_PER_MINUTE);
span = span.try_seconds_ranged(
secs.rem_ceil(t::SECONDS_PER_MINUTE),
)?;
let hours = mins.div_ceil(t::MINUTES_PER_HOUR);
span = span
.try_minutes_ranged(mins.rem_ceil(t::MINUTES_PER_HOUR))?;
let days = hours.div_ceil(t::HOURS_PER_CIVIL_DAY);
span = span.try_hours_ranged(
hours.rem_ceil(t::HOURS_PER_CIVIL_DAY),
)?;
span = span.try_days_ranged(days)?;
Ok(span)
}
Unit::Hour => {
let micros = nanos.div_ceil(t::NANOS_PER_MICRO);
span = span.try_nanoseconds_ranged(
nanos.rem_ceil(t::NANOS_PER_MICRO),
)?;
let millis = micros.div_ceil(t::MICROS_PER_MILLI);
span = span.try_microseconds_ranged(
micros.rem_ceil(t::MICROS_PER_MILLI),
)?;
let secs = millis.div_ceil(t::MILLIS_PER_SECOND);
span = span.try_milliseconds_ranged(
millis.rem_ceil(t::MILLIS_PER_SECOND),
)?;
let mins = secs.div_ceil(t::SECONDS_PER_MINUTE);
span = span.try_seconds_ranged(
secs.rem_ceil(t::SECONDS_PER_MINUTE),
)?;
let hours = mins.div_ceil(t::MINUTES_PER_HOUR);
span = span
.try_minutes_ranged(mins.rem_ceil(t::MINUTES_PER_HOUR))?;
span = span.try_hours_ranged(hours)?;
Ok(span)
}
Unit::Minute => {
let micros = nanos.div_ceil(t::NANOS_PER_MICRO);
span = span.try_nanoseconds_ranged(
nanos.rem_ceil(t::NANOS_PER_MICRO),
)?;
let millis = micros.div_ceil(t::MICROS_PER_MILLI);
span = span.try_microseconds_ranged(
micros.rem_ceil(t::MICROS_PER_MILLI),
)?;
let secs = millis.div_ceil(t::MILLIS_PER_SECOND);
span = span.try_milliseconds_ranged(
millis.rem_ceil(t::MILLIS_PER_SECOND),
)?;
let mins = secs.div_ceil(t::SECONDS_PER_MINUTE);
span =
span.try_seconds(secs.rem_ceil(t::SECONDS_PER_MINUTE))?;
span = span.try_minutes_ranged(mins)?;
Ok(span)
}
Unit::Second => {
let micros = nanos.div_ceil(t::NANOS_PER_MICRO);
span = span.try_nanoseconds_ranged(
nanos.rem_ceil(t::NANOS_PER_MICRO),
)?;
let millis = micros.div_ceil(t::MICROS_PER_MILLI);
span = span.try_microseconds_ranged(
micros.rem_ceil(t::MICROS_PER_MILLI),
)?;
let secs = millis.div_ceil(t::MILLIS_PER_SECOND);
span = span.try_milliseconds_ranged(
millis.rem_ceil(t::MILLIS_PER_SECOND),
)?;
span = span.try_seconds_ranged(secs)?;
Ok(span)
}
Unit::Millisecond => {
let micros = nanos.div_ceil(t::NANOS_PER_MICRO);
span = span.try_nanoseconds_ranged(
nanos.rem_ceil(t::NANOS_PER_MICRO),
)?;
let millis = micros.div_ceil(t::MICROS_PER_MILLI);
span = span.try_microseconds_ranged(
micros.rem_ceil(t::MICROS_PER_MILLI),
)?;
span = span.try_milliseconds_ranged(millis)?;
Ok(span)
}
Unit::Microsecond => {
let micros = nanos.div_ceil(t::NANOS_PER_MICRO);
span = span.try_nanoseconds_ranged(
nanos.rem_ceil(t::NANOS_PER_MICRO),
)?;
span = span.try_microseconds_ranged(micros)?;
Ok(span)
}
Unit::Nanosecond => {
span = span.try_nanoseconds_ranged(nanos)?;
Ok(span)
}
}
}
#[inline]
pub(crate) fn to_invariant_nanoseconds(&self) -> NoUnits128 {
let mut nanos = NoUnits128::rfrom(self.get_nanoseconds_ranged());
nanos += NoUnits128::rfrom(self.get_microseconds_ranged())
* t::NANOS_PER_MICRO;
nanos += NoUnits128::rfrom(self.get_milliseconds_ranged())
* t::NANOS_PER_MILLI;
nanos +=
NoUnits128::rfrom(self.get_seconds_ranged()) * t::NANOS_PER_SECOND;
nanos +=
NoUnits128::rfrom(self.get_minutes_ranged()) * t::NANOS_PER_MINUTE;
nanos +=
NoUnits128::rfrom(self.get_hours_ranged()) * t::NANOS_PER_HOUR;
nanos +=
NoUnits128::rfrom(self.get_days_ranged()) * t::NANOS_PER_CIVIL_DAY;
nanos
}
#[inline]
pub(crate) fn to_invariant_seconds(&self) -> Option<NoUnits> {
if self.has_fractional_seconds() {
return None;
}
let mut seconds = NoUnits::rfrom(self.get_seconds_ranged());
seconds +=
NoUnits::rfrom(self.get_minutes_ranged()) * t::SECONDS_PER_MINUTE;
seconds +=
NoUnits::rfrom(self.get_hours_ranged()) * t::SECONDS_PER_HOUR;
seconds +=
NoUnits::rfrom(self.get_days_ranged()) * t::SECONDS_PER_CIVIL_DAY;
Some(seconds)
}
#[allow(dead_code)] #[inline]
pub(crate) fn rebalance(self, unit: Unit) -> Result<Span, Error> {
Span::from_invariant_nanoseconds(unit, self.to_invariant_nanoseconds())
}
#[inline]
pub(crate) fn has_fractional_seconds(&self) -> bool {
self.milliseconds != 0
|| self.microseconds != 0
|| self.nanoseconds != 0
}
#[inline(always)]
pub(crate) fn only_calendar(self) -> Span {
let mut span = self;
span.hours = t::SpanHours::N::<0>();
span.minutes = t::SpanMinutes::N::<0>();
span.seconds = t::SpanSeconds::N::<0>();
span.milliseconds = t::SpanMilliseconds::N::<0>();
span.microseconds = t::SpanMicroseconds::N::<0>();
span.nanoseconds = t::SpanNanoseconds::N::<0>();
if span.sign != 0
&& span.years == 0
&& span.months == 0
&& span.weeks == 0
&& span.days == 0
{
span.sign = t::Sign::N::<0>();
}
span
}
#[inline(always)]
pub(crate) fn only_time(self) -> Span {
let mut span = self;
span.years = t::SpanYears::N::<0>();
span.months = t::SpanMonths::N::<0>();
span.weeks = t::SpanWeeks::N::<0>();
span.days = t::SpanDays::N::<0>();
if span.sign != 0
&& span.hours == 0
&& span.minutes == 0
&& span.seconds == 0
&& span.milliseconds == 0
&& span.microseconds == 0
&& span.nanoseconds == 0
{
span.sign = t::Sign::N::<0>();
}
span
}
#[inline(always)]
pub(crate) fn only_lower(self, unit: Unit) -> Span {
let mut span = self;
if unit <= Unit::Microsecond {
span = span.microseconds_ranged(C(0));
}
if unit <= Unit::Millisecond {
span = span.milliseconds_ranged(C(0));
}
if unit <= Unit::Second {
span = span.seconds_ranged(C(0));
}
if unit <= Unit::Minute {
span = span.minutes_ranged(C(0));
}
if unit <= Unit::Hour {
span = span.hours_ranged(C(0));
}
if unit <= Unit::Day {
span = span.days_ranged(C(0));
}
if unit <= Unit::Week {
span = span.weeks_ranged(C(0));
}
if unit <= Unit::Month {
span = span.months_ranged(C(0));
}
if unit <= Unit::Year {
span = span.years_ranged(C(0));
}
span
}
#[inline(always)]
pub(crate) fn without_lower(self, unit: Unit) -> Span {
let mut span = self;
if unit > Unit::Nanosecond {
span = span.nanoseconds_ranged(C(0));
}
if unit > Unit::Microsecond {
span = span.microseconds_ranged(C(0));
}
if unit > Unit::Millisecond {
span = span.milliseconds_ranged(C(0));
}
if unit > Unit::Second {
span = span.seconds_ranged(C(0));
}
if unit > Unit::Minute {
span = span.minutes_ranged(C(0));
}
if unit > Unit::Hour {
span = span.hours_ranged(C(0));
}
if unit > Unit::Day {
span = span.days_ranged(C(0));
}
if unit > Unit::Week {
span = span.weeks_ranged(C(0));
}
if unit > Unit::Month {
span = span.months_ranged(C(0));
}
span
}
#[inline(always)]
pub(crate) fn smallest_non_time_non_zero_unit_error(
&self,
) -> Option<Error> {
let non_time_unit = self.largest_calendar_unit()?;
Some(err!(
"operation can only be performed with units of hours \
or smaller, but found non-zero {unit} units",
unit = non_time_unit.singular(),
))
}
#[inline]
pub(crate) fn largest_calendar_unit(&self) -> Option<Unit> {
if self.days != 0 {
Some(Unit::Day)
} else if self.weeks != 0 {
Some(Unit::Week)
} else if self.months != 0 {
Some(Unit::Month)
} else if self.years != 0 {
Some(Unit::Year)
} else {
None
}
}
#[inline]
pub(crate) fn largest_unit(&self) -> Unit {
if self.years != 0 {
Unit::Year
} else if self.months != 0 {
Unit::Month
} else if self.weeks != 0 {
Unit::Week
} else if self.days != 0 {
Unit::Day
} else if self.hours != 0 {
Unit::Hour
} else if self.minutes != 0 {
Unit::Minute
} else if self.seconds != 0 {
Unit::Second
} else if self.milliseconds != 0 {
Unit::Millisecond
} else if self.microseconds != 0 {
Unit::Microsecond
} else {
Unit::Nanosecond
}
}
#[allow(dead_code)]
fn debug(&self) -> alloc::string::String {
use core::fmt::Write;
let mut buf = alloc::string::String::new();
write!(buf, "Span {{ sign: {:?}", self.sign).unwrap();
if self.years != 0 {
write!(buf, ", years: {:?}", self.years).unwrap();
}
if self.months != 0 {
write!(buf, ", months: {:?}", self.months).unwrap();
}
if self.weeks != 0 {
write!(buf, ", weeks: {:?}", self.weeks).unwrap();
}
if self.days != 0 {
write!(buf, ", days: {:?}", self.days).unwrap();
}
if self.hours != 0 {
write!(buf, ", hours: {:?}", self.hours).unwrap();
}
if self.minutes != 0 {
write!(buf, ", minutes: {:?}", self.minutes).unwrap();
}
if self.seconds != 0 {
write!(buf, ", seconds: {:?}", self.seconds).unwrap();
}
if self.milliseconds != 0 {
write!(buf, ", milliseconds: {:?}", self.milliseconds).unwrap();
}
if self.microseconds != 0 {
write!(buf, ", microseconds: {:?}", self.microseconds).unwrap();
}
if self.nanoseconds != 0 {
write!(buf, ", nanoseconds: {:?}", self.nanoseconds).unwrap();
}
write!(buf, " }}").unwrap();
buf
}
#[inline]
fn resign(&self, units: impl RInto<NoUnits>, new: &Span) -> Sign {
let units = units.rinto();
if units < 0 {
return Sign::N::<-1>();
}
let mut new_is_zero = new.sign == 0 && units == 0;
if units == 0 {
new_is_zero = new.years == 0
&& new.months == 0
&& new.weeks == 0
&& new.days == 0
&& new.hours == 0
&& new.minutes == 0
&& new.seconds == 0
&& new.milliseconds == 0
&& new.microseconds == 0
&& new.nanoseconds == 0;
}
match (self.is_zero(), new_is_zero) {
(_, true) => Sign::N::<0>(),
(true, false) => units.signum().rinto(),
(false, false) => new.sign,
}
}
}
impl Default for Span {
#[inline]
fn default() -> Span {
Span {
sign: ri8::N::<0>(),
years: C(0).rinto(),
months: C(0).rinto(),
weeks: C(0).rinto(),
days: C(0).rinto(),
hours: C(0).rinto(),
minutes: C(0).rinto(),
seconds: C(0).rinto(),
milliseconds: C(0).rinto(),
microseconds: C(0).rinto(),
nanoseconds: C(0).rinto(),
}
}
}
impl core::fmt::Debug for Span {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
core::fmt::Display::fmt(self, f)
}
}
impl core::fmt::Display for Span {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use crate::fmt::StdFmtWrite;
DEFAULT_SPAN_PRINTER
.print_span(self, StdFmtWrite(f))
.map_err(|_| core::fmt::Error)
}
}
impl core::str::FromStr for Span {
type Err = Error;
#[inline]
fn from_str(string: &str) -> Result<Span, Error> {
DEFAULT_SPAN_PARSER.parse_span(string)
}
}
impl core::ops::Neg for Span {
type Output = Span;
#[inline]
fn neg(self) -> Span {
self.negate()
}
}
impl core::ops::Mul<i64> for Span {
type Output = Span;
#[inline]
fn mul(self, rhs: i64) -> Span {
self.checked_mul(rhs)
.expect("multiplying `Span` by a scalar overflowed")
}
}
impl core::ops::Mul<Span> for i64 {
type Output = Span;
#[inline]
fn mul(self, rhs: Span) -> Span {
rhs.checked_mul(self)
.expect("multiplying `Span` by a scalar overflowed")
}
}
impl TryFrom<Span> for UnsignedDuration {
type Error = Error;
#[inline]
fn try_from(sp: Span) -> Result<UnsignedDuration, Error> {
if sp.is_negative() {
return Err(err!(
"cannot convert negative span {sp:?} \
to unsigned std::time::Duration",
));
}
if sp.largest_unit() > Unit::Day {
return Err(err!(
"cannot convert span with non-zero {unit}, \
must use Span::to_duration with a relative date \
instead",
unit = sp.largest_unit().plural(),
));
}
Ok(sp.to_duration_invariant())
}
}
impl TryFrom<UnsignedDuration> for Span {
type Error = Error;
#[inline]
fn try_from(d: UnsignedDuration) -> Result<Span, Error> {
let seconds = i64::try_from(d.as_secs()).map_err(|_| {
err!("seconds from {d:?} overflows a 64-bit signed integer")
})?;
let nanoseconds = i64::from(d.subsec_nanos());
let milliseconds = nanoseconds / t::NANOS_PER_MILLI.value();
let microseconds = (nanoseconds % t::NANOS_PER_MILLI.value())
/ t::NANOS_PER_MICRO.value();
let nanoseconds = nanoseconds % t::NANOS_PER_MICRO.value();
let span = Span::new().try_seconds(seconds).with_context(|| {
err!("duration {d:?} overflows limits of a Jiff `Span`")
})?;
Ok(span
.milliseconds(milliseconds)
.microseconds(microseconds)
.nanoseconds(nanoseconds))
}
}
impl TryFrom<Span> for SignedDuration {
type Error = Error;
#[inline]
fn try_from(sp: Span) -> Result<SignedDuration, Error> {
if sp.largest_unit() > Unit::Day {
return Err(err!(
"cannot convert span with non-zero {unit}, \
must use Span::to_duration with a relative date \
instead",
unit = sp.largest_unit().plural(),
));
}
Ok(sp.to_jiff_duration_invariant())
}
}
impl TryFrom<SignedDuration> for Span {
type Error = Error;
#[inline]
fn try_from(d: SignedDuration) -> Result<Span, Error> {
let seconds = d.as_secs();
let nanoseconds = i64::from(d.subsec_nanos());
let milliseconds = nanoseconds / t::NANOS_PER_MILLI.value();
let microseconds = (nanoseconds % t::NANOS_PER_MILLI.value())
/ t::NANOS_PER_MICRO.value();
let nanoseconds = nanoseconds % t::NANOS_PER_MICRO.value();
let span = Span::new().try_seconds(seconds).with_context(|| {
err!("signed duration {d:?} overflows limits of a Jiff `Span`")
})?;
Ok(span
.milliseconds(milliseconds)
.microseconds(microseconds)
.nanoseconds(nanoseconds))
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Span {
#[inline]
fn serialize<S: serde::Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Span {
#[inline]
fn deserialize<D: serde::Deserializer<'de>>(
deserializer: D,
) -> Result<Span, D::Error> {
use serde::de;
struct SpanVisitor;
impl<'de> de::Visitor<'de> for SpanVisitor {
type Value = Span;
fn expecting(
&self,
f: &mut core::fmt::Formatter,
) -> core::fmt::Result {
f.write_str("a span duration string")
}
#[inline]
fn visit_bytes<E: de::Error>(
self,
value: &[u8],
) -> Result<Span, E> {
DEFAULT_SPAN_PARSER
.parse_span(value)
.map_err(de::Error::custom)
}
#[inline]
fn visit_str<E: de::Error>(self, value: &str) -> Result<Span, E> {
self.visit_bytes(value.as_bytes())
}
}
deserializer.deserialize_bytes(SpanVisitor)
}
}
#[cfg(test)]
impl quickcheck::Arbitrary for Span {
fn arbitrary(g: &mut quickcheck::Gen) -> Span {
type Nanos = ri64<-631_107_417_600_000_000, 631_107_417_600_000_000>;
let nanos = Nanos::arbitrary(g).get();
let relative =
SpanRelativeTo::from(DateTime::constant(0, 1, 1, 0, 0, 0, 0));
let round =
SpanRound::new().largest(Unit::arbitrary(g)).relative(relative);
Span::new().nanoseconds(nanos).round(round).unwrap()
}
fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = Self>> {
alloc::boxed::Box::new(
(
(
self.get_years_ranged(),
self.get_months_ranged(),
self.get_weeks_ranged(),
self.get_days_ranged(),
),
(
self.get_hours_ranged(),
self.get_minutes_ranged(),
self.get_seconds_ranged(),
self.get_milliseconds_ranged(),
),
(
self.get_microseconds_ranged(),
self.get_nanoseconds_ranged(),
),
)
.shrink()
.filter_map(
|(
(years, months, weeks, days),
(hours, minutes, seconds, milliseconds),
(microseconds, nanoseconds),
)| {
let span = Span::new()
.years_ranged(years)
.months_ranged(months)
.weeks_ranged(weeks)
.days_ranged(days)
.hours_ranged(hours)
.minutes_ranged(minutes)
.seconds_ranged(seconds)
.milliseconds_ranged(milliseconds)
.microseconds_ranged(microseconds)
.nanoseconds_ranged(nanoseconds);
Some(span)
},
),
)
}
}
pub trait ToSpan: Sized {
fn years(self) -> Span;
fn months(self) -> Span;
fn weeks(self) -> Span;
fn days(self) -> Span;
fn hours(self) -> Span;
fn minutes(self) -> Span;
fn seconds(self) -> Span;
fn milliseconds(self) -> Span;
fn microseconds(self) -> Span;
fn nanoseconds(self) -> Span;
#[inline]
fn year(self) -> Span {
self.years()
}
#[inline]
fn month(self) -> Span {
self.months()
}
#[inline]
fn week(self) -> Span {
self.weeks()
}
#[inline]
fn day(self) -> Span {
self.days()
}
#[inline]
fn hour(self) -> Span {
self.hours()
}
#[inline]
fn minute(self) -> Span {
self.minutes()
}
#[inline]
fn second(self) -> Span {
self.seconds()
}
#[inline]
fn millisecond(self) -> Span {
self.milliseconds()
}
#[inline]
fn microsecond(self) -> Span {
self.microseconds()
}
#[inline]
fn nanosecond(self) -> Span {
self.nanoseconds()
}
}
macro_rules! impl_to_span {
($ty:ty) => {
impl ToSpan for $ty {
#[inline]
fn years(self) -> Span {
Span::new().years(self)
}
#[inline]
fn months(self) -> Span {
Span::new().months(self)
}
#[inline]
fn weeks(self) -> Span {
Span::new().weeks(self)
}
#[inline]
fn days(self) -> Span {
Span::new().days(self)
}
#[inline]
fn hours(self) -> Span {
Span::new().hours(self)
}
#[inline]
fn minutes(self) -> Span {
Span::new().minutes(self)
}
#[inline]
fn seconds(self) -> Span {
Span::new().seconds(self)
}
#[inline]
fn milliseconds(self) -> Span {
Span::new().milliseconds(self)
}
#[inline]
fn microseconds(self) -> Span {
Span::new().microseconds(self)
}
#[inline]
fn nanoseconds(self) -> Span {
Span::new().nanoseconds(self)
}
}
};
}
impl_to_span!(i8);
impl_to_span!(i16);
impl_to_span!(i32);
impl_to_span!(i64);
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
pub enum Unit {
Year = 9,
Month = 8,
Week = 7,
Day = 6,
Hour = 5,
Minute = 4,
Second = 3,
Millisecond = 2,
Microsecond = 1,
Nanosecond = 0,
}
impl Unit {
pub(crate) fn next(&self) -> Option<Unit> {
match *self {
Unit::Year => None,
Unit::Month => Some(Unit::Year),
Unit::Week => Some(Unit::Month),
Unit::Day => Some(Unit::Week),
Unit::Hour => Some(Unit::Day),
Unit::Minute => Some(Unit::Hour),
Unit::Second => Some(Unit::Minute),
Unit::Millisecond => Some(Unit::Second),
Unit::Microsecond => Some(Unit::Millisecond),
Unit::Nanosecond => Some(Unit::Microsecond),
}
}
pub(crate) fn nanoseconds(self) -> NoUnits128 {
match self {
Unit::Nanosecond => Constant(1),
Unit::Microsecond => t::NANOS_PER_MICRO,
Unit::Millisecond => t::NANOS_PER_MILLI,
Unit::Second => t::NANOS_PER_SECOND,
Unit::Minute => t::NANOS_PER_MINUTE,
Unit::Hour => t::NANOS_PER_HOUR,
Unit::Day => t::NANOS_PER_CIVIL_DAY,
unit => unreachable!("{unit:?} has no definitive time interval"),
}
.rinto()
}
pub(crate) fn is_definitively_variable(self) -> bool {
matches!(self, Unit::Year | Unit::Month | Unit::Week)
}
pub(crate) fn singular(&self) -> &'static str {
match *self {
Unit::Year => "year",
Unit::Month => "month",
Unit::Week => "week",
Unit::Day => "day",
Unit::Hour => "hour",
Unit::Minute => "minute",
Unit::Second => "second",
Unit::Millisecond => "millisecond",
Unit::Microsecond => "microsecond",
Unit::Nanosecond => "nanosecond",
}
}
pub(crate) fn plural(&self) -> &'static str {
match *self {
Unit::Year => "years",
Unit::Month => "months",
Unit::Week => "weeks",
Unit::Day => "days",
Unit::Hour => "hours",
Unit::Minute => "minutes",
Unit::Second => "seconds",
Unit::Millisecond => "milliseconds",
Unit::Microsecond => "microseconds",
Unit::Nanosecond => "nanoseconds",
}
}
}
#[cfg(test)]
impl Unit {
fn from_usize(n: usize) -> Option<Unit> {
match n {
0 => Some(Unit::Nanosecond),
1 => Some(Unit::Microsecond),
2 => Some(Unit::Millisecond),
3 => Some(Unit::Second),
4 => Some(Unit::Minute),
5 => Some(Unit::Hour),
6 => Some(Unit::Day),
7 => Some(Unit::Week),
8 => Some(Unit::Month),
9 => Some(Unit::Year),
_ => None,
}
}
}
#[cfg(test)]
impl quickcheck::Arbitrary for Unit {
fn arbitrary(g: &mut quickcheck::Gen) -> Unit {
Unit::from_usize(usize::arbitrary(g) % 10).unwrap()
}
fn shrink(&self) -> alloc::boxed::Box<dyn Iterator<Item = Self>> {
alloc::boxed::Box::new(
(*self as usize)
.shrink()
.map(|n| Unit::from_usize(n % 10).unwrap()),
)
}
}
#[derive(Clone, Copy, Debug)]
pub struct SpanArithmetic<'a> {
duration: Duration,
relative: Option<SpanRelativeTo<'a>>,
}
impl<'a> SpanArithmetic<'a> {
#[inline]
fn relative<R: Into<SpanRelativeTo<'a>>>(
self,
relative: R,
) -> SpanArithmetic<'a> {
SpanArithmetic { relative: Some(relative.into()), ..self }
}
#[inline]
fn checked_add(self, span1: Span) -> Result<Span, Error> {
match self.duration.to_signed()? {
SDuration::Span(span2) => {
span1.checked_add_span(self.relative, &span2)
}
SDuration::Absolute(dur2) => {
span1.checked_add_duration(self.relative, dur2)
}
}
}
}
impl From<Span> for SpanArithmetic<'static> {
fn from(span: Span) -> SpanArithmetic<'static> {
let duration = Duration::from(span);
SpanArithmetic { duration, relative: None }
}
}
impl<'a> From<&'a Span> for SpanArithmetic<'static> {
fn from(span: &'a Span) -> SpanArithmetic<'static> {
let duration = Duration::from(*span);
SpanArithmetic { duration, relative: None }
}
}
impl From<(Span, Date)> for SpanArithmetic<'static> {
#[inline]
fn from((span, date): (Span, Date)) -> SpanArithmetic<'static> {
SpanArithmetic::from(span).relative(date)
}
}
impl From<(Span, DateTime)> for SpanArithmetic<'static> {
#[inline]
fn from((span, datetime): (Span, DateTime)) -> SpanArithmetic<'static> {
SpanArithmetic::from(span).relative(datetime)
}
}
impl<'a> From<(Span, &'a Zoned)> for SpanArithmetic<'a> {
#[inline]
fn from((span, zoned): (Span, &'a Zoned)) -> SpanArithmetic<'a> {
SpanArithmetic::from(span).relative(zoned)
}
}
impl<'a> From<(&'a Span, Date)> for SpanArithmetic<'static> {
#[inline]
fn from((span, date): (&'a Span, Date)) -> SpanArithmetic<'static> {
SpanArithmetic::from(span).relative(date)
}
}
impl<'a> From<(&'a Span, DateTime)> for SpanArithmetic<'static> {
#[inline]
fn from(
(span, datetime): (&'a Span, DateTime),
) -> SpanArithmetic<'static> {
SpanArithmetic::from(span).relative(datetime)
}
}
impl<'a, 'b> From<(&'a Span, &'b Zoned)> for SpanArithmetic<'b> {
#[inline]
fn from((span, zoned): (&'a Span, &'b Zoned)) -> SpanArithmetic<'b> {
SpanArithmetic::from(span).relative(zoned)
}
}
impl From<SignedDuration> for SpanArithmetic<'static> {
fn from(duration: SignedDuration) -> SpanArithmetic<'static> {
let duration = Duration::from(duration);
SpanArithmetic { duration, relative: None }
}
}
impl From<(SignedDuration, Date)> for SpanArithmetic<'static> {
#[inline]
fn from(
(duration, date): (SignedDuration, Date),
) -> SpanArithmetic<'static> {
SpanArithmetic::from(duration).relative(date)
}
}
impl From<(SignedDuration, DateTime)> for SpanArithmetic<'static> {
#[inline]
fn from(
(duration, datetime): (SignedDuration, DateTime),
) -> SpanArithmetic<'static> {
SpanArithmetic::from(duration).relative(datetime)
}
}
impl<'a> From<(SignedDuration, &'a Zoned)> for SpanArithmetic<'a> {
#[inline]
fn from(
(duration, zoned): (SignedDuration, &'a Zoned),
) -> SpanArithmetic<'a> {
SpanArithmetic::from(duration).relative(zoned)
}
}
impl From<UnsignedDuration> for SpanArithmetic<'static> {
fn from(duration: UnsignedDuration) -> SpanArithmetic<'static> {
let duration = Duration::from(duration);
SpanArithmetic { duration, relative: None }
}
}
impl From<(UnsignedDuration, Date)> for SpanArithmetic<'static> {
#[inline]
fn from(
(duration, date): (UnsignedDuration, Date),
) -> SpanArithmetic<'static> {
SpanArithmetic::from(duration).relative(date)
}
}
impl From<(UnsignedDuration, DateTime)> for SpanArithmetic<'static> {
#[inline]
fn from(
(duration, datetime): (UnsignedDuration, DateTime),
) -> SpanArithmetic<'static> {
SpanArithmetic::from(duration).relative(datetime)
}
}
impl<'a> From<(UnsignedDuration, &'a Zoned)> for SpanArithmetic<'a> {
#[inline]
fn from(
(duration, zoned): (UnsignedDuration, &'a Zoned),
) -> SpanArithmetic<'a> {
SpanArithmetic::from(duration).relative(zoned)
}
}
#[derive(Clone, Copy, Debug)]
pub struct SpanCompare<'a> {
span: Span,
relative: Option<SpanRelativeTo<'a>>,
}
impl<'a> SpanCompare<'a> {
#[inline]
fn new(span: Span) -> SpanCompare<'static> {
SpanCompare { span, relative: None }
}
#[inline]
fn relative<R: Into<SpanRelativeTo<'a>>>(
self,
relative: R,
) -> SpanCompare<'a> {
SpanCompare { relative: Some(relative.into()), ..self }
}
fn compare(self, span: Span) -> Result<Ordering, Error> {
let (span1, span2) = (span, self.span);
let unit = span1.largest_unit().max(span2.largest_unit());
let start = match self.relative {
Some(r) => {
if !r.is_variable(unit) {
let nanos1 = span1.to_invariant_nanoseconds();
let nanos2 = span2.to_invariant_nanoseconds();
return Ok(nanos1.cmp(&nanos2));
}
r.to_relative()?
}
None => {
if unit.is_definitively_variable() {
return Err(err!(
"using largest unit (which is '{unit}') in given span \
requires that a relative reference time be given, \
but none was provided",
unit = unit.singular(),
));
}
let nanos1 = span1.to_invariant_nanoseconds();
let nanos2 = span2.to_invariant_nanoseconds();
return Ok(nanos1.cmp(&nanos2));
}
};
let end1 = start.checked_add(span1)?.to_nanosecond();
let end2 = start.checked_add(span2)?.to_nanosecond();
Ok(end1.cmp(&end2))
}
}
impl From<Span> for SpanCompare<'static> {
fn from(span: Span) -> SpanCompare<'static> {
SpanCompare::new(span)
}
}
impl<'a> From<&'a Span> for SpanCompare<'static> {
fn from(span: &'a Span) -> SpanCompare<'static> {
SpanCompare::new(*span)
}
}
impl From<(Span, Date)> for SpanCompare<'static> {
#[inline]
fn from((span, date): (Span, Date)) -> SpanCompare<'static> {
SpanCompare::from(span).relative(date)
}
}
impl From<(Span, DateTime)> for SpanCompare<'static> {
#[inline]
fn from((span, datetime): (Span, DateTime)) -> SpanCompare<'static> {
SpanCompare::from(span).relative(datetime)
}
}
impl<'a> From<(Span, &'a Zoned)> for SpanCompare<'a> {
#[inline]
fn from((span, zoned): (Span, &'a Zoned)) -> SpanCompare<'a> {
SpanCompare::from(span).relative(zoned)
}
}
impl<'a> From<(&'a Span, Date)> for SpanCompare<'static> {
#[inline]
fn from((span, date): (&'a Span, Date)) -> SpanCompare<'static> {
SpanCompare::from(span).relative(date)
}
}
impl<'a> From<(&'a Span, DateTime)> for SpanCompare<'static> {
#[inline]
fn from((span, datetime): (&'a Span, DateTime)) -> SpanCompare<'static> {
SpanCompare::from(span).relative(datetime)
}
}
impl<'a, 'b> From<(&'a Span, &'b Zoned)> for SpanCompare<'b> {
#[inline]
fn from((span, zoned): (&'a Span, &'b Zoned)) -> SpanCompare<'b> {
SpanCompare::from(span).relative(zoned)
}
}
#[derive(Clone, Copy, Debug)]
pub struct SpanTotal<'a> {
unit: Unit,
relative: Option<SpanRelativeTo<'a>>,
}
impl<'a> SpanTotal<'a> {
#[inline]
fn new(unit: Unit) -> SpanTotal<'static> {
SpanTotal { unit, relative: None }
}
#[inline]
fn relative<R: Into<SpanRelativeTo<'a>>>(
self,
relative: R,
) -> SpanTotal<'a> {
SpanTotal { relative: Some(relative.into()), ..self }
}
fn total(self, span: Span) -> Result<f64, Error> {
let max_unit = self.unit.max(span.largest_unit());
let relative = match self.relative {
Some(r) => {
if !r.is_variable(max_unit) {
return Ok(self.total_invariant(span));
}
r.to_relative()?
}
None => {
if max_unit.is_definitively_variable() {
return Err(err!(
"using unit '{unit}' requires that a relative \
reference time be given, but none was provided",
unit = max_unit.singular(),
));
}
return Ok(self.total_invariant(span));
}
};
let relspan = relative.into_relative_span(self.unit, span)?;
if !relspan.kind.is_variable(self.unit) {
return Ok(self.total_invariant(relspan.span));
}
assert!(self.unit >= Unit::Day);
let sign = relspan.span.get_sign_ranged();
let (relative_start, relative_end) = match relspan.kind {
RelativeSpanKind::Civil { start, end } => {
let start = Relative::Civil(start);
let end = Relative::Civil(end);
(start, end)
}
RelativeSpanKind::Zoned { start, end } => {
let start = Relative::Zoned(start);
let end = Relative::Zoned(end);
(start, end)
}
};
let (relative0, relative1) = clamp_relative_span(
&relative_start,
relspan.span.without_lower(self.unit),
self.unit,
sign.rinto(),
)?;
let denom = (relative1 - relative0).get() as f64;
let numer = (relative_end.to_nanosecond() - relative0).get() as f64;
let unit_val = relspan.span.get_units_ranged(self.unit).get() as f64;
Ok(unit_val + (numer / denom) * (sign.get() as f64))
}
#[inline]
fn total_invariant(&self, span: Span) -> f64 {
assert!(self.unit <= Unit::Day);
let nanos = span.to_invariant_nanoseconds();
(nanos.get() as f64) / (self.unit.nanoseconds().get() as f64)
}
}
impl From<Unit> for SpanTotal<'static> {
#[inline]
fn from(unit: Unit) -> SpanTotal<'static> {
SpanTotal::new(unit)
}
}
impl From<(Unit, Date)> for SpanTotal<'static> {
#[inline]
fn from((unit, date): (Unit, Date)) -> SpanTotal<'static> {
SpanTotal::from(unit).relative(date)
}
}
impl From<(Unit, DateTime)> for SpanTotal<'static> {
#[inline]
fn from((unit, datetime): (Unit, DateTime)) -> SpanTotal<'static> {
SpanTotal::from(unit).relative(datetime)
}
}
impl<'a> From<(Unit, &'a Zoned)> for SpanTotal<'a> {
#[inline]
fn from((unit, zoned): (Unit, &'a Zoned)) -> SpanTotal<'a> {
SpanTotal::from(unit).relative(zoned)
}
}
#[derive(Clone, Copy, Debug)]
pub struct SpanRound<'a> {
largest: Option<Unit>,
smallest: Unit,
mode: RoundMode,
increment: i64,
relative: Option<SpanRelativeTo<'a>>,
}
impl<'a> SpanRound<'a> {
#[inline]
pub fn new() -> SpanRound<'static> {
SpanRound {
largest: None,
smallest: Unit::Nanosecond,
mode: RoundMode::HalfExpand,
increment: 1,
relative: None,
}
}
#[inline]
pub fn smallest(self, unit: Unit) -> SpanRound<'a> {
SpanRound { smallest: unit, ..self }
}
#[inline]
pub fn largest(self, unit: Unit) -> SpanRound<'a> {
SpanRound { largest: Some(unit), ..self }
}
#[inline]
pub fn mode(self, mode: RoundMode) -> SpanRound<'a> {
SpanRound { mode, ..self }
}
#[inline]
pub fn increment(self, increment: i64) -> SpanRound<'a> {
SpanRound { increment, ..self }
}
#[inline]
pub fn relative<R: Into<SpanRelativeTo<'a>>>(
self,
relative: R,
) -> SpanRound<'a> {
SpanRound { relative: Some(relative.into()), ..self }
}
#[inline]
pub(crate) fn get_smallest(&self) -> Unit {
self.smallest
}
#[inline]
pub(crate) fn get_largest(&self) -> Option<Unit> {
self.largest
}
#[inline]
pub(crate) fn rounding_may_change_span_ignore_largest(&self) -> bool {
self.smallest > Unit::Nanosecond || self.increment > 1
}
fn round(&self, span: Span) -> Result<Span, Error> {
let existing_largest = span.largest_unit();
let mode = self.mode;
let smallest = self.smallest;
let largest =
self.largest.unwrap_or_else(|| smallest.max(existing_largest));
let max = existing_largest.max(largest);
let increment = increment::for_span(smallest, self.increment)?;
if largest < smallest {
return Err(err!(
"largest unit ('{largest}') cannot be smaller than \
smallest unit ('{smallest}')",
largest = largest.singular(),
smallest = smallest.singular(),
));
}
let relative = match self.relative {
Some(ref r) => {
if !r.is_variable(max) {
return Ok(round_span_invariant(
span, smallest, largest, increment, mode,
)?);
}
r.to_relative()?
}
None => {
if smallest.is_definitively_variable() {
return Err(err!(
"using unit '{unit}' in round option 'smallest' \
requires that a relative reference time be given, \
but none was provided",
unit = smallest.singular(),
));
}
if let Some(largest) = self.largest {
if largest.is_definitively_variable() {
return Err(err!(
"using unit '{unit}' in round option 'largest' \
requires that a relative reference time be \
given, but none was provided",
unit = largest.singular(),
));
}
}
if existing_largest.is_definitively_variable() {
return Err(err!(
"using largest unit (which is '{unit}') in given span \
requires that a relative reference time be given, \
but none was provided",
unit = existing_largest.singular(),
));
}
assert!(max <= Unit::Day);
return Ok(round_span_invariant(
span, smallest, largest, increment, mode,
)?);
}
};
relative.round(span, smallest, largest, increment, mode)
}
}
impl Default for SpanRound<'static> {
fn default() -> SpanRound<'static> {
SpanRound::new()
}
}
impl From<Unit> for SpanRound<'static> {
fn from(unit: Unit) -> SpanRound<'static> {
SpanRound::default().smallest(unit)
}
}
impl From<(Unit, i64)> for SpanRound<'static> {
fn from((unit, increment): (Unit, i64)) -> SpanRound<'static> {
SpanRound::default().smallest(unit).increment(increment)
}
}
#[derive(Clone, Copy, Debug)]
pub struct SpanRelativeTo<'a> {
kind: SpanRelativeToKind<'a>,
}
impl<'a> SpanRelativeTo<'a> {
fn is_variable(&self, unit: Unit) -> bool {
if unit.is_definitively_variable() {
return true;
}
unit == Unit::Day
&& matches!(self.kind, SpanRelativeToKind::Zoned { .. })
}
fn to_relative(&self) -> Result<Relative<'a>, Error> {
match self.kind {
SpanRelativeToKind::Civil(dt) => {
Ok(Relative::Civil(RelativeCivil::new(dt)?))
}
SpanRelativeToKind::Zoned(zdt) => {
Ok(Relative::Zoned(RelativeZoned {
zoned: Cow::Borrowed(zdt),
}))
}
}
}
}
#[derive(Clone, Copy, Debug)]
enum SpanRelativeToKind<'a> {
Civil(DateTime),
Zoned(&'a Zoned),
}
impl<'a> From<&'a Zoned> for SpanRelativeTo<'a> {
fn from(zdt: &'a Zoned) -> SpanRelativeTo<'a> {
SpanRelativeTo { kind: SpanRelativeToKind::Zoned(zdt) }
}
}
impl From<DateTime> for SpanRelativeTo<'static> {
fn from(dt: DateTime) -> SpanRelativeTo<'static> {
SpanRelativeTo { kind: SpanRelativeToKind::Civil(dt) }
}
}
impl From<Date> for SpanRelativeTo<'static> {
fn from(date: Date) -> SpanRelativeTo<'static> {
let dt = DateTime::from_parts(date, Time::midnight());
SpanRelativeTo { kind: SpanRelativeToKind::Civil(dt) }
}
}
impl<'a> core::fmt::Display for SpanRelativeToKind<'a> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
match *self {
SpanRelativeToKind::Civil(dt) => core::fmt::Display::fmt(&dt, f),
SpanRelativeToKind::Zoned(zdt) => core::fmt::Display::fmt(zdt, f),
}
}
}
#[derive(Clone, Debug)]
enum Relative<'a> {
Civil(RelativeCivil),
Zoned(RelativeZoned<'a>),
}
impl<'a> Relative<'a> {
fn checked_add(&self, span: Span) -> Result<Relative, Error> {
match *self {
Relative::Civil(dt) => Ok(Relative::Civil(dt.checked_add(span)?)),
Relative::Zoned(ref zdt) => {
Ok(Relative::Zoned(zdt.checked_add(span)?))
}
}
}
fn checked_add_duration(
&self,
duration: SignedDuration,
) -> Result<Relative, Error> {
match *self {
Relative::Civil(dt) => {
Ok(Relative::Civil(dt.checked_add_duration(duration)?))
}
Relative::Zoned(ref zdt) => {
Ok(Relative::Zoned(zdt.checked_add_duration(duration)?))
}
}
}
fn until(&self, largest: Unit, other: &Relative) -> Result<Span, Error> {
match (self, other) {
(&Relative::Civil(ref dt1), &Relative::Civil(ref dt2)) => {
dt1.until(largest, dt2)
}
(&Relative::Zoned(ref zdt1), &Relative::Zoned(ref zdt2)) => {
zdt1.until(largest, zdt2)
}
_ => unreachable!(),
}
}
fn to_nanosecond(&self) -> NoUnits128 {
match *self {
Relative::Civil(dt) => dt.timestamp.as_nanosecond_ranged().rinto(),
Relative::Zoned(ref zdt) => {
zdt.zoned.timestamp().as_nanosecond_ranged().rinto()
}
}
}
fn into_relative_span(
self,
largest: Unit,
span: Span,
) -> Result<RelativeSpan<'a>, Error> {
let kind = match self {
Relative::Civil(start) => {
let end = start.checked_add(span)?;
RelativeSpanKind::Civil { start, end }
}
Relative::Zoned(start) => {
let end = start.checked_add(span)?;
RelativeSpanKind::Zoned { start, end }
}
};
let relspan = kind.into_relative_span(largest)?;
if span.get_sign_ranged() != 0
&& relspan.span.get_sign_ranged() != 0
&& span.get_sign_ranged() != relspan.span.get_sign_ranged()
{
unreachable!(
"balanced span should have same sign as original span"
)
}
Ok(relspan)
}
fn round(
self,
span: Span,
smallest: Unit,
largest: Unit,
increment: NoUnits128,
mode: RoundMode,
) -> Result<Span, Error> {
let relspan = self.into_relative_span(largest, span)?;
if relspan.span.get_sign_ranged() == 0 {
return Ok(relspan.span);
}
let nudge = match relspan.kind {
RelativeSpanKind::Civil { start, end } => {
if smallest > Unit::Day {
Nudge::relative_calendar(
relspan.span,
&Relative::Civil(start),
&Relative::Civil(end),
smallest,
increment,
mode,
)?
} else {
let relative_end = end.timestamp.as_nanosecond_ranged();
Nudge::relative_invariant(
relspan.span,
relative_end.rinto(),
smallest,
largest,
increment,
mode,
)?
}
}
RelativeSpanKind::Zoned { ref start, ref end } => {
if smallest >= Unit::Day {
Nudge::relative_calendar(
relspan.span,
&Relative::Zoned(start.clone()),
&Relative::Zoned(end.clone()),
smallest,
increment,
mode,
)?
} else if largest >= Unit::Day {
Nudge::relative_zoned_time(
relspan.span,
start,
smallest,
increment,
mode,
)?
} else {
let relative_end =
end.zoned.timestamp().as_nanosecond_ranged();
Nudge::relative_invariant(
relspan.span,
relative_end.rinto(),
smallest,
largest,
increment,
mode,
)?
}
}
};
nudge.bubble(&relspan, smallest, largest)
}
}
#[derive(Clone, Debug)]
struct RelativeSpan<'a> {
span: Span,
kind: RelativeSpanKind<'a>,
}
#[derive(Clone, Debug)]
enum RelativeSpanKind<'a> {
Civil { start: RelativeCivil, end: RelativeCivil },
Zoned { start: RelativeZoned<'a>, end: RelativeZoned<'a> },
}
impl<'a> RelativeSpanKind<'a> {
fn is_variable(&self, unit: Unit) -> bool {
if unit.is_definitively_variable() {
return true;
}
unit == Unit::Day && matches!(*self, RelativeSpanKind::Zoned { .. })
}
fn into_relative_span(
self,
largest: Unit,
) -> Result<RelativeSpan<'a>, Error> {
let span = match self {
RelativeSpanKind::Civil { ref start, ref end } => start
.datetime
.until((largest, end.datetime))
.with_context(|| {
err!(
"failed to get span between {start} and {end} \
with largest unit as {unit}",
start = start.datetime,
end = end.datetime,
unit = largest.plural(),
)
})?,
RelativeSpanKind::Zoned { ref start, ref end } => start
.zoned
.until((largest, &*end.zoned))
.with_context(|| {
err!(
"failed to get span between {start} and {end} \
with largest unit as {unit}",
start = start.zoned,
end = end.zoned,
unit = largest.plural(),
)
})?,
};
Ok(RelativeSpan { span, kind: self })
}
}
#[derive(Clone, Copy, Debug)]
struct RelativeCivil {
datetime: DateTime,
timestamp: Timestamp,
}
impl RelativeCivil {
fn new(datetime: DateTime) -> Result<RelativeCivil, Error> {
let timestamp = datetime
.to_zoned(TimeZone::UTC)
.with_context(|| {
err!("failed to convert {datetime} to timestamp")
})?
.timestamp();
Ok(RelativeCivil { datetime, timestamp })
}
fn checked_add(&self, span: Span) -> Result<RelativeCivil, Error> {
let datetime = self.datetime.checked_add(span).with_context(|| {
err!("failed to add {span} to {dt}", dt = self.datetime)
})?;
let timestamp = datetime
.to_zoned(TimeZone::UTC)
.with_context(|| {
err!("failed to convert {datetime} to timestamp")
})?
.timestamp();
Ok(RelativeCivil { datetime, timestamp })
}
fn checked_add_duration(
&self,
duration: SignedDuration,
) -> Result<RelativeCivil, Error> {
let datetime =
self.datetime.checked_add(duration).with_context(|| {
err!("failed to add {duration:?} to {dt}", dt = self.datetime)
})?;
let timestamp = datetime
.to_zoned(TimeZone::UTC)
.with_context(|| {
err!("failed to convert {datetime} to timestamp")
})?
.timestamp();
Ok(RelativeCivil { datetime, timestamp })
}
fn until(
&self,
largest: Unit,
other: &RelativeCivil,
) -> Result<Span, Error> {
self.datetime.until((largest, other.datetime)).with_context(|| {
err!(
"failed to get span between {dt1} and {dt2} \
with largest unit as {unit}",
unit = largest.plural(),
dt1 = self.datetime,
dt2 = other.datetime,
)
})
}
}
#[derive(Clone, Debug)]
struct RelativeZoned<'a> {
zoned: Cow<'a, Zoned>,
}
impl<'a> RelativeZoned<'a> {
fn checked_add(
&self,
span: Span,
) -> Result<RelativeZoned<'static>, Error> {
let zoned = self.zoned.checked_add(span).with_context(|| {
err!("failed to add {span} to {zoned}", zoned = self.zoned)
})?;
Ok(RelativeZoned { zoned: Cow::Owned(zoned) })
}
fn checked_add_duration(
&self,
duration: SignedDuration,
) -> Result<RelativeZoned<'static>, Error> {
let zoned = self.zoned.checked_add(duration).with_context(|| {
err!("failed to add {duration:?} to {zoned}", zoned = self.zoned)
})?;
Ok(RelativeZoned { zoned: Cow::Owned(zoned) })
}
fn until(
&self,
largest: Unit,
other: &RelativeZoned<'a>,
) -> Result<Span, Error> {
self.zoned.until((largest, &*other.zoned)).with_context(|| {
err!(
"failed to get span between {zdt1} and {zdt2} \
with largest unit as {unit}",
unit = largest.plural(),
zdt1 = self.zoned,
zdt2 = other.zoned,
)
})
}
}
#[derive(Debug)]
struct Nudge {
span: Span,
rounded_relative_end: NoUnits128,
grew_big_unit: bool,
}
impl Nudge {
fn relative_invariant(
balanced: Span,
relative_end: NoUnits128,
smallest: Unit,
largest: Unit,
increment: NoUnits128,
mode: RoundMode,
) -> Result<Nudge, Error> {
assert!(smallest <= Unit::Day);
let sign = balanced.get_sign_ranged();
let balanced_nanos = balanced.to_invariant_nanoseconds();
let rounded_nanos = mode.round_by_unit_in_nanoseconds(
balanced_nanos,
smallest,
increment,
);
let span = Span::from_invariant_nanoseconds(largest, rounded_nanos)
.with_context(|| {
err!(
"failed to convert rounded nanoseconds {rounded_nanos} \
to span for largest unit as {unit}",
unit = largest.plural(),
)
})?
.years_ranged(balanced.get_years_ranged())
.months_ranged(balanced.get_months_ranged())
.weeks_ranged(balanced.get_weeks_ranged());
let diff_nanos = rounded_nanos - balanced_nanos;
let diff_days = rounded_nanos.div_ceil(t::NANOS_PER_CIVIL_DAY)
- balanced_nanos.div_ceil(t::NANOS_PER_CIVIL_DAY);
let grew_big_unit = diff_days.signum() == sign;
let rounded_relative_end = relative_end + diff_nanos;
Ok(Nudge { span, rounded_relative_end, grew_big_unit })
}
fn relative_calendar(
balanced: Span,
relative_start: &Relative<'_>,
relative_end: &Relative<'_>,
smallest: Unit,
increment: NoUnits128,
mode: RoundMode,
) -> Result<Nudge, Error> {
#[cfg(not(feature = "std"))]
use crate::util::libm::Float;
assert!(smallest >= Unit::Day);
let sign = balanced.get_sign_ranged();
let truncated = increment
* balanced.get_units_ranged(smallest).div_ceil(increment);
let span = balanced
.without_lower(smallest)
.try_units_ranged(smallest, truncated)
.with_context(|| {
err!(
"failed to set {unit} to {truncated} on span {balanced}",
unit = smallest.singular()
)
})?;
let (relative0, relative1) = clamp_relative_span(
relative_start,
span,
smallest,
NoUnits::try_rfrom("increment", increment)?
.try_checked_mul("signed increment", sign)?,
)?;
let denom = (relative1 - relative0).get() as f64;
let numer = (relative_end.to_nanosecond() - relative0).get() as f64;
let exact = (truncated.get() as f64)
+ (numer / denom) * (sign.get() as f64) * (increment.get() as f64);
let rounded = mode.round_float(exact, increment);
let grew_big_unit =
((rounded.get() as f64) - exact).signum() == (sign.get() as f64);
let span =
span.try_units_ranged(smallest, rounded).with_context(|| {
err!(
"failed to set {unit} to {truncated} on span {span}",
unit = smallest.singular()
)
})?;
let rounded_relative_end =
if grew_big_unit { relative1 } else { relative0 };
Ok(Nudge { span, rounded_relative_end, grew_big_unit })
}
fn relative_zoned_time(
balanced: Span,
relative_start: &RelativeZoned<'_>,
smallest: Unit,
increment: NoUnits128,
mode: RoundMode,
) -> Result<Nudge, Error> {
let sign = balanced.get_sign_ranged();
let time_nanos =
balanced.only_lower(Unit::Day).to_invariant_nanoseconds();
let mut rounded_time_nanos =
mode.round_by_unit_in_nanoseconds(time_nanos, smallest, increment);
let (relative0, relative1) = clamp_relative_span(
&Relative::Zoned(relative_start.clone()),
balanced.without_lower(Unit::Day),
Unit::Day,
sign.rinto(),
)?;
let day_nanos = relative1 - relative0;
let beyond_day_nanos = rounded_time_nanos - day_nanos;
let mut day_delta = NoUnits::N::<0>();
let rounded_relative_end =
if beyond_day_nanos == 0 || beyond_day_nanos.signum() == sign {
day_delta += C(1);
rounded_time_nanos = mode.round_by_unit_in_nanoseconds(
beyond_day_nanos,
smallest,
increment,
);
relative1 + rounded_time_nanos
} else {
relative0 + rounded_time_nanos
};
let span =
Span::from_invariant_nanoseconds(Unit::Hour, rounded_time_nanos)
.with_context(|| {
err!(
"failed to convert rounded nanoseconds \
{rounded_time_nanos} to span for largest unit as {unit}",
unit = Unit::Hour.plural(),
)
})?
.years_ranged(balanced.get_years_ranged())
.months_ranged(balanced.get_months_ranged())
.weeks_ranged(balanced.get_weeks_ranged())
.days_ranged(balanced.get_days_ranged() + day_delta);
let grew_big_unit = day_delta != 0;
Ok(Nudge { span, rounded_relative_end, grew_big_unit })
}
fn bubble(
&self,
relative: &RelativeSpan,
smallest: Unit,
largest: Unit,
) -> Result<Span, Error> {
if !self.grew_big_unit || smallest == Unit::Week {
return Ok(self.span);
}
let smallest = smallest.max(Unit::Day);
let mut balanced = self.span;
let sign = balanced.get_sign_ranged();
let mut unit = smallest;
while let Some(u) = unit.next() {
unit = u;
if unit > largest {
break;
}
if unit == Unit::Week && largest != Unit::Week {
continue;
}
let span_start = balanced.without_lower(unit);
let new_units = span_start
.get_units_ranged(unit)
.try_checked_add("bubble-units", sign)
.with_context(|| {
err!(
"failed to add sign {sign} to {unit} value {value}",
unit = unit.plural(),
value = span_start.get_units_ranged(unit),
)
})?;
let span_end = span_start
.try_units_ranged(unit, new_units)
.with_context(|| {
err!(
"failed to set {unit} to value \
{new_units} on span {span_start}",
unit = unit.plural(),
)
})?;
let threshold = match relative.kind {
RelativeSpanKind::Civil { ref start, .. } => {
start.checked_add(span_end)?.timestamp
}
RelativeSpanKind::Zoned { ref start, .. } => {
start.checked_add(span_end)?.zoned.timestamp()
}
};
let beyond =
self.rounded_relative_end - threshold.as_nanosecond_ranged();
if beyond == 0 || beyond.signum() == sign {
balanced = span_end;
} else {
break;
}
}
Ok(balanced)
}
}
fn round_span_invariant(
span: Span,
smallest: Unit,
largest: Unit,
increment: NoUnits128,
mode: RoundMode,
) -> Result<Span, Error> {
assert!(smallest <= Unit::Day);
assert!(largest <= Unit::Day);
let nanos = span.to_invariant_nanoseconds();
let rounded =
mode.round_by_unit_in_nanoseconds(nanos, smallest, increment);
Span::from_invariant_nanoseconds(largest, rounded).with_context(|| {
err!(
"failed to convert rounded nanoseconds {rounded} \
to span for largest unit as {unit}",
unit = largest.plural(),
)
})
}
fn clamp_relative_span(
relative: &Relative<'_>,
span: Span,
unit: Unit,
amount: NoUnits,
) -> Result<(NoUnits128, NoUnits128), Error> {
let amount = span
.get_units_ranged(unit)
.try_checked_add("clamp-units", amount)
.with_context(|| {
err!(
"failed to add {amount} to {unit} \
value {value} on span {span}",
unit = unit.plural(),
value = span.get_units_ranged(unit),
)
})?;
let span_amount =
span.try_units_ranged(unit, amount).with_context(|| {
err!(
"failed to set {unit} unit to {amount} on span {span}",
unit = unit.plural(),
)
})?;
let relative0 = relative.checked_add(span)?.to_nanosecond();
let relative1 = relative.checked_add(span_amount)?.to_nanosecond();
Ok((relative0, relative1))
}
#[cfg(test)]
mod tests {
use crate::{civil::date, RoundMode};
use super::*;
#[test]
fn test_total() {
if crate::tz::db().is_definitively_empty() {
return;
}
let span = 130.hours().minutes(20);
let total = span.total(Unit::Second).unwrap();
assert_eq!(total, 469200.0);
let span = 123456789.seconds();
let total = span.total(Unit::Day).unwrap();
assert_eq!(total, 1428.8980208333332);
let span = 2756.hours();
let dt = date(2020, 1, 1).at(0, 0, 0, 0);
let zdt = dt.intz("Europe/Rome").unwrap();
let total = span.total((Unit::Month, &zdt)).unwrap();
assert_eq!(total, 3.7958333333333334);
let total = span.total((Unit::Month, dt)).unwrap();
assert_eq!(total, 3.7944444444444443);
}
#[test]
fn test_compare() {
if crate::tz::db().is_definitively_empty() {
return;
}
let span1 = 79.hours().minutes(10);
let span2 = 3.days().hours(7).seconds(630);
let span3 = 3.days().hours(6).minutes(50);
let mut array = [span1, span2, span3];
array.sort_by(|sp1, sp2| sp1.compare(sp2).unwrap());
assert_eq!(array, [span3, span1, span2]);
let dt = date(2020, 11, 1).at(0, 0, 0, 0);
let zdt = dt.intz("America/Los_Angeles").unwrap();
array.sort_by(|sp1, sp2| sp1.compare((sp2, &zdt)).unwrap());
assert_eq!(array, [span1, span3, span2]);
}
#[test]
fn test_checked_add() {
let span1 = 1.hour();
let span2 = 30.minutes();
let sum = span1.checked_add(span2).unwrap();
assert_eq!(sum, 1.hour().minutes(30));
let span1 = 1.hour().minutes(30);
let span2 = 2.hours().minutes(45);
let sum = span1.checked_add(span2).unwrap();
assert_eq!(sum, 4.hours().minutes(15));
let span = 50
.years()
.months(50)
.days(50)
.hours(50)
.minutes(50)
.seconds(50)
.milliseconds(500)
.microseconds(500)
.nanoseconds(500);
let relative = date(1900, 1, 1).at(0, 0, 0, 0);
let sum = span.checked_add((span, relative)).unwrap();
let expected = 108
.years()
.months(7)
.days(12)
.hours(5)
.minutes(41)
.seconds(41)
.milliseconds(1)
.microseconds(1)
.nanoseconds(0);
assert_eq!(sum, expected);
let span = 1.month().days(15);
let relative = date(2000, 2, 1).at(0, 0, 0, 0);
let sum = span.checked_add((span, relative)).unwrap();
assert_eq!(sum, 3.months());
let relative = date(2000, 3, 1).at(0, 0, 0, 0);
let sum = span.checked_add((span, relative)).unwrap();
assert_eq!(sum, 2.months().days(30));
}
#[test]
fn test_round_day_time() {
let span = 29.seconds();
let rounded = span.round(Unit::Minute).unwrap();
assert_eq!(rounded, 0.minute());
let span = 30.seconds();
let rounded = span.round(Unit::Minute).unwrap();
assert_eq!(rounded, 1.minute());
let span = 8.seconds();
let rounded = span
.round(
SpanRound::new()
.smallest(Unit::Nanosecond)
.largest(Unit::Microsecond),
)
.unwrap();
assert_eq!(rounded, 8_000_000.microseconds());
let span = 130.minutes();
let rounded = span.round(SpanRound::new().largest(Unit::Day)).unwrap();
assert_eq!(rounded, 2.hours().minutes(10));
let span = 10.minutes().seconds(52);
let rounded = span.round(Unit::Minute).unwrap();
assert_eq!(rounded, 11.minutes());
let span = 10.minutes().seconds(52);
let rounded = span
.round(
SpanRound::new().smallest(Unit::Minute).mode(RoundMode::Trunc),
)
.unwrap();
assert_eq!(rounded, 10.minutes());
let span = 2.hours().minutes(34).seconds(18);
let rounded =
span.round(SpanRound::new().largest(Unit::Second)).unwrap();
assert_eq!(rounded, 9258.seconds());
let span = 6.minutes();
let rounded = span
.round(
SpanRound::new()
.smallest(Unit::Minute)
.increment(5)
.mode(RoundMode::Ceil),
)
.unwrap();
assert_eq!(rounded, 10.minutes());
}
#[test]
fn test_round_relative_zoned_calendar() {
if crate::tz::db().is_definitively_empty() {
return;
}
let span = 2756.hours();
let relative =
date(2020, 1, 1).at(0, 0, 0, 0).intz("America/New_York").unwrap();
let options = SpanRound::new()
.largest(Unit::Year)
.smallest(Unit::Day)
.relative(&relative);
let rounded = span.round(options).unwrap();
assert_eq!(rounded, 3.months().days(24));
let span = 24.hours().nanoseconds(5);
let relative = date(2000, 10, 29)
.at(0, 0, 0, 0)
.intz("America/Vancouver")
.unwrap();
let options = SpanRound::new()
.largest(Unit::Day)
.smallest(Unit::Minute)
.relative(&relative)
.mode(RoundMode::Expand)
.increment(30);
let rounded = span.round(options).unwrap();
assert_eq!(rounded, 24.hours().minutes(30));
let span = -1.month().hours(24);
let relative: crate::Zoned =
date(2024, 4, 11).at(2, 0, 0, 0).intz("America/New_York").unwrap();
let options =
SpanRound::new().smallest(Unit::Millisecond).relative(&relative);
let rounded = span.round(options).unwrap();
assert_eq!(rounded, -1.month().days(1).hours(1));
let dt = relative.checked_add(span).unwrap();
let diff = relative.until((Unit::Month, &dt)).unwrap();
assert_eq!(diff, -1.month().days(1).hours(1));
let span = -1.month().hours(24);
let relative =
date(2024, 6, 11).at(2, 0, 0, 0).intz("America/New_York").unwrap();
let options =
SpanRound::new().smallest(Unit::Millisecond).relative(&relative);
let rounded = span.round(options).unwrap();
assert_eq!(rounded, -1.month().days(1));
}
#[test]
fn test_round_relative_zoned_time() {
if crate::tz::db().is_definitively_empty() {
return;
}
let span = 2756.hours();
let relative =
date(2020, 1, 1).at(0, 0, 0, 0).intz("America/New_York").unwrap();
let options = SpanRound::new().largest(Unit::Year).relative(&relative);
let rounded = span.round(options).unwrap();
assert_eq!(rounded, 3.months().days(23).hours(21));
let span = 2756.hours();
let relative =
date(2020, 9, 1).at(0, 0, 0, 0).intz("America/New_York").unwrap();
let options = SpanRound::new().largest(Unit::Year).relative(&relative);
let rounded = span.round(options).unwrap();
assert_eq!(rounded, 3.months().days(23).hours(19));
let span = 3.hours();
let relative =
date(2020, 3, 8).at(0, 0, 0, 0).intz("America/New_York").unwrap();
let options = SpanRound::new().largest(Unit::Year).relative(&relative);
let rounded = span.round(options).unwrap();
assert_eq!(rounded, 3.hours());
}
#[test]
fn test_round_relative_day_time() {
let span = 2756.hours();
let options =
SpanRound::new().largest(Unit::Year).relative(date(2020, 1, 1));
let rounded = span.round(options).unwrap();
assert_eq!(rounded, 3.months().days(23).hours(20));
let span = 2756.hours();
let options =
SpanRound::new().largest(Unit::Year).relative(date(2020, 9, 1));
let rounded = span.round(options).unwrap();
assert_eq!(rounded, 3.months().days(23).hours(20));
let span = 190.days();
let options =
SpanRound::new().largest(Unit::Year).relative(date(2020, 1, 1));
let rounded = span.round(options).unwrap();
assert_eq!(rounded, 6.months().days(8));
let span = 30
.days()
.hours(23)
.minutes(59)
.seconds(59)
.milliseconds(999)
.microseconds(999)
.nanoseconds(999);
let options = SpanRound::new()
.smallest(Unit::Microsecond)
.largest(Unit::Year)
.relative(date(2024, 5, 1));
let rounded = span.round(options).unwrap();
assert_eq!(rounded, 1.month());
let span = 364
.days()
.hours(23)
.minutes(59)
.seconds(59)
.milliseconds(999)
.microseconds(999)
.nanoseconds(999);
let options = SpanRound::new()
.smallest(Unit::Microsecond)
.largest(Unit::Year)
.relative(date(2023, 1, 1));
let rounded = span.round(options).unwrap();
assert_eq!(rounded, 1.year());
let span = 365
.days()
.hours(23)
.minutes(59)
.seconds(59)
.milliseconds(999)
.microseconds(999)
.nanoseconds(999);
let options = SpanRound::new()
.smallest(Unit::Microsecond)
.largest(Unit::Year)
.relative(date(2023, 1, 1));
let rounded = span.round(options).unwrap();
assert_eq!(rounded, 1.year().days(1));
let span = 365
.days()
.hours(23)
.minutes(59)
.seconds(59)
.milliseconds(999)
.microseconds(999)
.nanoseconds(999);
let options = SpanRound::new()
.smallest(Unit::Microsecond)
.largest(Unit::Year)
.relative(date(2024, 1, 1));
let rounded = span.round(options).unwrap();
assert_eq!(rounded, 1.year());
let span = 3.hours();
let options =
SpanRound::new().largest(Unit::Year).relative(date(2020, 3, 8));
let rounded = span.round(options).unwrap();
assert_eq!(rounded, 3.hours());
}
#[test]
fn span_sign() {
assert_eq!(Span::new().get_sign_ranged(), 0);
assert_eq!(Span::new().days(1).get_sign_ranged(), 1);
assert_eq!(Span::new().days(-1).get_sign_ranged(), -1);
assert_eq!(Span::new().days(1).days(0).get_sign_ranged(), 0);
assert_eq!(Span::new().days(-1).days(0).get_sign_ranged(), 0);
assert_eq!(Span::new().years(1).days(1).days(0).get_sign_ranged(), 1);
assert_eq!(
Span::new().years(-1).days(-1).days(0).get_sign_ranged(),
-1
);
}
#[test]
fn span_size() {
#[cfg(target_pointer_width = "64")]
{
#[cfg(debug_assertions)]
{
assert_eq!(core::mem::align_of::<Span>(), 8);
assert_eq!(core::mem::size_of::<Span>(), 184);
}
#[cfg(not(debug_assertions))]
{
assert_eq!(core::mem::align_of::<Span>(), 8);
assert_eq!(core::mem::size_of::<Span>(), 64);
}
}
}
quickcheck::quickcheck! {
fn prop_roundtrip_span_nanoseconds(span: Span) -> quickcheck::TestResult {
let largest = span.largest_unit();
if largest > Unit::Day {
return quickcheck::TestResult::discard();
}
let nanos = span.to_invariant_nanoseconds();
let got = Span::from_invariant_nanoseconds(largest, nanos).unwrap();
quickcheck::TestResult::from_bool(nanos == got.to_invariant_nanoseconds())
}
}
}