use core::{cmp::Ordering, time::Duration as UnsignedDuration};
use crate::{
civil::{Date, DateTime, Time},
duration::{Duration, SDuration},
error::{span::Error as E, unit::UnitConfigError, Error, ErrorContext},
fmt::{friendly, temporal},
tz::TimeZone,
util::{b, borrow::DumbCow, round::Increment},
RoundMode, SignedDuration, Timestamp, Zoned,
};
#[cfg(test)]
macro_rules! span_eq {
($span1:expr, $span2:expr $(,)?) => {{
assert_eq!($span1.fieldwise(), $span2.fieldwise());
}};
($span1:expr, $span2:expr, $($tt:tt)*) => {{
assert_eq!($span1.fieldwise(), $span2.fieldwise(), $($tt)*);
}};
}
#[cfg(test)]
pub(crate) use span_eq;
#[derive(Clone, Copy, Default)]
pub struct Span {
sign: b::Sign,
units: UnitSet,
years: i16,
months: i32,
weeks: i32,
days: i32,
hours: i32,
minutes: i64,
seconds: i64,
milliseconds: i64,
microseconds: i64,
nanoseconds: i64,
}
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 = b::SpanYears::check(years.into())?;
let mut span = self.years_unchecked(years.abs());
span.sign = self.resign(years, &span);
Ok(span)
}
#[inline]
pub fn try_months<I: Into<i64>>(self, months: I) -> Result<Span, Error> {
let months = b::SpanMonths::check(months.into())?;
let mut span = self.months_unchecked(months.abs());
span.sign = self.resign(months, &span);
Ok(span)
}
#[inline]
pub fn try_weeks<I: Into<i64>>(self, weeks: I) -> Result<Span, Error> {
let weeks = b::SpanWeeks::check(weeks.into())?;
let mut span = self.weeks_unchecked(weeks.abs());
span.sign = self.resign(weeks, &span);
Ok(span)
}
#[inline]
pub fn try_days<I: Into<i64>>(self, days: I) -> Result<Span, Error> {
let days = b::SpanDays::check(days.into())?;
let mut span = self.days_unchecked(days.abs());
span.sign = self.resign(days, &span);
Ok(span)
}
#[inline]
pub fn try_hours<I: Into<i64>>(self, hours: I) -> Result<Span, Error> {
let hours = b::SpanHours::check(hours.into())?;
let mut span = self.hours_unchecked(hours.abs());
span.sign = self.resign(hours, &span);
Ok(span)
}
#[inline]
pub fn try_minutes<I: Into<i64>>(self, minutes: I) -> Result<Span, Error> {
let minutes = b::SpanMinutes::check(minutes.into())?;
let mut span = self.minutes_unchecked(minutes.abs());
span.sign = self.resign(minutes, &span);
Ok(span)
}
#[inline]
pub fn try_seconds<I: Into<i64>>(self, seconds: I) -> Result<Span, Error> {
let seconds = b::SpanSeconds::check(seconds.into())?;
let mut span = self.seconds_unchecked(seconds.abs());
span.sign = self.resign(seconds, &span);
Ok(span)
}
#[inline]
pub fn try_milliseconds<I: Into<i64>>(
self,
milliseconds: I,
) -> Result<Span, Error> {
let milliseconds = b::SpanMilliseconds::check(milliseconds.into())?;
let mut span = self.milliseconds_unchecked(milliseconds.abs());
span.sign = self.resign(milliseconds, &span);
Ok(span)
}
#[inline]
pub fn try_microseconds<I: Into<i64>>(
self,
microseconds: I,
) -> Result<Span, Error> {
let microseconds = b::SpanMicroseconds::check(microseconds.into())?;
let mut span = self.microseconds_unchecked(microseconds.abs());
span.sign = self.resign(microseconds, &span);
Ok(span)
}
#[inline]
pub fn try_nanoseconds<I: Into<i64>>(
self,
nanoseconds: I,
) -> Result<Span, Error> {
let nanoseconds = b::SpanNanoseconds::check(nanoseconds.into())?;
let mut span = self.nanoseconds_unchecked(nanoseconds.abs());
span.sign = self.resign(nanoseconds, &span);
Ok(span)
}
}
impl Span {
#[inline]
pub fn get_years(&self) -> i16 {
self.sign * self.years
}
#[inline]
pub fn get_months(&self) -> i32 {
self.sign * self.months
}
#[inline]
pub fn get_weeks(&self) -> i32 {
self.sign * self.weeks
}
#[inline]
pub fn get_days(&self) -> i32 {
self.sign * self.days
}
#[inline]
pub fn get_hours(&self) -> i32 {
self.sign * self.hours
}
#[inline]
pub fn get_minutes(&self) -> i64 {
self.sign * self.minutes
}
#[inline]
pub fn get_seconds(&self) -> i64 {
self.sign * self.seconds
}
#[inline]
pub fn get_milliseconds(&self) -> i64 {
self.sign * self.milliseconds
}
#[inline]
pub fn get_microseconds(&self) -> i64 {
self.sign * self.microseconds
}
#[inline]
pub fn get_nanoseconds(&self) -> i64 {
self.sign * self.nanoseconds
}
}
impl Span {
#[inline]
pub fn abs(self) -> Span {
if self.is_zero() {
return self;
}
Span { sign: b::Sign::Positive, ..self }
}
#[inline]
pub fn negate(self) -> Span {
Span { sign: -self.sign, ..self }
}
#[inline]
pub fn signum(self) -> i8 {
self.sign.signum()
}
#[inline]
pub fn is_positive(self) -> bool {
self.get_sign().is_positive()
}
#[inline]
pub fn is_negative(self) -> bool {
self.get_sign().is_negative()
}
#[inline]
pub fn is_zero(self) -> bool {
self.sign.is_zero()
}
#[inline]
pub fn fieldwise(self) -> SpanFieldwise {
SpanFieldwise(self)
}
#[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 = self.sign * b::Sign::from(rhs);
let rhs = rhs.checked_abs().ok_or_else(b::SpanMultiple::error)?;
if self.years != 0 {
let rhs = b::SpanYears::check(rhs)?;
self.years = b::SpanYears::checked_mul(self.years, rhs)?;
}
if self.months != 0 {
let rhs = b::SpanMonths::check(rhs)?;
self.months = b::SpanMonths::checked_mul(self.months, rhs)?;
}
if self.weeks != 0 {
let rhs = b::SpanWeeks::check(rhs)?;
self.weeks = b::SpanWeeks::checked_mul(self.weeks, rhs)?;
}
if self.days != 0 {
let rhs = b::SpanDays::check(rhs)?;
self.days = b::SpanDays::checked_mul(self.days, rhs)?;
}
if self.hours != 0 {
let rhs = b::SpanHours::check(rhs)?;
self.hours = b::SpanHours::checked_mul(self.hours, rhs)?;
}
if self.minutes != 0 {
self.minutes = b::SpanMinutes::checked_mul(self.minutes, rhs)?;
}
if self.seconds != 0 {
self.seconds = b::SpanSeconds::checked_mul(self.seconds, rhs)?;
}
if self.milliseconds != 0 {
self.milliseconds =
b::SpanMilliseconds::checked_mul(self.milliseconds, rhs)?;
}
if self.microseconds != 0 {
self.microseconds =
b::SpanMicroseconds::checked_mul(self.microseconds, rhs)?;
}
if self.nanoseconds != 0 {
self.nanoseconds =
b::SpanNanoseconds::checked_mul(self.nanoseconds, rhs)?;
}
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) => match r.to_relative(unit)? {
None => return span1.checked_add_invariant(unit, &span2),
Some(r) => r,
},
None => {
requires_relative_date_err(unit)?;
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) => match r.to_relative(unit)? {
None => {
return span1.checked_add_invariant_duration(unit, dur2)
}
Some(r) => r,
},
None => {
requires_relative_date_err(unit)?;
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::Week);
self.checked_add_invariant_duration(unit, span.to_invariant_duration())
}
#[inline]
fn checked_add_invariant_duration(
&self,
unit: Unit,
rhs_duration: SignedDuration,
) -> Result<Span, Error> {
assert!(unit <= Unit::Week);
let self_duration = self.to_invariant_duration();
let sum = self_duration + rhs_duration;
Span::from_invariant_duration(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_duration<'a, R: Into<SpanRelativeTo<'a>>>(
&self,
relative: R,
) -> Result<SignedDuration, Error> {
let max_unit = self.largest_unit();
let relative: SpanRelativeTo<'a> = relative.into();
let Some(result) = relative.to_relative(max_unit).transpose() else {
return Ok(self.to_invariant_duration());
};
let relspan = result
.and_then(|r| r.into_relative_span(Unit::Second, *self))
.with_context(|| match relative.kind {
SpanRelativeToKind::Civil(_) => E::ToDurationCivil,
SpanRelativeToKind::Zoned(_) => E::ToDurationZoned,
SpanRelativeToKind::DaysAre24Hours => {
E::ToDurationDaysAre24Hours
}
})?;
debug_assert!(relspan.span.largest_unit() <= Unit::Second);
Ok(relspan.span.to_invariant_duration())
}
#[inline]
pub(crate) fn to_invariant_duration(&self) -> SignedDuration {
const _FITS_IN_U64: () = {
debug_assert!(
i64::MAX as i128
> ((b::SpanWeeks::MAX as i128 * b::SECS_PER_WEEK as i128)
+ (b::SpanDays::MAX as i128
* b::SECS_PER_CIVIL_DAY as i128)
+ (b::SpanHours::MAX as i128
* b::SECS_PER_HOUR as i128)
+ (b::SpanMinutes::MAX as i128
* b::SECS_PER_MIN as i128)
+ b::SpanSeconds::MAX as i128
+ (b::SpanMilliseconds::MAX as i128
/ b::MILLIS_PER_SEC as i128)
+ (b::SpanMicroseconds::MAX as i128
/ b::MICROS_PER_SEC as i128)
+ (b::SpanNanoseconds::MAX as i128
/ b::NANOS_PER_SEC as i128)),
);
()
};
SignedDuration::from_civil_weeks32(self.get_weeks())
+ SignedDuration::from_civil_days32(self.get_days())
+ SignedDuration::from_hours32(self.get_hours())
+ SignedDuration::from_mins(self.get_minutes())
+ SignedDuration::from_secs(self.get_seconds())
+ SignedDuration::from_millis(self.get_milliseconds())
+ SignedDuration::from_micros(self.get_microseconds())
+ SignedDuration::from_nanos(self.get_nanoseconds())
}
#[inline]
pub(crate) fn to_invariant_duration_time_only(&self) -> SignedDuration {
const _FITS_IN_U64: () = {
debug_assert!(
i64::MAX as i128
> ((b::SpanHours::MAX as i128 * b::SECS_PER_HOUR as i128)
+ (b::SpanMinutes::MAX as i128
* b::SECS_PER_MIN as i128)
+ b::SpanSeconds::MAX as i128
+ (b::SpanMilliseconds::MAX as i128
/ b::MILLIS_PER_SEC as i128)
+ (b::SpanMicroseconds::MAX as i128
/ b::MICROS_PER_SEC as i128)
+ (b::SpanNanoseconds::MAX as i128
/ b::NANOS_PER_SEC as i128)),
);
()
};
SignedDuration::from_hours32(self.get_hours())
+ SignedDuration::from_mins(self.get_minutes())
+ SignedDuration::from_secs(self.get_seconds())
+ SignedDuration::from_millis(self.get_milliseconds())
+ SignedDuration::from_micros(self.get_microseconds())
+ SignedDuration::from_nanos(self.get_nanoseconds())
}
}
impl Span {
#[inline]
fn try_unit(self, unit: Unit, value: i64) -> Result<Span, Error> {
match unit {
Unit::Year => self.try_years(value),
Unit::Month => self.try_months(value),
Unit::Week => self.try_weeks(value),
Unit::Day => self.try_days(value),
Unit::Hour => self.try_hours(value),
Unit::Minute => self.try_minutes(value),
Unit::Second => self.try_seconds(value),
Unit::Millisecond => self.try_milliseconds(value),
Unit::Microsecond => self.try_microseconds(value),
Unit::Nanosecond => self.try_nanoseconds(value),
}
}
#[inline]
pub(crate) fn get_years_unsigned(&self) -> u16 {
self.years as u16
}
#[inline]
pub(crate) fn get_months_unsigned(&self) -> u32 {
self.months as u32
}
#[inline]
pub(crate) fn get_weeks_unsigned(&self) -> u32 {
self.weeks as u32
}
#[inline]
pub(crate) fn get_days_unsigned(&self) -> u32 {
self.days as u32
}
#[inline]
pub(crate) fn get_hours_unsigned(&self) -> u32 {
self.hours as u32
}
#[inline]
pub(crate) fn get_minutes_unsigned(&self) -> u64 {
self.minutes as u64
}
#[inline]
pub(crate) fn get_seconds_unsigned(&self) -> u64 {
self.seconds as u64
}
#[inline]
pub(crate) fn get_milliseconds_unsigned(&self) -> u64 {
self.milliseconds as u64
}
#[inline]
pub(crate) fn get_microseconds_unsigned(&self) -> u64 {
self.microseconds as u64
}
#[inline]
pub(crate) fn get_nanoseconds_unsigned(&self) -> u64 {
self.nanoseconds as u64
}
#[inline]
fn get_sign(&self) -> b::Sign {
self.sign
}
#[inline]
fn get_unit(&self, unit: Unit) -> i64 {
match unit {
Unit::Year => self.get_years().into(),
Unit::Month => self.get_months().into(),
Unit::Week => self.get_weeks().into(),
Unit::Day => self.get_days().into(),
Unit::Hour => self.get_hours().into(),
Unit::Minute => self.get_minutes(),
Unit::Second => self.get_seconds(),
Unit::Millisecond => self.get_milliseconds(),
Unit::Microsecond => self.get_microseconds(),
Unit::Nanosecond => self.get_nanoseconds(),
}
}
}
impl Span {
#[inline]
pub(crate) fn years_unchecked(mut self, years: i16) -> Span {
self.years = years;
self.units = self.units.set(Unit::Year, years == 0);
self
}
#[inline]
pub(crate) fn months_unchecked(mut self, months: i32) -> Span {
self.months = months;
self.units = self.units.set(Unit::Month, months == 0);
self
}
#[inline]
pub(crate) fn weeks_unchecked(mut self, weeks: i32) -> Span {
self.weeks = weeks;
self.units = self.units.set(Unit::Week, weeks == 0);
self
}
#[inline]
pub(crate) fn days_unchecked(mut self, days: i32) -> Span {
self.days = days;
self.units = self.units.set(Unit::Day, days == 0);
self
}
#[inline]
pub(crate) fn hours_unchecked(mut self, hours: i32) -> Span {
self.hours = hours;
self.units = self.units.set(Unit::Hour, hours == 0);
self
}
#[inline]
pub(crate) fn minutes_unchecked(mut self, minutes: i64) -> Span {
self.minutes = minutes;
self.units = self.units.set(Unit::Minute, minutes == 0);
self
}
#[inline]
pub(crate) fn seconds_unchecked(mut self, seconds: i64) -> Span {
self.seconds = seconds;
self.units = self.units.set(Unit::Second, seconds == 0);
self
}
#[inline]
pub(crate) fn milliseconds_unchecked(mut self, milliseconds: i64) -> Span {
self.milliseconds = milliseconds;
self.units = self.units.set(Unit::Millisecond, milliseconds == 0);
self
}
#[inline]
pub(crate) fn microseconds_unchecked(mut self, microseconds: i64) -> Span {
self.microseconds = microseconds;
self.units = self.units.set(Unit::Microsecond, microseconds == 0);
self
}
#[inline]
pub(crate) fn nanoseconds_unchecked(mut self, nanoseconds: i64) -> Span {
self.nanoseconds = nanoseconds;
self.units = self.units.set(Unit::Nanosecond, nanoseconds == 0);
self
}
#[inline]
pub(crate) fn sign_unchecked(self, sign: b::Sign) -> Span {
Span { sign, ..self }
}
}
impl Span {
pub(crate) fn from_invariant_duration(
largest: Unit,
mut dur: SignedDuration,
) -> Result<Span, Error> {
let mut span = Span::new();
if matches!(largest, Unit::Week) {
let (weeks, rem) = dur.as_civil_weeks_with_remainder();
span = span.try_weeks(weeks)?;
dur = rem;
}
if largest >= Unit::Day {
let (days, rem) = dur.as_civil_days_with_remainder();
span = span.try_days(days)?;
dur = rem;
}
if largest >= Unit::Hour {
let (hours, rem) = dur.as_hours_with_remainder();
span = span.try_hours(hours)?;
dur = rem;
}
if largest >= Unit::Minute {
let (mins, rem) = dur.as_mins_with_remainder();
span = span.try_minutes(mins)?;
dur = rem;
}
if largest >= Unit::Second {
let (secs, rem) = dur.as_secs_with_remainder();
span = span.try_seconds(secs)?;
dur = rem;
}
if largest >= Unit::Millisecond {
let (millis, rem) = dur.as_millis_with_remainder();
let millis = i64::try_from(millis)
.map_err(|_| b::SpanMilliseconds::error())?;
span = span.try_milliseconds(millis)?;
dur = rem;
}
if largest >= Unit::Microsecond {
let (micros, rem) = dur.as_micros_with_remainder();
let micros = i64::try_from(micros)
.map_err(|_| b::SpanMicroseconds::error())?;
span = span.try_microseconds(micros)?;
dur = rem;
}
if largest >= Unit::Nanosecond {
let nanos = i64::try_from(dur.as_nanos())
.map_err(|_| b::SpanNanoseconds::error())?;
span = span.try_nanoseconds(nanos)?;
}
Ok(span)
}
#[inline]
pub(crate) fn to_hms_seconds(&self) -> i64 {
let mut secs = self.seconds;
secs += self.minutes * b::SECS_PER_MIN;
secs += i64::from(self.hours) * b::SECS_PER_HOUR;
self.sign * secs
}
#[inline]
pub(crate) fn has_fractional_seconds(&self) -> bool {
static SUBSECOND: UnitSet = UnitSet::from_slice(&[
Unit::Millisecond,
Unit::Microsecond,
Unit::Nanosecond,
]);
!self.units().intersection(SUBSECOND).is_empty()
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn only_calendar(mut self) -> Span {
self.hours = 0;
self.minutes = 0;
self.seconds = 0;
self.milliseconds = 0;
self.microseconds = 0;
self.nanoseconds = 0;
if !self.sign.is_zero()
&& self.years == 0
&& self.months == 0
&& self.weeks == 0
&& self.days == 0
{
self.sign = b::Sign::Zero;
}
self.units = self.units.only_calendar();
self
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn only_time(mut self) -> Span {
self.years = 0;
self.months = 0;
self.weeks = 0;
self.days = 0;
if !self.sign.is_zero()
&& self.hours == 0
&& self.minutes == 0
&& self.seconds == 0
&& self.milliseconds == 0
&& self.microseconds == 0
&& self.nanoseconds == 0
{
self.sign = b::Sign::Zero;
}
self.units = self.units.only_time();
self
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn only_lower(self, unit: Unit) -> Span {
let mut span = self;
if unit <= Unit::Microsecond {
span = span.microseconds(0);
}
if unit <= Unit::Millisecond {
span = span.milliseconds(0);
}
if unit <= Unit::Second {
span = span.seconds(0);
}
if unit <= Unit::Minute {
span = span.minutes(0);
}
if unit <= Unit::Hour {
span = span.hours(0);
}
if unit <= Unit::Day {
span = span.days(0);
}
if unit <= Unit::Week {
span = span.weeks(0);
}
if unit <= Unit::Month {
span = span.months(0);
}
if unit <= Unit::Year {
span = span.years(0);
}
span
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn without_lower(self, unit: Unit) -> Span {
let mut span = self;
if unit > Unit::Nanosecond {
span = span.nanoseconds(0);
}
if unit > Unit::Microsecond {
span = span.microseconds(0);
}
if unit > Unit::Millisecond {
span = span.milliseconds(0);
}
if unit > Unit::Second {
span = span.seconds(0);
}
if unit > Unit::Minute {
span = span.minutes(0);
}
if unit > Unit::Hour {
span = span.hours(0);
}
if unit > Unit::Day {
span = span.days(0);
}
if unit > Unit::Week {
span = span.weeks(0);
}
if unit > Unit::Month {
span = span.months(0);
}
span
}
#[cfg_attr(feature = "perf-inline", inline(always))]
pub(crate) fn smallest_non_time_non_zero_unit_error(
&self,
) -> Option<Error> {
let non_time_unit = self.largest_calendar_unit()?;
Some(Error::from(UnitConfigError::CalendarUnitsNotAllowed {
unit: non_time_unit,
}))
}
#[inline]
fn largest_calendar_unit(&self) -> Option<Unit> {
self.units().only_calendar().largest_unit()
}
#[inline]
pub(crate) fn largest_unit(&self) -> Unit {
self.units().largest_unit().unwrap_or(Unit::Nanosecond)
}
#[inline]
pub(crate) fn units(&self) -> UnitSet {
self.units
}
#[cfg(feature = "alloc")]
#[allow(dead_code)]
pub(crate) fn debug(&self) -> alloc::string::String {
use core::fmt::Write;
let mut buf = alloc::string::String::new();
write!(buf, "Span {{ sign: {:?}, units: {:?}", self.sign, self.units)
.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();
}
buf.push_str(" }}");
buf
}
#[inline]
fn resign(&self, units: impl Into<i64>, new: &Span) -> b::Sign {
fn imp(span: &Span, units: i64, new: &Span) -> b::Sign {
if units.is_negative() {
return b::Sign::Negative;
}
let mut new_is_zero = new.sign.is_zero() && 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 (span.is_zero(), new_is_zero) {
(_, true) => b::Sign::Zero,
(true, false) => b::Sign::from(units),
(false, false) => new.sign,
}
}
imp(self, units.into(), new)
}
}
impl core::fmt::Debug for Span {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use crate::fmt::StdFmtWrite;
friendly::DEFAULT_SPAN_PRINTER
.print_span(self, StdFmtWrite(f))
.map_err(|_| core::fmt::Error)
}
}
impl core::fmt::Display for Span {
#[inline]
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
use crate::fmt::StdFmtWrite;
if f.alternate() {
friendly::DEFAULT_SPAN_PRINTER
.print_span(self, StdFmtWrite(f))
.map_err(|_| core::fmt::Error)
} else {
temporal::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> {
parse_iso_or_friendly(string.as_bytes())
}
}
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(Error::from(E::ConvertNegative));
}
SignedDuration::try_from(sp).and_then(UnsignedDuration::try_from)
}
}
impl TryFrom<UnsignedDuration> for Span {
type Error = Error;
#[inline]
fn try_from(d: UnsignedDuration) -> Result<Span, Error> {
let sdur = SignedDuration::try_from(d)
.map_err(|_| b::SpanSeconds::error())?;
Span::try_from(sdur)
}
}
impl TryFrom<Span> for SignedDuration {
type Error = Error;
#[inline]
fn try_from(sp: Span) -> Result<SignedDuration, Error> {
requires_relative_date_err(sp.largest_unit())
.context(E::ConvertSpanToSignedDuration)?;
Ok(sp.to_invariant_duration())
}
}
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 / b::NANOS_PER_MILLI;
let microseconds =
(nanoseconds % b::NANOS_PER_MILLI) / b::NANOS_PER_MICRO;
let nanoseconds = nanoseconds % b::NANOS_PER_MICRO;
let span = Span::new().try_seconds(seconds)?;
Ok(span
.milliseconds(milliseconds)
.microseconds(microseconds)
.nanoseconds(nanoseconds))
}
}
#[cfg(feature = "serde")]
impl serde_core::Serialize for Span {
#[inline]
fn serialize<S: serde_core::Serializer>(
&self,
serializer: S,
) -> Result<S::Ok, S::Error> {
serializer.collect_str(self)
}
}
#[cfg(feature = "serde")]
impl<'de> serde_core::Deserialize<'de> for Span {
#[inline]
fn deserialize<D: serde_core::Deserializer<'de>>(
deserializer: D,
) -> Result<Span, D::Error> {
use serde_core::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> {
parse_iso_or_friendly(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_str(SpanVisitor)
}
}
#[cfg(test)]
impl quickcheck::Arbitrary for Span {
fn arbitrary(g: &mut quickcheck::Gen) -> Span {
const MIN: i64 = -631_107_417_600_000_000;
const MAX: i64 = 631_107_417_600_000_000;
const LEN: i64 = MAX - MIN + 1;
let mut nanos = i64::arbitrary(g).wrapping_rem_euclid(LEN);
nanos += MIN;
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(),
self.get_months(),
self.get_weeks(),
self.get_days(),
),
(
self.get_hours(),
self.get_minutes(),
self.get_seconds(),
self.get_milliseconds(),
),
(self.get_microseconds(), self.get_nanoseconds()),
)
.shrink()
.filter_map(
|(
(years, months, weeks, days),
(hours, minutes, seconds, milliseconds),
(microseconds, nanoseconds),
)| {
let span = Span::new()
.try_years(years)
.ok()?
.try_months(months)
.ok()?
.try_weeks(weeks)
.ok()?
.try_days(days)
.ok()?
.try_hours(hours)
.ok()?
.try_minutes(minutes)
.ok()?
.try_seconds(seconds)
.ok()?
.try_milliseconds(milliseconds)
.ok()?
.try_microseconds(microseconds)
.ok()?
.try_nanoseconds(nanoseconds)
.ok()?;
Some(span)
},
),
)
}
}
#[derive(Clone, Copy, Debug, Default)]
#[repr(transparent)]
pub struct SpanFieldwise(pub Span);
impl core::ops::Neg for SpanFieldwise {
type Output = SpanFieldwise;
#[inline]
fn neg(self) -> SpanFieldwise {
SpanFieldwise(self.0.negate())
}
}
impl Eq for SpanFieldwise {}
impl PartialEq for SpanFieldwise {
fn eq(&self, rhs: &SpanFieldwise) -> bool {
self.0.sign == rhs.0.sign
&& self.0.years == rhs.0.years
&& self.0.months == rhs.0.months
&& self.0.weeks == rhs.0.weeks
&& self.0.days == rhs.0.days
&& self.0.hours == rhs.0.hours
&& self.0.minutes == rhs.0.minutes
&& self.0.seconds == rhs.0.seconds
&& self.0.milliseconds == rhs.0.milliseconds
&& self.0.microseconds == rhs.0.microseconds
&& self.0.nanoseconds == rhs.0.nanoseconds
}
}
impl<'a> PartialEq<SpanFieldwise> for &'a SpanFieldwise {
fn eq(&self, rhs: &SpanFieldwise) -> bool {
*self == rhs
}
}
impl PartialEq<Span> for SpanFieldwise {
fn eq(&self, rhs: &Span) -> bool {
self == rhs.fieldwise()
}
}
impl PartialEq<SpanFieldwise> for Span {
fn eq(&self, rhs: &SpanFieldwise) -> bool {
self.fieldwise() == *rhs
}
}
impl<'a> PartialEq<SpanFieldwise> for &'a Span {
fn eq(&self, rhs: &SpanFieldwise) -> bool {
self.fieldwise() == *rhs
}
}
impl core::hash::Hash for SpanFieldwise {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.0.sign.hash(state);
self.0.years.hash(state);
self.0.months.hash(state);
self.0.weeks.hash(state);
self.0.days.hash(state);
self.0.hours.hash(state);
self.0.minutes.hash(state);
self.0.seconds.hash(state);
self.0.milliseconds.hash(state);
self.0.microseconds.hash(state);
self.0.nanoseconds.hash(state);
}
}
impl From<Span> for SpanFieldwise {
fn from(span: Span) -> SpanFieldwise {
SpanFieldwise(span)
}
}
impl From<SpanFieldwise> for Span {
fn from(span: SpanFieldwise) -> Span {
span.0
}
}
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 duration(self) -> SignedDuration {
match self {
Unit::Nanosecond => SignedDuration::from_nanos(1),
Unit::Microsecond => SignedDuration::from_micros(1),
Unit::Millisecond => SignedDuration::from_millis(1),
Unit::Second => SignedDuration::from_secs(1),
Unit::Minute => SignedDuration::from_mins32(1),
Unit::Hour => SignedDuration::from_hours32(1),
Unit::Day => SignedDuration::from_civil_days32(1),
Unit::Week => SignedDuration::from_civil_weeks32(1),
unit => unreachable!("{unit:?} has no definitive time interval"),
}
}
fn is_variable(self) -> bool {
matches!(self, Unit::Year | Unit::Month | Unit::Week | Unit::Day)
}
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",
}
}
pub(crate) fn compact(&self) -> &'static str {
match *self {
Unit::Year => "y",
Unit::Month => "mo",
Unit::Week => "w",
Unit::Day => "d",
Unit::Hour => "h",
Unit::Minute => "m",
Unit::Second => "s",
Unit::Millisecond => "ms",
Unit::Microsecond => "µs",
Unit::Nanosecond => "ns",
}
}
pub(crate) fn as_usize(&self) -> usize {
*self as usize
}
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,
}
}
fn error(self) -> b::BoundsError {
match self {
Unit::Year => b::SpanYears::error(),
Unit::Month => b::SpanMonths::error(),
Unit::Week => b::SpanWeeks::error(),
Unit::Day => b::SpanDays::error(),
Unit::Hour => b::SpanHours::error(),
Unit::Minute => b::SpanMinutes::error(),
Unit::Second => b::SpanSeconds::error(),
Unit::Millisecond => b::SpanMilliseconds::error(),
Unit::Microsecond => b::SpanMicroseconds::error(),
Unit::Nanosecond => b::SpanNanoseconds::error(),
}
}
}
#[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]
pub fn days_are_24_hours(self) -> SpanArithmetic<'a> {
self.relative(SpanRelativeTo::days_are_24_hours())
}
}
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<(Span, SpanRelativeTo<'a>)> for SpanArithmetic<'a> {
#[inline]
fn from(
(span, relative): (Span, SpanRelativeTo<'a>),
) -> SpanArithmetic<'a> {
SpanArithmetic::from(span).relative(relative)
}
}
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<'a, 'b> From<(&'a Span, SpanRelativeTo<'b>)> for SpanArithmetic<'b> {
#[inline]
fn from(
(span, relative): (&'a Span, SpanRelativeTo<'b>),
) -> SpanArithmetic<'b> {
SpanArithmetic::from(span).relative(relative)
}
}
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]
pub fn days_are_24_hours(self) -> SpanCompare<'a> {
self.relative(SpanRelativeTo::days_are_24_hours())
}
}
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) => match r.to_relative(unit)? {
Some(r) => r,
None => {
let dur1 = span1.to_invariant_duration();
let dur2 = span2.to_invariant_duration();
return Ok(dur1.cmp(&dur2));
}
},
None => {
requires_relative_date_err(unit)?;
let dur1 = span1.to_invariant_duration();
let dur2 = span2.to_invariant_duration();
return Ok(dur1.cmp(&dur2));
}
};
let end1 = start.checked_add(span1)?.to_duration();
let end2 = start.checked_add(span2)?.to_duration();
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<(Span, SpanRelativeTo<'a>)> for SpanCompare<'a> {
#[inline]
fn from((span, relative): (Span, SpanRelativeTo<'a>)) -> SpanCompare<'a> {
SpanCompare::from(span).relative(relative)
}
}
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)
}
}
impl<'a, 'b> From<(&'a Span, SpanRelativeTo<'b>)> for SpanCompare<'b> {
#[inline]
fn from(
(span, relative): (&'a Span, SpanRelativeTo<'b>),
) -> SpanCompare<'b> {
SpanCompare::from(span).relative(relative)
}
}
#[derive(Clone, Copy, Debug)]
pub struct SpanTotal<'a> {
unit: Unit,
relative: Option<SpanRelativeTo<'a>>,
}
impl<'a> SpanTotal<'a> {
#[inline]
pub fn days_are_24_hours(self) -> SpanTotal<'a> {
self.relative(SpanRelativeTo::days_are_24_hours())
}
}
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) => match r.to_relative(max_unit)? {
Some(r) => r,
None => {
return Ok(self.total_invariant(span));
}
},
None => {
requires_relative_date_err(max_unit)?;
return Ok(self.total_invariant(span));
}
};
let relspan = relative.into_relative_span(self.unit, span)?;
if !self.unit.is_variable() {
return Ok(self.total_invariant(relspan.span));
}
assert!(self.unit >= Unit::Day);
let sign = relspan.span.get_sign();
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) = unit_start_and_end(
&relative_start,
relspan.span.without_lower(self.unit),
self.unit,
sign.as_i64(),
)?;
let denom = (relative1 - relative0).as_nanos() as f64;
let numer = (relative_end.to_duration() - relative0).as_nanos() as f64;
let unit_val = relspan.span.get_unit(self.unit) as f64;
Ok(unit_val + (numer / denom) * (sign.as_i8() as f64))
}
#[inline]
fn total_invariant(&self, span: Span) -> f64 {
assert!(self.unit <= Unit::Week);
let dur = span.to_invariant_duration().as_nanos();
(dur as f64) / (self.unit.duration().as_nanos() 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)
}
}
impl<'a> From<(Unit, SpanRelativeTo<'a>)> for SpanTotal<'a> {
#[inline]
fn from((unit, relative): (Unit, SpanRelativeTo<'a>)) -> SpanTotal<'a> {
SpanTotal::from(unit).relative(relative)
}
}
#[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 fn days_are_24_hours(self) -> SpanRound<'a> {
self.relative(SpanRelativeTo::days_are_24_hours())
}
#[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(&self) -> bool {
self.smallest > Unit::Nanosecond || self.increment != 1
}
#[inline]
pub(crate) fn rounding_calendar_only_may_change_span(&self) -> bool {
self.smallest > Unit::Day || self.increment != 1
}
fn round(&self, span: Span) -> Result<Span, Error> {
let existing_largest = span.largest_unit();
let largest = self
.largest
.unwrap_or_else(|| self.smallest.max(existing_largest));
let max = existing_largest.max(largest);
let increment = Increment::for_span(self.smallest, self.increment)?;
if largest < self.smallest {
return Err(Error::from(
UnitConfigError::LargestSmallerThanSmallest {
smallest: self.smallest,
largest,
},
));
}
let relative = match self.relative {
Some(ref r) => {
match r.to_relative(max)? {
Some(r) => r,
None => {
return Ok(round_span_invariant(
span, largest, &increment, self.mode,
)?);
}
}
}
None => {
requires_relative_date_err(self.smallest)
.context(E::OptionSmallest)?;
if let Some(largest) = self.largest {
requires_relative_date_err(largest)
.context(E::OptionLargest)?;
}
requires_relative_date_err(existing_largest)
.context(E::OptionLargestInSpan)?;
assert!(max <= Unit::Week);
return Ok(round_span_invariant(
span, largest, &increment, self.mode,
)?);
}
};
relative.round(span, largest, &increment, self.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> {
#[inline]
pub const fn days_are_24_hours() -> SpanRelativeTo<'static> {
let kind = SpanRelativeToKind::DaysAre24Hours;
SpanRelativeTo { kind }
}
fn to_relative(&self, unit: Unit) -> Result<Option<Relative<'a>>, Error> {
if !unit.is_variable() {
return Ok(None);
}
match self.kind {
SpanRelativeToKind::Civil(dt) => {
Ok(Some(Relative::Civil(RelativeCivil::new(dt)?)))
}
SpanRelativeToKind::Zoned(zdt) => {
Ok(Some(Relative::Zoned(RelativeZoned {
zoned: DumbCow::Borrowed(zdt),
})))
}
SpanRelativeToKind::DaysAre24Hours => {
if matches!(unit, Unit::Year | Unit::Month) {
return Err(Error::from(
UnitConfigError::RelativeYearOrMonthGivenDaysAre24Hours {
unit,
},
));
}
Ok(None)
}
}
}
}
#[derive(Clone, Copy, Debug)]
enum SpanRelativeToKind<'a> {
Civil(DateTime),
Zoned(&'a Zoned),
DaysAre24Hours,
}
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) }
}
}
#[derive(Clone, Copy, Default)]
pub(crate) struct UnitSet(u16);
impl UnitSet {
#[inline]
const fn empty() -> UnitSet {
UnitSet(0)
}
#[inline]
const fn set(self, unit: Unit, is_zero: bool) -> UnitSet {
let bit = 1 << unit as usize;
if is_zero {
UnitSet(self.0 & !bit)
} else {
UnitSet(self.0 | bit)
}
}
#[inline]
pub(crate) const fn from_slice(units: &[Unit]) -> UnitSet {
let mut set = UnitSet::empty();
let mut i = 0;
while i < units.len() {
set = set.set(units[i], false);
i += 1;
}
set
}
#[inline]
pub(crate) fn is_empty(&self) -> bool {
self.0 == 0
}
#[inline]
pub(crate) fn contains(self, unit: Unit) -> bool {
(self.0 & (1 << unit as usize)) != 0
}
#[inline]
pub(crate) fn contains_only(self, unit: Unit) -> bool {
self.0 == (1 << unit as usize)
}
#[inline]
pub(crate) fn only_calendar(self) -> UnitSet {
UnitSet(self.0 & 0b0000_0011_1100_0000)
}
#[inline]
pub(crate) fn only_time(self) -> UnitSet {
UnitSet(self.0 & 0b0000_0000_0011_1111)
}
#[inline]
pub(crate) fn intersection(self, other: UnitSet) -> UnitSet {
UnitSet(self.0 & other.0)
}
#[inline]
pub(crate) fn largest_unit(self) -> Option<Unit> {
let zeros = usize::try_from(self.0.leading_zeros()).ok()?;
15usize.checked_sub(zeros).and_then(Unit::from_usize)
}
}
impl core::fmt::Debug for UnitSet {
fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result {
write!(f, "{{")?;
let mut units = *self;
let mut i = 0;
while let Some(unit) = units.largest_unit() {
if i > 0 {
write!(f, ", ")?;
}
i += 1;
write!(f, "{}", unit.compact())?;
units = units.set(unit, false);
}
if i == 0 {
write!(f, "∅")?;
}
write!(f, "}}")
}
}
#[derive(Clone, Debug)]
enum Relative<'a> {
Civil(RelativeCivil),
Zoned(RelativeZoned<'a>),
}
impl<'a> Relative<'a> {
fn checked_add(&'a self, span: Span) -> Result<Relative<'a>, 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(
&'a self,
duration: SignedDuration,
) -> Result<Relative<'a>, 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_duration(&self) -> SignedDuration {
match *self {
Relative::Civil(dt) => dt.timestamp.as_duration(),
Relative::Zoned(ref zdt) => zdt.zoned.timestamp().as_duration(),
}
}
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().is_zero()
&& !relspan.span.get_sign().is_zero()
&& span.get_sign() != relspan.span.get_sign()
{
unreachable!(
"balanced span should have same sign as original span"
)
}
Ok(relspan)
}
fn round(
self,
span: Span,
largest: Unit,
increment: &Increment,
mode: RoundMode,
) -> Result<Span, Error> {
let relspan = self.into_relative_span(largest, span)?;
if relspan.span.get_sign().is_zero() {
return Ok(relspan.span);
}
let nudge = match relspan.kind {
RelativeSpanKind::Civil { start, end } => {
if increment.unit() > Unit::Day {
Nudge::relative_calendar(
relspan.span,
&Relative::Civil(start),
&Relative::Civil(end),
increment,
mode,
)?
} else {
Nudge::relative_invariant(
relspan.span,
end.timestamp.as_duration(),
largest,
increment,
mode,
)?
}
}
RelativeSpanKind::Zoned { ref start, ref end } => {
if increment.unit() >= Unit::Day {
Nudge::relative_calendar(
relspan.span,
&Relative::Zoned(start.borrowed()),
&Relative::Zoned(end.borrowed()),
increment,
mode,
)?
} else if largest >= Unit::Day {
Nudge::relative_zoned_time(
relspan.span,
start,
increment,
mode,
)?
} else {
Nudge::relative_invariant(
relspan.span,
end.zoned.timestamp().as_duration(),
largest,
increment,
mode,
)?
}
}
};
nudge.bubble(&relspan, increment.unit(), 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 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))
.context(E::FailedSpanBetweenDateTimes { unit: largest })?,
RelativeSpanKind::Zoned { ref start, ref end } => {
start.zoned.until((largest, &*end.zoned)).context(
E::FailedSpanBetweenZonedDateTimes { unit: largest },
)?
}
};
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)
.context(E::ConvertDateTimeToTimestamp)?
.timestamp();
Ok(RelativeCivil { datetime, timestamp })
}
fn checked_add(&self, span: Span) -> Result<RelativeCivil, Error> {
let datetime = self.datetime.checked_add(span)?;
let timestamp = datetime
.to_zoned(TimeZone::UTC)
.context(E::ConvertDateTimeToTimestamp)?
.timestamp();
Ok(RelativeCivil { datetime, timestamp })
}
fn checked_add_duration(
&self,
duration: SignedDuration,
) -> Result<RelativeCivil, Error> {
let datetime = self.datetime.checked_add(duration)?;
let timestamp = datetime
.to_zoned(TimeZone::UTC)
.context(E::ConvertDateTimeToTimestamp)?
.timestamp();
Ok(RelativeCivil { datetime, timestamp })
}
fn until(
&self,
largest: Unit,
other: &RelativeCivil,
) -> Result<Span, Error> {
self.datetime
.until((largest, other.datetime))
.context(E::FailedSpanBetweenDateTimes { unit: largest })
}
}
#[derive(Clone, Debug)]
struct RelativeZoned<'a> {
zoned: DumbCow<'a, Zoned>,
}
impl<'a> RelativeZoned<'a> {
fn checked_add(
&self,
span: Span,
) -> Result<RelativeZoned<'static>, Error> {
let zoned = self.zoned.checked_add(span)?;
Ok(RelativeZoned { zoned: DumbCow::Owned(zoned) })
}
fn checked_add_duration(
&self,
duration: SignedDuration,
) -> Result<RelativeZoned<'static>, Error> {
let zoned = self.zoned.checked_add(duration)?;
Ok(RelativeZoned { zoned: DumbCow::Owned(zoned) })
}
fn until(
&self,
largest: Unit,
other: &RelativeZoned<'a>,
) -> Result<Span, Error> {
self.zoned
.until((largest, &*other.zoned))
.context(E::FailedSpanBetweenZonedDateTimes { unit: largest })
}
fn borrowed(&'a self) -> RelativeZoned<'a> {
RelativeZoned { zoned: self.zoned.borrowed() }
}
}
#[derive(Debug)]
struct Nudge {
span: Span,
rounded_relative_end: SignedDuration,
grew_big_unit: bool,
}
impl Nudge {
fn relative_invariant(
balanced: Span,
relative_end: SignedDuration,
largest: Unit,
increment: &Increment,
mode: RoundMode,
) -> Result<Nudge, Error> {
assert!(increment.unit() <= Unit::Day);
let sign = balanced.get_sign();
let balanced_nanos = balanced.to_invariant_duration();
let rounded_nanos = increment.round(mode, balanced_nanos)?;
let span = Span::from_invariant_duration(largest, rounded_nanos)
.context(E::ConvertNanoseconds { unit: largest })?
.years(balanced.get_years())
.months(balanced.get_months())
.weeks(balanced.get_weeks());
let diff_nanos = rounded_nanos - balanced_nanos;
let diff_days =
rounded_nanos.as_civil_days() - balanced_nanos.as_civil_days();
let grew_big_unit = b::Sign::from(diff_days) == 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<'_>,
increment: &Increment,
mode: RoundMode,
) -> Result<Nudge, Error> {
assert!(increment.unit() >= Unit::Day);
let increment_units = i64::from(increment.value());
let smallest = increment.unit();
let sign = balanced.get_sign();
let truncated =
increment_units * (balanced.get_unit(smallest) / increment_units);
let span =
balanced.without_lower(smallest).try_unit(smallest, truncated)?;
let amount = sign * increment_units;
let (relative0, relative1) =
unit_start_and_end(relative_start, span, smallest, amount)?;
let progress_nanos = relative_end.to_duration() - relative0;
let increment_nanos = (relative1 - relative0).abs();
let rounded_nanos =
mode.round_by_duration(progress_nanos, increment_nanos)?;
let grew_big_unit =
sign == b::Sign::from(rounded_nanos - progress_nanos);
debug_assert_eq!(rounded_nanos.subsec_nanos(), 0);
debug_assert_eq!(increment_nanos.subsec_nanos(), 0);
let span = span.try_unit(
smallest,
truncated
+ (increment_units
* (rounded_nanos.as_secs() / increment_nanos.as_secs())),
)?;
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<'_>,
increment: &Increment,
mode: RoundMode,
) -> Result<Nudge, Error> {
let sign = balanced.get_sign();
let time_dur = balanced.only_lower(Unit::Day).to_invariant_duration();
let mut rounded_time_nanos = increment.round(mode, time_dur)?;
let (relative0, relative1) = unit_start_and_end(
&Relative::Zoned(relative_start.borrowed()),
balanced.without_lower(Unit::Day),
Unit::Day,
sign.as_i64(),
)?;
let day_nanos = relative1 - relative0;
let beyond_day_nanos = rounded_time_nanos - day_nanos;
let mut day_delta = 0;
let rounded_relative_end = if beyond_day_nanos.is_zero()
|| b::Sign::from(beyond_day_nanos) == sign
{
day_delta += 1;
rounded_time_nanos = increment.round(mode, beyond_day_nanos)?;
relative1 + rounded_time_nanos
} else {
relative0 + rounded_time_nanos
};
let span =
Span::from_invariant_duration(Unit::Hour, rounded_time_nanos)
.context(E::ConvertNanoseconds { unit: Unit::Hour })?
.years(balanced.get_years())
.months(balanced.get_months())
.weeks(balanced.get_weeks())
.days(balanced.get_days() + 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();
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_unit(unit)
.checked_add(sign.as_i64())
.ok_or_else(|| unit.error())?;
let span_end = span_start.try_unit(unit, new_units)?;
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_duration();
if beyond.is_zero() || b::Sign::from(beyond) == sign {
balanced = span_end;
} else {
break;
}
}
Ok(balanced)
}
}
fn round_span_invariant(
span: Span,
largest: Unit,
increment: &Increment,
mode: RoundMode,
) -> Result<Span, Error> {
debug_assert!(increment.unit() <= Unit::Week);
debug_assert!(largest <= Unit::Week);
let dur = span.to_invariant_duration();
let rounded = increment.round(mode, dur)?;
Span::from_invariant_duration(largest, rounded)
.context(E::ConvertNanoseconds { unit: largest })
}
fn unit_start_and_end(
relative: &Relative<'_>,
span: Span,
unit: Unit,
amount: i64,
) -> Result<(SignedDuration, SignedDuration), Error> {
let amount =
span.get_unit(unit).checked_add(amount).ok_or_else(|| unit.error())?;
let span_amount = span.try_unit(unit, amount)?;
let relative0 = relative.checked_add(span)?.to_duration();
let relative1 = relative.checked_add(span_amount)?.to_duration();
assert_ne!(
relative0, relative1,
"adding different spans should produce different timestamps"
);
Ok((relative0, relative1))
}
#[cfg_attr(feature = "perf-inline", inline(always))]
fn parse_iso_or_friendly(bytes: &[u8]) -> Result<Span, Error> {
let Some((&byte, tail)) = bytes.split_first() else {
return Err(crate::Error::from(
crate::error::fmt::Error::HybridDurationEmpty,
));
};
let mut first = byte;
if first == b'+' || first == b'-' {
let Some(&byte) = tail.first() else {
return Err(crate::Error::from(
crate::error::fmt::Error::HybridDurationPrefix { sign: first },
));
};
first = byte;
}
if first == b'P' || first == b'p' {
temporal::DEFAULT_SPAN_PARSER.parse_span(bytes)
} else {
friendly::DEFAULT_SPAN_PARSER.parse_span(bytes)
}
}
fn requires_relative_date_err(unit: Unit) -> Result<(), Error> {
if unit.is_variable() {
return Err(Error::from(if matches!(unit, Unit::Week | Unit::Day) {
UnitConfigError::RelativeWeekOrDay { unit }
} else {
UnitConfigError::RelativeYearOrMonth { unit }
}));
}
Ok(())
}
#[cfg(test)]
mod tests {
use std::io::Cursor;
use alloc::string::ToString;
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(SpanTotal::from(Unit::Day).days_are_24_hours())
.unwrap();
assert_eq!(total, 1428.8980208333332);
let span = 2756.hours();
let dt = date(2020, 1, 1).at(0, 0, 0, 0);
let zdt = dt.in_tz("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 = 79.hours().seconds(630);
let span3 = 78.hours().minutes(50);
let mut array = [span1, span2, span3];
array.sort_by(|sp1, sp2| sp1.compare(sp2).unwrap());
assert_eq!(array, [span3, span1, span2].map(SpanFieldwise));
let day24 = SpanRelativeTo::days_are_24_hours();
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, day24)).unwrap());
assert_eq!(array, [span3, span1, span2].map(SpanFieldwise));
let dt = date(2020, 11, 1).at(0, 0, 0, 0);
let zdt = dt.in_tz("America/Los_Angeles").unwrap();
array.sort_by(|sp1, sp2| sp1.compare((sp2, &zdt)).unwrap());
assert_eq!(array, [span1, span3, span2].map(SpanFieldwise));
}
#[test]
fn test_checked_add() {
let span1 = 1.hour();
let span2 = 30.minutes();
let sum = span1.checked_add(span2).unwrap();
span_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();
span_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);
span_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();
span_eq!(sum, 3.months());
let relative = date(2000, 3, 1).at(0, 0, 0, 0);
let sum = span.checked_add((span, relative)).unwrap();
span_eq!(sum, 2.months().days(30));
}
#[test]
fn test_round_day_time() {
let span = 29.seconds();
let rounded = span.round(Unit::Minute).unwrap();
span_eq!(rounded, 0.minute());
let span = 30.seconds();
let rounded = span.round(Unit::Minute).unwrap();
span_eq!(rounded, 1.minute());
let span = 8.seconds();
let rounded = span
.round(
SpanRound::new()
.smallest(Unit::Nanosecond)
.largest(Unit::Microsecond),
)
.unwrap();
span_eq!(rounded, 8_000_000.microseconds());
let span = 130.minutes();
let rounded = span
.round(SpanRound::new().largest(Unit::Day).days_are_24_hours())
.unwrap();
span_eq!(rounded, 2.hours().minutes(10));
let span = 10.minutes().seconds(52);
let rounded = span.round(Unit::Minute).unwrap();
span_eq!(rounded, 11.minutes());
let span = 10.minutes().seconds(52);
let rounded = span
.round(
SpanRound::new().smallest(Unit::Minute).mode(RoundMode::Trunc),
)
.unwrap();
span_eq!(rounded, 10.minutes());
let span = 2.hours().minutes(34).seconds(18);
let rounded =
span.round(SpanRound::new().largest(Unit::Second)).unwrap();
span_eq!(rounded, 9258.seconds());
let span = 6.minutes();
let rounded = span
.round(
SpanRound::new()
.smallest(Unit::Minute)
.increment(5)
.mode(RoundMode::Ceil),
)
.unwrap();
span_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).in_tz("America/New_York").unwrap();
let options = SpanRound::new()
.largest(Unit::Year)
.smallest(Unit::Day)
.relative(&relative);
let rounded = span.round(options).unwrap();
span_eq!(rounded, 3.months().days(24));
let span = 24.hours().nanoseconds(5);
let relative = date(2000, 10, 29)
.at(0, 0, 0, 0)
.in_tz("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();
span_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)
.in_tz("America/New_York")
.unwrap();
let options =
SpanRound::new().smallest(Unit::Millisecond).relative(&relative);
let rounded = span.round(options).unwrap();
span_eq!(rounded, -1.month().days(1).hours(1));
let dt = relative.checked_add(span).unwrap();
let diff = relative.until((Unit::Month, &dt)).unwrap();
span_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)
.in_tz("America/New_York")
.unwrap();
let options =
SpanRound::new().smallest(Unit::Millisecond).relative(&relative);
let rounded = span.round(options).unwrap();
span_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).in_tz("America/New_York").unwrap();
let options = SpanRound::new().largest(Unit::Year).relative(&relative);
let rounded = span.round(options).unwrap();
span_eq!(rounded, 3.months().days(23).hours(21));
let span = 2756.hours();
let relative =
date(2020, 9, 1).at(0, 0, 0, 0).in_tz("America/New_York").unwrap();
let options = SpanRound::new().largest(Unit::Year).relative(&relative);
let rounded = span.round(options).unwrap();
span_eq!(rounded, 3.months().days(23).hours(19));
let span = 3.hours();
let relative =
date(2020, 3, 8).at(0, 0, 0, 0).in_tz("America/New_York").unwrap();
let options = SpanRound::new().largest(Unit::Year).relative(&relative);
let rounded = span.round(options).unwrap();
span_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();
span_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();
span_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();
span_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();
span_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();
span_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();
span_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();
span_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();
span_eq!(rounded, 3.hours());
}
#[test]
fn span_sign() {
assert_eq!(Span::new().get_sign(), b::Sign::Zero);
assert_eq!(Span::new().days(1).get_sign(), b::Sign::Positive);
assert_eq!(Span::new().days(-1).get_sign(), b::Sign::Negative);
assert_eq!(Span::new().days(1).days(0).get_sign(), b::Sign::Zero);
assert_eq!(Span::new().days(-1).days(0).get_sign(), b::Sign::Zero);
assert_eq!(
Span::new().years(1).days(1).days(0).get_sign(),
b::Sign::Positive,
);
assert_eq!(
Span::new().years(-1).days(-1).days(0).get_sign(),
b::Sign::Negative,
);
}
#[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>(), 64);
}
#[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 dur = span.to_invariant_duration();
let got = Span::from_invariant_duration(largest, dur).unwrap();
quickcheck::TestResult::from_bool(dur == got.to_invariant_duration())
}
}
#[test]
fn span_deserialize_yaml() {
let expected = Span::new()
.years(1)
.months(2)
.weeks(3)
.days(4)
.hours(5)
.minutes(6)
.seconds(7);
let deserialized: Span =
serde_yaml::from_str("P1y2m3w4dT5h6m7s").unwrap();
span_eq!(deserialized, expected);
let deserialized: Span =
serde_yaml::from_slice("P1y2m3w4dT5h6m7s".as_bytes()).unwrap();
span_eq!(deserialized, expected);
let cursor = Cursor::new(b"P1y2m3w4dT5h6m7s");
let deserialized: Span = serde_yaml::from_reader(cursor).unwrap();
span_eq!(deserialized, expected);
}
#[test]
fn display() {
let span = Span::new()
.years(1)
.months(2)
.weeks(3)
.days(4)
.hours(5)
.minutes(6)
.seconds(7)
.milliseconds(8)
.microseconds(9)
.nanoseconds(10);
insta::assert_snapshot!(
span,
@"P1Y2M3W4DT5H6M7.00800901S",
);
insta::assert_snapshot!(
alloc::format!("{span:#}"),
@"1y 2mo 3w 4d 5h 6m 7s 8ms 9µs 10ns",
);
}
#[test]
fn humantime_compatibility_parse() {
let dur = std::time::Duration::new(60 * 60 * 24 * 411, 123_456_789);
let formatted = humantime::format_duration(dur).to_string();
assert_eq!(
formatted,
"1year 1month 15days 7h 26m 24s 123ms 456us 789ns"
);
let expected = 1
.year()
.months(1)
.days(15)
.hours(7)
.minutes(26)
.seconds(24)
.milliseconds(123)
.microseconds(456)
.nanoseconds(789);
span_eq!(formatted.parse::<Span>().unwrap(), expected);
}
#[test]
fn humantime_compatibility_print() {
static PRINTER: friendly::SpanPrinter = friendly::SpanPrinter::new()
.designator(friendly::Designator::HumanTime);
let span = 1
.year()
.months(1)
.days(15)
.hours(7)
.minutes(26)
.seconds(24)
.milliseconds(123)
.microseconds(456)
.nanoseconds(789);
let formatted = PRINTER.span_to_string(&span);
assert_eq!(formatted, "1y 1month 15d 7h 26m 24s 123ms 456us 789ns");
let dur = humantime::parse_duration(&formatted).unwrap();
let expected =
std::time::Duration::new(60 * 60 * 24 * 411, 123_456_789);
assert_eq!(dur, expected);
}
#[test]
fn from_str() {
let p = |s: &str| -> Result<Span, Error> { s.parse() };
insta::assert_snapshot!(
p("1 day").unwrap(),
@"P1D",
);
insta::assert_snapshot!(
p("+1 day").unwrap(),
@"P1D",
);
insta::assert_snapshot!(
p("-1 day").unwrap(),
@"-P1D",
);
insta::assert_snapshot!(
p("P1d").unwrap(),
@"P1D",
);
insta::assert_snapshot!(
p("+P1d").unwrap(),
@"P1D",
);
insta::assert_snapshot!(
p("-P1d").unwrap(),
@"-P1D",
);
insta::assert_snapshot!(
p("").unwrap_err(),
@r#"an empty string is not a valid duration in either the ISO 8601 format or Jiff's "friendly" format"#,
);
insta::assert_snapshot!(
p("+").unwrap_err(),
@r#"found nothing after sign `+`, which is not a valid duration in either the ISO 8601 format or Jiff's "friendly" format"#,
);
insta::assert_snapshot!(
p("-").unwrap_err(),
@r#"found nothing after sign `-`, which is not a valid duration in either the ISO 8601 format or Jiff's "friendly" format"#,
);
}
#[test]
fn serde_deserialize() {
let p = |s: &str| -> Result<Span, serde_json::Error> {
serde_json::from_str(&alloc::format!("\"{s}\""))
};
insta::assert_snapshot!(
p("1 day").unwrap(),
@"P1D",
);
insta::assert_snapshot!(
p("+1 day").unwrap(),
@"P1D",
);
insta::assert_snapshot!(
p("-1 day").unwrap(),
@"-P1D",
);
insta::assert_snapshot!(
p("P1d").unwrap(),
@"P1D",
);
insta::assert_snapshot!(
p("+P1d").unwrap(),
@"P1D",
);
insta::assert_snapshot!(
p("-P1d").unwrap(),
@"-P1D",
);
insta::assert_snapshot!(
p("").unwrap_err(),
@r#"an empty string is not a valid duration in either the ISO 8601 format or Jiff's "friendly" format at line 1 column 2"#,
);
insta::assert_snapshot!(
p("+").unwrap_err(),
@r#"found nothing after sign `+`, which is not a valid duration in either the ISO 8601 format or Jiff's "friendly" format at line 1 column 3"#,
);
insta::assert_snapshot!(
p("-").unwrap_err(),
@r#"found nothing after sign `-`, which is not a valid duration in either the ISO 8601 format or Jiff's "friendly" format at line 1 column 3"#,
);
}
#[test]
fn maximum_invariant_duration() {
let span = Span::new()
.weeks(b::SpanWeeks::MAX)
.days(b::SpanDays::MAX)
.hours(b::SpanHours::MAX)
.minutes(b::SpanMinutes::MAX)
.seconds(b::SpanSeconds::MAX)
.milliseconds(b::SpanMilliseconds::MAX)
.microseconds(b::SpanMicroseconds::MAX)
.nanoseconds(b::SpanNanoseconds::MAX);
let dur = span.to_invariant_duration();
assert_eq!(dur.as_secs(), 4_426_974_863_236);
assert_eq!(
dur,
SignedDuration::new(
1_229_715_239 * 60 * 60 + 47 * 60 + 16,
854_775_807
),
);
let sum = dur + dur;
assert_eq!(sum.as_secs(), 8_853_949_726_473);
assert_eq!(
sum,
SignedDuration::new(
2_459_430_479 * 60 * 60 + 34 * 60 + 33,
709_551_614,
),
);
}
#[test]
fn minimum_invariant_duration() {
let span = Span::new()
.weeks(b::SpanWeeks::MIN)
.days(b::SpanDays::MIN)
.hours(b::SpanHours::MIN)
.minutes(b::SpanMinutes::MIN)
.seconds(b::SpanSeconds::MIN)
.milliseconds(b::SpanMilliseconds::MIN)
.microseconds(b::SpanMicroseconds::MIN)
.nanoseconds(b::SpanNanoseconds::MIN);
let dur = span.to_invariant_duration();
assert_eq!(dur.as_secs(), -4_426_974_863_236);
assert_eq!(
dur,
-SignedDuration::new(
1_229_715_239 * 60 * 60 + 47 * 60 + 16,
854_775_807
),
);
let sum = dur + dur;
assert_eq!(sum.as_secs(), -8_853_949_726_473);
assert_eq!(
sum,
-SignedDuration::new(
2_459_430_479 * 60 * 60 + 34 * 60 + 33,
709_551_614,
),
);
}
}