use crate::{
builtins::core::{PlainDateTime, PlainTime},
error::ErrorMessage,
iso::{IsoDateTime, IsoTime},
options::{
Overflow, RelativeTo, ResolvedRoundingOptions, RoundingIncrement, RoundingOptions,
ToStringRoundingOptions, Unit, UnitGroup,
},
parsers::{FormattableDateDuration, FormattableDuration, FormattableTimeDuration, Precision},
primitive::FiniteF64,
provider::TimeZoneProvider,
temporal_assert, Sign, TemporalError, TemporalResult, NS_PER_DAY,
};
use alloc::string::String;
use core::{cmp::Ordering, str::FromStr};
use ixdtf::{
encoding::Utf8, parsers::IsoDurationParser, records::Fraction, records::TimeDurationRecord,
};
use normalized::InternalDurationRecord;
use num_traits::Euclid;
use self::normalized::TimeDuration;
mod date;
pub(crate) mod normalized;
#[cfg(test)]
mod tests;
#[doc(inline)]
pub use date::DateDuration;
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct PartialDuration {
pub years: Option<i64>,
pub months: Option<i64>,
pub weeks: Option<i64>,
pub days: Option<i64>,
pub hours: Option<i64>,
pub minutes: Option<i64>,
pub seconds: Option<i64>,
pub milliseconds: Option<i64>,
pub microseconds: Option<i128>,
pub nanoseconds: Option<i128>,
}
impl Default for PartialDuration {
fn default() -> Self {
Self::empty()
}
}
impl PartialDuration {
pub const fn empty() -> Self {
Self {
years: None,
months: None,
weeks: None,
days: None,
hours: None,
minutes: None,
seconds: None,
milliseconds: None,
microseconds: None,
nanoseconds: None,
}
}
pub const fn with_years(mut self, years: i64) -> Self {
self.years = Some(years);
self
}
pub const fn with_months(mut self, months: i64) -> Self {
self.months = Some(months);
self
}
pub const fn with_weeks(mut self, weeks: i64) -> Self {
self.weeks = Some(weeks);
self
}
pub const fn with_days(mut self, days: i64) -> Self {
self.days = Some(days);
self
}
pub const fn with_hours(mut self, hours: i64) -> Self {
self.hours = Some(hours);
self
}
pub const fn with_minutes(mut self, minutes: i64) -> Self {
self.minutes = Some(minutes);
self
}
pub const fn with_seconds(mut self, seconds: i64) -> Self {
self.seconds = Some(seconds);
self
}
pub const fn with_milliseconds(mut self, milliseconds: i64) -> Self {
self.milliseconds = Some(milliseconds);
self
}
pub const fn with_microseconds(mut self, microseconds: i128) -> Self {
self.microseconds = Some(microseconds);
self
}
pub const fn with_nanoseconds(mut self, nanoseconds: i128) -> Self {
self.nanoseconds = Some(nanoseconds);
self
}
}
impl PartialDuration {
#[inline]
#[must_use]
pub fn is_empty(&self) -> bool {
self == &Self::default()
}
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, PartialEq, PartialOrd)]
pub struct Duration {
pub(crate) sign: Sign,
pub(crate) years: u32,
pub(crate) months: u32,
pub(crate) weeks: u32,
pub(crate) days: u64,
pub(crate) hours: u64,
pub(crate) minutes: u64,
pub(crate) seconds: u64,
pub(crate) milliseconds: u64,
pub(crate) microseconds: u128,
pub(crate) nanoseconds: u128,
}
impl Default for Duration {
fn default() -> Self {
Self {
sign: Sign::Zero,
years: 0,
months: 0,
weeks: 0,
days: 0,
hours: 0,
minutes: 0,
seconds: 0,
milliseconds: 0,
microseconds: 0,
nanoseconds: 0,
}
}
}
impl core::fmt::Display for Duration {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
let string = self.as_temporal_string(ToStringRoundingOptions::default());
debug_assert!(
string.is_ok(),
"Duration must return a valid string with default options."
);
f.write_str(&string.map_err(|_| Default::default())?)
}
}
impl TryFrom<PartialDuration> for Duration {
type Error = TemporalError;
fn try_from(partial: PartialDuration) -> Result<Self, Self::Error> {
Duration::from_partial_duration(partial)
}
}
#[cfg(test)]
impl Duration {
pub(crate) fn from_hours(value: i64) -> Self {
Self {
sign: Sign::from(value.signum() as i8),
hours: value.saturating_abs() as u64,
..Default::default()
}
}
}
impl Duration {
#[allow(clippy::too_many_arguments)]
pub(crate) const fn new_unchecked(
sign: Sign,
years: u32,
months: u32,
weeks: u32,
days: u64,
hours: u64,
minutes: u64,
seconds: u64,
milliseconds: u64,
microseconds: u128,
nanoseconds: u128,
) -> Self {
Self {
sign,
years,
months,
weeks,
days,
hours,
minutes,
seconds,
#[cfg(feature = "float64_representable_durations")]
milliseconds: milliseconds as f64 as u64,
#[cfg(feature = "float64_representable_durations")]
microseconds: microseconds as f64 as u128,
#[cfg(feature = "float64_representable_durations")]
nanoseconds: nanoseconds as f64 as u128,
#[cfg(not(feature = "float64_representable_durations"))]
milliseconds,
#[cfg(not(feature = "float64_representable_durations"))]
microseconds,
#[cfg(not(feature = "float64_representable_durations"))]
nanoseconds,
}
}
#[inline]
pub(crate) fn from_internal(
duration_record: InternalDurationRecord,
largest_unit: Unit,
) -> TemporalResult<Self> {
let mut days = 0;
let mut hours = 0;
let mut minutes = 0;
let mut seconds = 0;
let mut milliseconds = 0;
let mut microseconds = 0;
let sign = duration_record
.normalized_time_duration()
.sign()
.as_sign_multiplier();
let mut nanoseconds = duration_record.normalized_time_duration().0.abs();
match largest_unit {
Unit::Year | Unit::Month | Unit::Week | Unit::Day => {
(microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000);
(milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000);
(seconds, milliseconds) = milliseconds.div_rem_euclid(&1_000);
(minutes, seconds) = seconds.div_rem_euclid(&60);
(hours, minutes) = minutes.div_rem_euclid(&60);
(days, hours) = hours.div_rem_euclid(&24);
}
Unit::Hour => {
(microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000);
(milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000);
(seconds, milliseconds) = milliseconds.div_rem_euclid(&1_000);
(minutes, seconds) = seconds.div_rem_euclid(&60);
(hours, minutes) = minutes.div_rem_euclid(&60);
}
Unit::Minute => {
(microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000);
(milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000);
(seconds, milliseconds) = milliseconds.div_rem_euclid(&1_000);
(minutes, seconds) = seconds.div_rem_euclid(&60);
}
Unit::Second => {
(microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000);
(milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000);
(seconds, milliseconds) = milliseconds.div_rem_euclid(&1_000);
}
Unit::Millisecond => {
(microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000);
(milliseconds, microseconds) = microseconds.div_rem_euclid(&1_000);
}
Unit::Microsecond => {
(microseconds, nanoseconds) = nanoseconds.div_rem_euclid(&1_000);
}
_ => temporal_assert!(largest_unit == Unit::Nanosecond),
}
Duration::new(
duration_record.date().years,
duration_record.date().months,
duration_record.date().weeks,
duration_record.date().days + days as i64 * sign as i64,
hours as i64 * sign as i64,
minutes as i64 * sign as i64,
seconds as i64 * sign as i64,
milliseconds as i64 * sign as i64,
microseconds * sign as i128,
nanoseconds * sign as i128,
)
}
#[inline]
pub(crate) fn to_normalized(self) -> TimeDuration {
TimeDuration::from_duration(&self)
}
#[inline]
#[must_use]
pub(crate) fn fields_signum(&self) -> [i64; 10] {
[
self.years().signum(),
self.months().signum(),
self.weeks().signum(),
self.days().signum(),
self.hours().signum(),
self.minutes().signum(),
self.seconds().signum(),
self.milliseconds().signum(),
self.microseconds().signum() as i64,
self.nanoseconds().signum() as i64,
]
}
#[inline]
pub(crate) fn default_largest_unit(&self) -> Unit {
self.fields_signum()
.iter()
.enumerate()
.find(|x| x.1 != &0)
.map(|x| Unit::from(10 - x.0))
.unwrap_or(Unit::Nanosecond)
}
pub(crate) fn to_internal_duration_record(self) -> InternalDurationRecord {
let date_duration =
DateDuration::new_unchecked(self.years(), self.months(), self.weeks(), self.days());
let time_duration = TimeDuration::from_components(
self.hours(),
self.minutes(),
self.seconds(),
self.milliseconds(),
self.microseconds(),
self.nanoseconds(),
);
InternalDurationRecord::combine(date_duration, time_duration)
}
#[allow(clippy::wrong_self_convention)]
pub(crate) fn to_date_duration_record_without_time(&self) -> TemporalResult<DateDuration> {
let internal_duration = InternalDurationRecord::from_duration_with_24_hour_days(self)?;
internal_duration.to_date_duration_record_without_time()
}
}
impl Duration {
#[allow(clippy::too_many_arguments)]
pub fn new(
years: i64,
months: i64,
weeks: i64,
days: i64,
hours: i64,
minutes: i64,
seconds: i64,
milliseconds: i64,
microseconds: i128,
nanoseconds: i128,
) -> TemporalResult<Self> {
if !is_valid_duration(
years,
months,
weeks,
days,
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
) {
return Err(TemporalError::range().with_enum(ErrorMessage::DurationNotValid));
}
let sign = duration_sign(&[
years,
months,
weeks,
days,
hours,
minutes,
seconds,
milliseconds,
microseconds.signum() as i64,
nanoseconds.signum() as i64,
]);
Ok(Duration::new_unchecked(
sign,
years.saturating_abs() as u32,
months.saturating_abs() as u32,
weeks.saturating_abs() as u32,
days.unsigned_abs(),
hours.unsigned_abs(),
minutes.unsigned_abs(),
seconds.unsigned_abs(),
milliseconds.unsigned_abs(),
microseconds.unsigned_abs(),
nanoseconds.unsigned_abs(),
))
}
pub fn from_partial_duration(partial: PartialDuration) -> TemporalResult<Self> {
if partial == PartialDuration::default() {
return Err(TemporalError::r#type()
.with_message("PartialDuration cannot have all empty fields."));
}
Self::new(
partial.years.unwrap_or_default(),
partial.months.unwrap_or_default(),
partial.weeks.unwrap_or_default(),
partial.days.unwrap_or_default(),
partial.hours.unwrap_or_default(),
partial.minutes.unwrap_or_default(),
partial.seconds.unwrap_or_default(),
partial.milliseconds.unwrap_or_default(),
partial.microseconds.unwrap_or_default(),
partial.nanoseconds.unwrap_or_default(),
)
}
pub fn from_utf8(s: &[u8]) -> TemporalResult<Self> {
let parse_record = IsoDurationParser::<Utf8>::from_utf8(s).parse()?;
fn fraction_to_unadjusted_ns(fraction: Option<Fraction>) -> Result<u32, TemporalError> {
if let Some(fraction) = fraction {
fraction.to_nanoseconds().ok_or(
TemporalError::range()
.with_enum(ErrorMessage::FractionalTimeMoreThanNineDigits),
)
} else {
Ok(0)
}
}
let (hours, minutes, seconds, millis, micros, nanos) = match parse_record.time {
Some(TimeDurationRecord::Hours { hours, fraction }) => {
let unadjusted_fraction = fraction_to_unadjusted_ns(fraction)? as u64;
let fractional_hours_ns = unadjusted_fraction * 3600;
let minutes = fractional_hours_ns.div_euclid(60 * 1_000_000_000);
let fractional_minutes_ns = fractional_hours_ns.rem_euclid(60 * 1_000_000_000);
let seconds = fractional_minutes_ns.div_euclid(1_000_000_000);
let fractional_seconds = fractional_minutes_ns.rem_euclid(1_000_000_000);
let milliseconds = fractional_seconds.div_euclid(1_000_000);
let rem = fractional_seconds.rem_euclid(1_000_000);
let microseconds = rem.div_euclid(1_000);
let nanoseconds = rem.rem_euclid(1_000);
(
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
)
}
Some(TimeDurationRecord::Minutes {
hours,
minutes,
fraction,
}) => {
let unadjusted_fraction = fraction_to_unadjusted_ns(fraction)? as u64;
let fractional_minutes_ns = unadjusted_fraction * 60;
let seconds = fractional_minutes_ns.div_euclid(1_000_000_000);
let fractional_seconds = fractional_minutes_ns.rem_euclid(1_000_000_000);
let milliseconds = fractional_seconds.div_euclid(1_000_000);
let rem = fractional_seconds.rem_euclid(1_000_000);
let microseconds = rem.div_euclid(1_000);
let nanoseconds = rem.rem_euclid(1_000);
(
hours,
minutes,
seconds,
milliseconds,
microseconds,
nanoseconds,
)
}
Some(TimeDurationRecord::Seconds {
hours,
minutes,
seconds,
fraction,
}) => {
let ns = fraction_to_unadjusted_ns(fraction)?;
let milliseconds = ns.div_euclid(1_000_000);
let rem = ns.rem_euclid(1_000_000);
let microseconds = rem.div_euclid(1_000);
let nanoseconds = rem.rem_euclid(1_000);
(
hours,
minutes,
seconds,
milliseconds as u64,
microseconds as u64,
nanoseconds as u64,
)
}
None => (0, 0, 0, 0, 0, 0),
};
let (years, months, weeks, days) = if let Some(date) = parse_record.date {
(date.years, date.months, date.weeks, date.days)
} else {
(0, 0, 0, 0)
};
let sign = parse_record.sign as i64;
Self::new(
years as i64 * sign,
months as i64 * sign,
weeks as i64 * sign,
days as i64 * sign,
hours as i64 * sign,
minutes as i64 * sign,
seconds as i64 * sign,
millis as i64 * sign,
micros as i128 * sign as i128,
nanos as i128 * sign as i128,
)
}
#[inline]
#[must_use]
pub fn is_time_within_range(&self) -> bool {
self.hours < 24
&& self.minutes < 60
&& self.seconds < 60
&& self.milliseconds < 1000
&& self.microseconds < 1000
&& self.nanoseconds < 1000
}
#[inline]
pub fn compare_with_provider(
&self,
other: &Duration,
relative_to: Option<RelativeTo>,
provider: &(impl TimeZoneProvider + ?Sized),
) -> TemporalResult<Ordering> {
if self == other {
return Ok(Ordering::Equal);
}
let largest_unit_1 = self.default_largest_unit();
let largest_unit_2 = other.default_largest_unit();
let duration_one = self.to_internal_duration_record();
let duration_two = other.to_internal_duration_record();
if let Some(RelativeTo::ZonedDateTime(zdt)) = relative_to.as_ref() {
if largest_unit_1.is_date_unit() || largest_unit_2.is_date_unit() {
let after1 =
zdt.add_zoned_date_time(duration_one, Overflow::Constrain, provider)?;
let after2 =
zdt.add_zoned_date_time(duration_two, Overflow::Constrain, provider)?;
return Ok(after1.cmp(&after2));
}
}
let (days1, days2) =
if largest_unit_1.is_calendar_unit() || largest_unit_2.is_calendar_unit() {
let Some(RelativeTo::PlainDate(pdt)) = relative_to.as_ref() else {
return Err(TemporalError::range());
};
let days1 = self.date().days(pdt)?;
let days2 = other.date().days(pdt)?;
(days1, days2)
} else {
(self.date().days, other.date().days)
};
let time_duration_1 = self.to_normalized().add_days(days1)?;
let time_duration_2 = other.to_normalized().add_days(days2)?;
Ok(time_duration_1.cmp(&time_duration_2))
}
}
impl Duration {
#[inline]
#[must_use]
pub fn date(&self) -> DateDuration {
DateDuration::from(self)
}
#[inline]
#[must_use]
pub const fn years(&self) -> i64 {
self.years as i64 * self.sign.as_sign_multiplier() as i64
}
#[inline]
#[must_use]
pub const fn months(&self) -> i64 {
self.months as i64 * self.sign.as_sign_multiplier() as i64
}
#[inline]
#[must_use]
pub const fn weeks(&self) -> i64 {
self.weeks as i64 * self.sign.as_sign_multiplier() as i64
}
#[inline]
#[must_use]
pub const fn days(&self) -> i64 {
self.days as i64 * self.sign.as_sign_multiplier() as i64
}
#[inline]
#[must_use]
pub const fn hours(&self) -> i64 {
self.hours as i64 * self.sign.as_sign_multiplier() as i64
}
#[inline]
#[must_use]
pub const fn minutes(&self) -> i64 {
self.minutes as i64 * self.sign.as_sign_multiplier() as i64
}
#[inline]
#[must_use]
pub const fn seconds(&self) -> i64 {
self.seconds as i64 * self.sign.as_sign_multiplier() as i64
}
#[inline]
#[must_use]
pub const fn milliseconds(&self) -> i64 {
self.milliseconds as i64 * self.sign.as_sign_multiplier() as i64
}
#[inline]
#[must_use]
pub const fn microseconds(&self) -> i128 {
self.microseconds as i128 * self.sign.as_sign_multiplier() as i128
}
#[inline]
#[must_use]
pub const fn nanoseconds(&self) -> i128 {
self.nanoseconds as i128 * self.sign.as_sign_multiplier() as i128
}
}
impl Duration {
#[inline]
#[must_use]
pub fn sign(&self) -> Sign {
self.sign
}
#[inline]
#[must_use]
pub fn is_zero(&self) -> bool {
self.sign() == Sign::Zero
}
#[inline]
#[must_use]
pub fn negated(&self) -> Self {
Self {
sign: self.sign.negate(),
..*self
}
}
#[inline]
#[must_use]
pub fn abs(&self) -> Self {
Self {
sign: if self.sign == Sign::Zero {
Sign::Zero
} else {
Sign::Positive
},
..*self
}
}
#[inline]
pub fn add(&self, other: &Self) -> TemporalResult<Self> {
let largest_unit_one = self.default_largest_unit();
let largest_unit_two = other.default_largest_unit();
let largest_unit = largest_unit_one.max(largest_unit_two);
if largest_unit.is_calendar_unit() {
return Err(TemporalError::range().with_message(
"Largest unit cannot be a calendar unit when adding two durations.",
));
}
let d1 = InternalDurationRecord::from_duration_with_24_hour_days(self)?;
let d2 = InternalDurationRecord::from_duration_with_24_hour_days(other)?;
let time_result = (d1.normalized_time_duration() + d2.normalized_time_duration())?;
let result = InternalDurationRecord::combine(DateDuration::default(), time_result);
Duration::from_internal(result, largest_unit)
}
#[inline]
pub fn subtract(&self, other: &Self) -> TemporalResult<Self> {
self.add(&other.negated())
}
#[inline]
pub fn round_with_provider(
&self,
options: RoundingOptions,
relative_to: Option<RelativeTo>,
provider: &(impl TimeZoneProvider + ?Sized),
) -> TemporalResult<Self> {
let rounding_increment = options.increment.unwrap_or_default();
let rounding_mode = options.rounding_mode.unwrap_or_default();
let smallest_unit = options.smallest_unit;
UnitGroup::DateTime.validate_unit(smallest_unit, None)?;
let smallest_unit = smallest_unit.unwrap_or(Unit::Nanosecond);
let existing_largest_unit = self.default_largest_unit();
let default_largest_unit = Unit::larger(existing_largest_unit, smallest_unit)?;
let largest_unit = match options.largest_unit {
Some(Unit::Auto) | None => default_largest_unit,
Some(unit) => unit,
};
if options.largest_unit.is_none() && options.smallest_unit.is_none() {
return Err(TemporalError::range()
.with_message("smallestUnit and largestUnit cannot both be None."));
}
if Unit::larger(largest_unit, smallest_unit)? != largest_unit {
return Err(
TemporalError::range().with_message("smallestUnit is larger than largestUnit.")
);
}
let maximum = smallest_unit.to_maximum_rounding_increment();
if let Some(maximum) = maximum {
rounding_increment.validate(maximum.into(), false)?;
}
if rounding_increment > RoundingIncrement::ONE
&& largest_unit != smallest_unit
&& smallest_unit.is_date_unit()
{
return Err(TemporalError::range().with_message(
"roundingIncrement > 1 and largest_unit is not smallest_unit and smallest_unit is date",
));
}
let resolved_options = ResolvedRoundingOptions {
largest_unit,
smallest_unit,
increment: rounding_increment,
rounding_mode,
};
match relative_to {
Some(RelativeTo::ZonedDateTime(zoned_relative_to)) => {
let internal_duration = self.to_internal_duration_record();
let target_epoch_ns = zoned_relative_to.add_zoned_date_time(
internal_duration,
Overflow::Constrain,
provider,
)?;
let internal = zoned_relative_to.diff_with_rounding(
&target_epoch_ns,
resolved_options,
provider,
)?;
let mut largest_unit = resolved_options.largest_unit;
if resolved_options.largest_unit.is_date_unit() {
largest_unit = Unit::Hour;
}
return Duration::from_internal(internal, largest_unit);
}
Some(RelativeTo::PlainDate(plain_relative_to)) => {
let internal_duration =
InternalDurationRecord::from_duration_with_24_hour_days(self)?;
let (target_time_days, target_time) = PlainTime::default()
.add_normalized_time_duration(internal_duration.normalized_time_duration());
let calendar = plain_relative_to.calendar();
let date_duration =
internal_duration
.date()
.adjust(target_time_days, None, None)?;
let target_date = calendar.date_add(
&plain_relative_to.iso,
&date_duration,
Overflow::Constrain,
)?;
let iso_date_time =
IsoDateTime::new_unchecked(plain_relative_to.iso, IsoTime::default());
let target_date_time = IsoDateTime::new_unchecked(target_date.iso, target_time.iso);
let internal_duration =
PlainDateTime::new_unchecked(iso_date_time, calendar.clone())
.diff_dt_with_rounding(
&PlainDateTime::new_unchecked(target_date_time, calendar.clone()),
resolved_options,
)?;
return Duration::from_internal(internal_duration, resolved_options.largest_unit);
}
None => {}
}
if existing_largest_unit.is_calendar_unit()
|| resolved_options.largest_unit.is_calendar_unit()
{
return Err(TemporalError::range().with_message(
"largestUnit when rounding Duration was not the largest provided unit",
));
}
temporal_assert!(!resolved_options.smallest_unit.is_calendar_unit());
let internal_duration = InternalDurationRecord::from_duration_with_24_hour_days(self)?;
let internal_duration = if resolved_options.smallest_unit == Unit::Day {
let days = internal_duration
.normalized_time_duration()
.round_to_fractional_days(
resolved_options.increment,
resolved_options.rounding_mode,
)?;
let date = DateDuration::new(0, 0, 0, days)?;
InternalDurationRecord::new(date, TimeDuration::default())?
} else {
let time_duration = internal_duration
.normalized_time_duration()
.round(resolved_options)?;
InternalDurationRecord::new(DateDuration::default(), time_duration)?
};
Duration::from_internal(internal_duration, resolved_options.largest_unit)
}
pub fn total_with_provider(
&self,
unit: Unit,
relative_to: Option<RelativeTo>,
provider: &(impl TimeZoneProvider + ?Sized),
) -> TemporalResult<FiniteF64> {
match relative_to {
Some(RelativeTo::ZonedDateTime(zoned_datetime)) => {
let internal_duration = self.to_internal_duration_record();
let target_epoch_ns = zoned_datetime.add_zoned_date_time(
internal_duration,
Overflow::Constrain,
provider,
)?;
let total = zoned_datetime.diff_with_total(&target_epoch_ns, unit, provider)?;
Ok(total)
}
Some(RelativeTo::PlainDate(plain_date)) => {
let (balanced_days, time) =
PlainTime::default().add_normalized_time_duration(self.to_normalized());
let date_duration = DateDuration::new(
self.years(),
self.months(),
self.weeks(),
self.days()
.checked_add(balanced_days)
.ok_or(TemporalError::range())?,
)?;
let target_date = plain_date.calendar().date_add(
&plain_date.iso,
&date_duration,
Overflow::Constrain,
)?;
let iso_date_time = IsoDateTime::new_unchecked(plain_date.iso, IsoTime::default());
let target_date_time = IsoDateTime::new_unchecked(target_date.iso, time.iso);
let plain_dt =
PlainDateTime::new_unchecked(iso_date_time, plain_date.calendar().clone());
let total = plain_dt.diff_dt_with_total(
&PlainDateTime::new_unchecked(target_date_time, plain_date.calendar().clone()),
unit,
)?;
Ok(total)
}
None => {
let largest_unit = self.default_largest_unit();
if largest_unit.is_calendar_unit() || unit.is_calendar_unit() {
return Err(TemporalError::range());
}
let internal = InternalDurationRecord::from_duration_with_24_hour_days(self)?;
let total = internal.normalized_time_duration().total(unit)?;
Ok(total)
}
}
}
pub fn as_temporal_string(&self, options: ToStringRoundingOptions) -> TemporalResult<String> {
if options.smallest_unit == Some(Unit::Hour) || options.smallest_unit == Some(Unit::Minute)
{
return Err(TemporalError::range().with_message(
"string rounding options cannot have hour or minute smallest unit.",
));
}
let resolved_options = options.resolve()?;
if resolved_options.smallest_unit == Unit::Nanosecond
&& resolved_options.increment == RoundingIncrement::ONE
{
let duration = duration_to_formattable(self, resolved_options.precision)?;
return Ok(duration.to_string());
}
let rounding_options = ResolvedRoundingOptions::from_to_string_options(&resolved_options);
let largest = self.default_largest_unit();
let internal_duration = self.to_internal_duration_record();
let time_duration = internal_duration
.normalized_time_duration()
.round(rounding_options)?;
let internal_duration =
InternalDurationRecord::combine(internal_duration.date(), time_duration);
let rounded_largest_unit = largest.max(Unit::Second);
let rounded = Self::from_internal(internal_duration, rounded_largest_unit)?;
Ok(duration_to_formattable(&rounded, resolved_options.precision)?.to_string())
}
}
pub fn duration_to_formattable(
duration: &Duration,
precision: Precision,
) -> TemporalResult<FormattableDuration> {
let sign = duration.sign();
let duration = duration.abs();
let date = duration.years() + duration.months() + duration.weeks() + duration.days();
let date = if date != 0 {
Some(FormattableDateDuration {
years: duration.years() as u32,
months: duration.months() as u32,
weeks: duration.weeks() as u32,
days: duration.days() as u64,
})
} else {
None
};
let hours = duration.hours().abs();
let minutes = duration.minutes().abs();
let time = TimeDuration::from_components(
0,
0,
duration.seconds(),
duration.milliseconds(),
duration.microseconds(),
duration.nanoseconds(),
);
let seconds = time.seconds().unsigned_abs();
let subseconds = time.subseconds().unsigned_abs();
let time = Some(FormattableTimeDuration::Seconds(
hours as u64,
minutes as u64,
seconds,
Some(subseconds),
));
Ok(FormattableDuration {
precision,
sign,
date,
time,
})
}
const TWO_POWER_FIFTY_THREE: i128 = 9_007_199_254_740_992;
const MAX_SAFE_NS_PRECISION: i128 = TWO_POWER_FIFTY_THREE * 1_000_000_000;
#[inline]
#[must_use]
#[allow(clippy::too_many_arguments)]
pub(crate) fn is_valid_duration(
years: i64,
months: i64,
weeks: i64,
days: i64,
hours: i64,
minutes: i64,
seconds: i64,
milliseconds: i64,
microseconds: i128,
nanoseconds: i128,
) -> bool {
let set = [
years,
months,
weeks,
days,
hours,
minutes,
seconds,
milliseconds,
microseconds.signum() as i64,
nanoseconds.signum() as i64,
];
let sign = duration_sign(&set);
for v in set {
if v < 0 && sign == Sign::Positive {
return false;
}
if v > 0 && sign == Sign::Negative {
return false;
}
}
if years.saturating_abs() > u32::MAX as i64 {
return false;
};
if months.saturating_abs() > u32::MAX as i64 {
return false;
};
if weeks.saturating_abs() > u32::MAX as i64 {
return false;
};
let seconds = seconds as f64 as i64;
let milliseconds = milliseconds as f64 as i64;
let microseconds = microseconds as f64 as i128;
let nanoseconds = nanoseconds as f64 as i128;
let normalized_nanoseconds = (days as i128 * NS_PER_DAY as i128)
+ (hours as i128) * 3_600_000_000_000
+ minutes as i128 * 60_000_000_000
+ seconds as i128 * 1_000_000_000;
let normalized_subseconds_parts = (milliseconds as i128)
.saturating_mul(1_000_000)
.saturating_add(microseconds.saturating_mul(1_000))
.saturating_add(nanoseconds);
let total_normalized_seconds =
normalized_nanoseconds.saturating_add(normalized_subseconds_parts);
if total_normalized_seconds.saturating_abs() >= MAX_SAFE_NS_PRECISION {
return false;
}
true
}
#[inline]
#[must_use]
pub(crate) fn duration_sign(set: &[i64]) -> Sign {
for v in set {
match (*v).cmp(&0) {
Ordering::Less => return Sign::Negative,
Ordering::Greater => return Sign::Positive,
_ => {}
}
}
Sign::Zero
}
impl From<DateDuration> for Duration {
fn from(value: DateDuration) -> Self {
Self {
sign: value.sign(),
years: value.years.unsigned_abs() as u32,
months: value.months.unsigned_abs() as u32,
weeks: value.weeks.unsigned_abs() as u32,
days: value.days.unsigned_abs(),
..Default::default()
}
}
}
impl FromStr for Duration {
type Err = TemporalError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::from_utf8(s.as_bytes())
}
}