mirror of https://github.com/boa-dev/boa.git
Kevin
10 months ago
committed by
GitHub
7 changed files with 1285 additions and 971 deletions
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,461 @@
|
||||
//! Implementation of a `DateDuration`
|
||||
|
||||
use crate::{ |
||||
components::{ |
||||
calendar::CalendarProtocol, duration::TimeDuration, tz::TzProtocol, Date, DateTime, |
||||
Duration, ZonedDateTime, |
||||
}, |
||||
options::{ArithmeticOverflow, TemporalRoundingMode, TemporalUnit}, |
||||
utils, TemporalError, TemporalResult, NS_PER_DAY, |
||||
}; |
||||
|
||||
use std::any::Any; |
||||
|
||||
/// `DateDuration` represents the [date duration record][spec] of the `Duration.`
|
||||
///
|
||||
/// These fields are laid out in the [Temporal Proposal][field spec] as 64-bit floating point numbers.
|
||||
///
|
||||
/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-date-duration-records
|
||||
/// [field spec]: https://tc39.es/proposal-temporal/#sec-properties-of-temporal-duration-instances
|
||||
#[derive(Debug, Default, Clone, Copy)] |
||||
pub struct DateDuration { |
||||
pub(crate) years: f64, |
||||
pub(crate) months: f64, |
||||
pub(crate) weeks: f64, |
||||
pub(crate) days: f64, |
||||
} |
||||
|
||||
impl DateDuration { |
||||
/// Creates a new, non-validated `DateDuration`.
|
||||
#[inline] |
||||
#[must_use] |
||||
pub(crate) const fn new_unchecked(years: f64, months: f64, weeks: f64, days: f64) -> Self { |
||||
Self { |
||||
years, |
||||
months, |
||||
weeks, |
||||
days, |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl DateDuration { |
||||
/// Creates a new `DateDuration` with provided values.
|
||||
pub fn new(years: f64, months: f64, weeks: f64, days: f64) -> TemporalResult<Self> { |
||||
let result = Self::new_unchecked(years, months, weeks, days); |
||||
if !super::is_valid_duration(&result.into_iter().collect()) { |
||||
return Err(TemporalError::range().with_message("Invalid DateDuration.")); |
||||
} |
||||
Ok(result) |
||||
} |
||||
|
||||
/// Returns a `PartialDateDuration` with all fields set to `NaN`.
|
||||
#[must_use] |
||||
pub const fn partial() -> Self { |
||||
Self { |
||||
years: f64::NAN, |
||||
months: f64::NAN, |
||||
weeks: f64::NAN, |
||||
days: f64::NAN, |
||||
} |
||||
} |
||||
|
||||
/// Returns a new `DateDuration` representing the absolute value of the current.
|
||||
#[inline] |
||||
#[must_use] |
||||
pub fn abs(&self) -> Self { |
||||
Self { |
||||
years: self.years.abs(), |
||||
months: self.months.abs(), |
||||
weeks: self.weeks.abs(), |
||||
days: self.days.abs(), |
||||
} |
||||
} |
||||
|
||||
/// Returns the `[[years]]` value.
|
||||
#[must_use] |
||||
pub const fn years(&self) -> f64 { |
||||
self.years |
||||
} |
||||
|
||||
/// Returns the `[[months]]` value.
|
||||
#[must_use] |
||||
pub const fn months(&self) -> f64 { |
||||
self.months |
||||
} |
||||
|
||||
/// Returns the `[[weeks]]` value.
|
||||
#[must_use] |
||||
pub const fn weeks(&self) -> f64 { |
||||
self.weeks |
||||
} |
||||
|
||||
/// Returns the `[[days]]` value.
|
||||
#[must_use] |
||||
pub const fn days(&self) -> f64 { |
||||
self.days |
||||
} |
||||
|
||||
/// Returns the iterator for `DateDuration`
|
||||
#[must_use] |
||||
pub fn iter(&self) -> DateIter<'_> { |
||||
<&Self as IntoIterator>::into_iter(self) |
||||
} |
||||
} |
||||
|
||||
// ==== DateDuration Operations ====
|
||||
|
||||
impl DateDuration { |
||||
/// Rounds the current `DateDuration` returning a tuple of the rounded `DateDuration` and
|
||||
/// the `total` value of the smallest unit prior to rounding.
|
||||
#[allow(clippy::type_complexity, clippy::let_and_return)] |
||||
pub fn round<C: CalendarProtocol, Z: TzProtocol>( |
||||
&self, |
||||
additional_time: Option<TimeDuration>, |
||||
increment: f64, |
||||
unit: TemporalUnit, |
||||
rounding_mode: TemporalRoundingMode, |
||||
relative_targets: ( |
||||
Option<&Date<C>>, |
||||
Option<&ZonedDateTime<C, Z>>, |
||||
Option<&DateTime<C>>, |
||||
), |
||||
context: &mut dyn Any, |
||||
) -> TemporalResult<(Self, f64)> { |
||||
// 1. If plainRelativeTo is not present, set plainRelativeTo to undefined.
|
||||
let plain_relative_to = relative_targets.0; |
||||
// 2. If zonedRelativeTo is not present, set zonedRelativeTo to undefined.
|
||||
let zoned_relative_to = relative_targets.1; |
||||
// 3. If precalculatedPlainDateTime is not present, set precalculatedPlainDateTime to undefined.
|
||||
let _ = relative_targets.2; |
||||
|
||||
let mut fractional_days = match unit { |
||||
// 4. If unit is "year", "month", or "week", and plainRelativeTo is undefined, then
|
||||
TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week |
||||
if plain_relative_to.is_none() => |
||||
{ |
||||
// a. Throw a RangeError exception.
|
||||
return Err(TemporalError::range() |
||||
.with_message("plainRelativeTo canot be undefined with given TemporalUnit")); |
||||
} |
||||
// 5. If unit is one of "year", "month", "week", or "day", then
|
||||
TemporalUnit::Year | TemporalUnit::Month | TemporalUnit::Week | TemporalUnit::Day => { |
||||
// a. Let nanoseconds be TotalDurationNanoseconds(hours, minutes, seconds, milliseconds, microseconds, nanoseconds).
|
||||
let nanoseconds = additional_time.unwrap_or_default().as_nanos(); |
||||
|
||||
// b. If zonedRelativeTo is not undefined, then
|
||||
// i. Let intermediate be ? MoveRelativeZonedDateTime(zonedRelativeTo, years, months, weeks, days, precalculatedPlainDateTime).
|
||||
// ii. Let result be ? NanosecondsToDays(nanoseconds, intermediate).
|
||||
// iii. Let fractionalDays be days + result.[[Days]] + result.[[Nanoseconds]] / result.[[DayLength]].
|
||||
// c. Else,
|
||||
// i. Let fractionalDays be days + nanoseconds / nsPerDay.
|
||||
// d. Set days, hours, minutes, seconds, milliseconds, microseconds, and nanoseconds to 0.
|
||||
// e. Assert: fractionalSeconds is not used below.
|
||||
if zoned_relative_to.is_none() { |
||||
self.days + nanoseconds / NS_PER_DAY as f64 |
||||
} else { |
||||
// implementation of b: i-iii needed.
|
||||
return Err(TemporalError::range().with_message("Not yet implemented.")); |
||||
} |
||||
} |
||||
_ => { |
||||
return Err(TemporalError::range() |
||||
.with_message("Invalid TemporalUnit provided to DateDuration.round")) |
||||
} |
||||
}; |
||||
|
||||
// 7. let total be unset.
|
||||
// We begin matching against unit and return the remainder value.
|
||||
match unit { |
||||
// 8. If unit is "year", then
|
||||
TemporalUnit::Year => { |
||||
let plain_relative_to = plain_relative_to.expect("this must exist."); |
||||
// a. Let calendar be plainRelativeTo.[[Calendar]].
|
||||
let calendar = plain_relative_to.calendar(); |
||||
|
||||
// b. Let yearsDuration be ! CreateTemporalDuration(years, 0, 0, 0, 0, 0, 0, 0, 0, 0).
|
||||
let years = DateDuration::new_unchecked(self.years, 0.0, 0.0, 0.0); |
||||
let years_duration = Duration::new_unchecked(years, TimeDuration::default()); |
||||
|
||||
// c. If calendar is an Object, then
|
||||
// i. Let dateAdd be ? GetMethod(calendar, "dateAdd").
|
||||
// d. Else,
|
||||
// i. Let dateAdd be unused.
|
||||
|
||||
// e. Let yearsLater be ? AddDate(calendar, plainRelativeTo, yearsDuration, undefined, dateAdd).
|
||||
let years_later = plain_relative_to.contextual_add_date( |
||||
&years_duration, |
||||
ArithmeticOverflow::Constrain, |
||||
context, |
||||
)?; |
||||
|
||||
// f. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0).
|
||||
let years_months_weeks = Duration::new_unchecked( |
||||
Self::new_unchecked(self.years, self.months, self.weeks, 0.0), |
||||
TimeDuration::default(), |
||||
); |
||||
|
||||
// g. Let yearsMonthsWeeksLater be ? AddDate(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd).
|
||||
let years_months_weeks_later = plain_relative_to.contextual_add_date( |
||||
&years_months_weeks, |
||||
ArithmeticOverflow::Constrain, |
||||
context, |
||||
)?; |
||||
|
||||
// h. Let monthsWeeksInDays be DaysUntil(yearsLater, yearsMonthsWeeksLater).
|
||||
let months_weeks_in_days = years_later.days_until(&years_months_weeks_later); |
||||
|
||||
// i. Set plainRelativeTo to yearsLater.
|
||||
let plain_relative_to = years_later; |
||||
|
||||
// j. Set fractionalDays to fractionalDays + monthsWeeksInDays.
|
||||
fractional_days += f64::from(months_weeks_in_days); |
||||
|
||||
// k. Let isoResult be ! AddISODate(plainRelativeTo.[[ISOYear]]. plainRelativeTo.[[ISOMonth]], plainRelativeTo.[[ISODay]], 0, 0, 0, truncate(fractionalDays), "constrain").
|
||||
let iso_result = plain_relative_to.iso_date().add_iso_date( |
||||
&DateDuration::new_unchecked(0.0, 0.0, 0.0, fractional_days.trunc()), |
||||
ArithmeticOverflow::Constrain, |
||||
)?; |
||||
|
||||
// l. Let wholeDaysLater be ? CreateDate(isoResult.[[Year]], isoResult.[[Month]], isoResult.[[Day]], calendar).
|
||||
let whole_days_later = Date::new_unchecked(iso_result, calendar.clone()); |
||||
|
||||
// m. Let untilOptions be OrdinaryObjectCreate(null).
|
||||
// n. Perform ! CreateDataPropertyOrThrow(untilOptions, "largestUnit", "year").
|
||||
// o. Let timePassed be ? DifferenceDate(calendar, plainRelativeTo, wholeDaysLater, untilOptions).
|
||||
let time_passed = plain_relative_to.contextual_difference_date( |
||||
&whole_days_later, |
||||
TemporalUnit::Year, |
||||
context, |
||||
)?; |
||||
|
||||
// p. Let yearsPassed be timePassed.[[Years]].
|
||||
let years_passed = time_passed.date.years(); |
||||
|
||||
// q. Set years to years + yearsPassed.
|
||||
let years = self.years() + years_passed; |
||||
|
||||
// r. Let yearsDuration be ! CreateTemporalDuration(yearsPassed, 0, 0, 0, 0, 0, 0, 0, 0, 0).
|
||||
let years_duration = Duration::one_year(years_passed); |
||||
|
||||
// s. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, yearsDuration, dateAdd).
|
||||
// t. Set plainRelativeTo to moveResult.[[RelativeTo]].
|
||||
// u. Let daysPassed be moveResult.[[Days]].
|
||||
let (plain_relative_to, days_passed) = |
||||
plain_relative_to.move_relative_date(&years_duration, context)?; |
||||
|
||||
// v. Set fractionalDays to fractionalDays - daysPassed.
|
||||
fractional_days -= days_passed; |
||||
|
||||
// w. If fractionalDays < 0, let sign be -1; else, let sign be 1.
|
||||
let sign = if fractional_days < 0.0 { -1 } else { 1 }; |
||||
|
||||
// x. Let oneYear be ! CreateTemporalDuration(sign, 0, 0, 0, 0, 0, 0, 0, 0, 0).
|
||||
let one_year = Duration::one_year(f64::from(sign)); |
||||
|
||||
// y. Set moveResult to ? MoveRelativeDate(calendar, plainRelativeTo, oneYear, dateAdd).
|
||||
// z. Let oneYearDays be moveResult.[[Days]].
|
||||
let (_, one_year_days) = |
||||
plain_relative_to.move_relative_date(&one_year, context)?; |
||||
|
||||
// aa. Let fractionalYears be years + fractionalDays / abs(oneYearDays).
|
||||
let frac_years = years + (fractional_days / one_year_days.abs()); |
||||
|
||||
// ab. Set years to RoundNumberToIncrement(fractionalYears, increment, roundingMode).
|
||||
let rounded_years = |
||||
utils::round_number_to_increment(frac_years, increment, rounding_mode); |
||||
|
||||
// ac. Set total to fractionalYears.
|
||||
// ad. Set months and weeks to 0.
|
||||
let result = Self::new(rounded_years, 0f64, 0f64, 0f64)?; |
||||
Ok((result, frac_years)) |
||||
} |
||||
// 9. Else if unit is "month", then
|
||||
TemporalUnit::Month => { |
||||
// a. Let calendar be plainRelativeTo.[[Calendar]].
|
||||
let plain_relative_to = plain_relative_to.expect("this must exist."); |
||||
|
||||
// b. Let yearsMonths be ! CreateTemporalDuration(years, months, 0, 0, 0, 0, 0, 0, 0, 0).
|
||||
let years_months = Duration::from_date_duration(DateDuration::new_unchecked( |
||||
self.years(), |
||||
self.months(), |
||||
0.0, |
||||
0.0, |
||||
)); |
||||
|
||||
// c. If calendar is an Object, then
|
||||
// i. Let dateAdd be ? GetMethod(calendar, "dateAdd").
|
||||
// d. Else,
|
||||
// i. Let dateAdd be unused.
|
||||
|
||||
// e. Let yearsMonthsLater be ? AddDate(calendar, plainRelativeTo, yearsMonths, undefined, dateAdd).
|
||||
let years_months_later = plain_relative_to.contextual_add_date( |
||||
&years_months, |
||||
ArithmeticOverflow::Constrain, |
||||
context, |
||||
)?; |
||||
|
||||
// f. Let yearsMonthsWeeks be ! CreateTemporalDuration(years, months, weeks, 0, 0, 0, 0, 0, 0, 0).
|
||||
let years_months_weeks = Duration::from_date_duration(DateDuration::new_unchecked( |
||||
self.years(), |
||||
self.months(), |
||||
self.weeks(), |
||||
0.0, |
||||
)); |
||||
|
||||
// g. Let yearsMonthsWeeksLater be ? AddDate(calendar, plainRelativeTo, yearsMonthsWeeks, undefined, dateAdd).
|
||||
let years_months_weeks_later = plain_relative_to.contextual_add_date( |
||||
&years_months_weeks, |
||||
ArithmeticOverflow::Constrain, |
||||
context, |
||||
)?; |
||||
|
||||
// h. Let weeksInDays be DaysUntil(yearsMonthsLater, yearsMonthsWeeksLater).
|
||||
let weeks_in_days = years_months_later.days_until(&years_months_weeks_later); |
||||
|
||||
// i. Set plainRelativeTo to yearsMonthsLater.
|
||||
let plain_relative_to = years_months_later; |
||||
|
||||
// j. Set fractionalDays to fractionalDays + weeksInDays.
|
||||
fractional_days += f64::from(weeks_in_days); |
||||
|
||||
// k. If fractionalDays < 0, let sign be -1; else, let sign be 1.
|
||||
let sign = if fractional_days < 0.0 { -1f64 } else { 1f64 }; |
||||
|
||||
// l. Let oneMonth be ! CreateTemporalDuration(0, sign, 0, 0, 0, 0, 0, 0, 0, 0).
|
||||
let one_month = Duration::one_month(sign); |
||||
|
||||
// m. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneMonth, dateAdd).
|
||||
// n. Set plainRelativeTo to moveResult.[[RelativeTo]].
|
||||
// o. Let oneMonthDays be moveResult.[[Days]].
|
||||
let (mut plain_relative_to, mut one_month_days) = |
||||
plain_relative_to.move_relative_date(&one_month, context)?; |
||||
|
||||
let mut months = self.months; |
||||
// p. Repeat, while abs(fractionalDays) ≥ abs(oneMonthDays),
|
||||
while fractional_days.abs() >= one_month_days.abs() { |
||||
// i. Set months to months + sign.
|
||||
months += sign; |
||||
|
||||
// ii. Set fractionalDays to fractionalDays - oneMonthDays.
|
||||
fractional_days -= one_month_days; |
||||
|
||||
// iii. Set moveResult to ? MoveRelativeDate(calendar, plainRelativeTo, oneMonth, dateAdd).
|
||||
let move_result = plain_relative_to.move_relative_date(&one_month, context)?; |
||||
|
||||
// iv. Set plainRelativeTo to moveResult.[[RelativeTo]].
|
||||
plain_relative_to = move_result.0; |
||||
// v. Set oneMonthDays to moveResult.[[Days]].
|
||||
one_month_days = move_result.1; |
||||
} |
||||
|
||||
// q. Let fractionalMonths be months + fractionalDays / abs(oneMonthDays).
|
||||
let frac_months = months + fractional_days / one_month_days.abs(); |
||||
|
||||
// r. Set months to RoundNumberToIncrement(fractionalMonths, increment, roundingMode).
|
||||
let rounded_months = |
||||
utils::round_number_to_increment(frac_months, increment, rounding_mode); |
||||
|
||||
// s. Set total to fractionalMonths.
|
||||
// t. Set weeks to 0.
|
||||
let result = Self::new(self.years, rounded_months, 0f64, 0f64)?; |
||||
Ok((result, frac_months)) |
||||
} |
||||
// 10. Else if unit is "week", then
|
||||
TemporalUnit::Week => { |
||||
// a. Let calendar be plainRelativeTo.[[Calendar]].
|
||||
let plain_relative_to = plain_relative_to.expect("date must exist given Week"); |
||||
|
||||
// b. If fractionalDays < 0, let sign be -1; else, let sign be 1.
|
||||
let sign = if fractional_days < 0.0 { -1f64 } else { 1f64 }; |
||||
|
||||
// c. Let oneWeek be ! CreateTemporalDuration(0, 0, sign, 0, 0, 0, 0, 0, 0, 0).
|
||||
let one_week = Duration::one_week(sign); |
||||
|
||||
// d. If calendar is an Object, then
|
||||
// i. Let dateAdd be ? GetMethod(calendar, "dateAdd").
|
||||
// e. Else,
|
||||
// i. Let dateAdd be unused.
|
||||
|
||||
// f. Let moveResult be ? MoveRelativeDate(calendar, plainRelativeTo, oneWeek, dateAdd).
|
||||
// g. Set plainRelativeTo to moveResult.[[RelativeTo]].
|
||||
// h. Let oneWeekDays be moveResult.[[Days]].
|
||||
let (mut plain_relative_to, mut one_week_days) = |
||||
plain_relative_to.move_relative_date(&one_week, context)?; |
||||
|
||||
let mut weeks = self.weeks; |
||||
// i. Repeat, while abs(fractionalDays) ≥ abs(oneWeekDays),
|
||||
while fractional_days.abs() >= one_week_days.abs() { |
||||
// i. Set weeks to weeks + sign.
|
||||
weeks += sign; |
||||
|
||||
// ii. Set fractionalDays to fractionalDays - oneWeekDays.
|
||||
fractional_days -= one_week_days; |
||||
|
||||
// iii. Set moveResult to ? MoveRelativeDate(calendar, plainRelativeTo, oneWeek, dateAdd).
|
||||
let move_result = plain_relative_to.move_relative_date(&one_week, context)?; |
||||
|
||||
// iv. Set plainRelativeTo to moveResult.[[RelativeTo]].
|
||||
plain_relative_to = move_result.0; |
||||
// v. Set oneWeekDays to moveResult.[[Days]].
|
||||
one_week_days = move_result.1; |
||||
} |
||||
|
||||
// j. Let fractionalWeeks be weeks + fractionalDays / abs(oneWeekDays).
|
||||
let frac_weeks = weeks + fractional_days / one_week_days.abs(); |
||||
|
||||
// k. Set weeks to RoundNumberToIncrement(fractionalWeeks, increment, roundingMode).
|
||||
let rounded_weeks = |
||||
utils::round_number_to_increment(frac_weeks, increment, rounding_mode); |
||||
// l. Set total to fractionalWeeks.
|
||||
let result = Self::new(self.years, self.months, rounded_weeks, 0f64)?; |
||||
Ok((result, frac_weeks)) |
||||
} |
||||
// 11. Else if unit is "day", then
|
||||
TemporalUnit::Day => { |
||||
// a. Set days to RoundNumberToIncrement(fractionalDays, increment, roundingMode).
|
||||
let rounded_days = |
||||
utils::round_number_to_increment(fractional_days, increment, rounding_mode); |
||||
// b. Set total to fractionalDays.
|
||||
let result = Self::new(self.years, self.months, self.weeks, rounded_days)?; |
||||
Ok((result, fractional_days)) |
||||
} |
||||
_ => unreachable!("All other TemporalUnits were returned early as invalid."), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl<'a> IntoIterator for &'a DateDuration { |
||||
type Item = f64; |
||||
type IntoIter = DateIter<'a>; |
||||
|
||||
fn into_iter(self) -> Self::IntoIter { |
||||
DateIter { |
||||
date: self, |
||||
index: 0, |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// An iterator over the `DateDuration`
|
||||
#[derive(Debug)] |
||||
pub struct DateIter<'a> { |
||||
date: &'a DateDuration, |
||||
index: usize, |
||||
} |
||||
|
||||
impl Iterator for DateIter<'_> { |
||||
type Item = f64; |
||||
|
||||
fn next(&mut self) -> Option<Self::Item> { |
||||
let result = match self.index { |
||||
0 => Some(self.date.years), |
||||
1 => Some(self.date.months), |
||||
2 => Some(self.date.weeks), |
||||
3 => Some(self.date.days), |
||||
_ => None, |
||||
}; |
||||
self.index += 1; |
||||
result |
||||
} |
||||
} |
@ -0,0 +1,545 @@
|
||||
//! An implementation of `TimeDuration` and it's methods.
|
||||
|
||||
use crate::{ |
||||
options::{TemporalRoundingMode, TemporalUnit}, |
||||
utils, TemporalError, TemporalResult, |
||||
}; |
||||
|
||||
use super::is_valid_duration; |
||||
|
||||
/// `TimeDuration` represents the [Time Duration record][spec] of the `Duration.`
|
||||
///
|
||||
/// These fields are laid out in the [Temporal Proposal][field spec] as 64-bit floating point numbers.
|
||||
///
|
||||
/// [spec]: https://tc39.es/proposal-temporal/#sec-temporal-time-duration-records
|
||||
/// [field spec]: https://tc39.es/proposal-temporal/#sec-properties-of-temporal-duration-instances
|
||||
#[derive(Debug, Default, Clone, Copy)] |
||||
pub struct TimeDuration { |
||||
pub(crate) hours: f64, |
||||
pub(crate) minutes: f64, |
||||
pub(crate) seconds: f64, |
||||
pub(crate) milliseconds: f64, |
||||
pub(crate) microseconds: f64, |
||||
pub(crate) nanoseconds: f64, |
||||
} |
||||
// ==== TimeDuration Private API ====
|
||||
|
||||
impl TimeDuration { |
||||
/// Creates a new `TimeDuration`.
|
||||
#[must_use] |
||||
pub(crate) const fn new_unchecked( |
||||
hours: f64, |
||||
minutes: f64, |
||||
seconds: f64, |
||||
milliseconds: f64, |
||||
microseconds: f64, |
||||
nanoseconds: f64, |
||||
) -> Self { |
||||
Self { |
||||
hours, |
||||
minutes, |
||||
seconds, |
||||
milliseconds, |
||||
microseconds, |
||||
nanoseconds, |
||||
} |
||||
} |
||||
|
||||
/// Returns the current `TimeDuration` as nanoseconds.
|
||||
#[inline] |
||||
pub(crate) fn as_nanos(&self) -> f64 { |
||||
self.hours |
||||
.mul_add(60_f64, self.minutes) |
||||
.mul_add(60_f64, self.seconds) |
||||
.mul_add(1_000_f64, self.milliseconds) |
||||
.mul_add(1_000_f64, self.microseconds) |
||||
.mul_add(1_000_f64, self.nanoseconds) |
||||
} |
||||
|
||||
/// Abstract Operation 7.5.18 `BalancePossiblyInfiniteDuration ( days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds, largestUnit )`
|
||||
///
|
||||
/// This function will balance the current `TimeDuration`. It returns the balanced `day` and `TimeDuration` value.
|
||||
#[allow(clippy::too_many_arguments)] |
||||
pub(crate) fn balance_possibly_infinite_time_duration( |
||||
days: f64, |
||||
hours: f64, |
||||
minutes: f64, |
||||
seconds: f64, |
||||
milliseconds: f64, |
||||
microseconds: f64, |
||||
nanoseconds: f64, |
||||
largest_unit: TemporalUnit, |
||||
) -> TemporalResult<(f64, Option<Self>)> { |
||||
// 1. Set hours to hours + days × 24.
|
||||
let hours = hours + (days * 24f64); |
||||
|
||||
// 2. Set nanoseconds to TotalDurationNanoseconds(hours, minutes, seconds, milliseconds, microseconds, nanoseconds).
|
||||
let mut nanoseconds = Self::new_unchecked( |
||||
hours, |
||||
minutes, |
||||
seconds, |
||||
milliseconds, |
||||
microseconds, |
||||
nanoseconds, |
||||
) |
||||
.as_nanos(); |
||||
|
||||
// 3. Set days, hours, minutes, seconds, milliseconds, and microseconds to 0.
|
||||
let mut days = 0f64; |
||||
let mut hours = 0f64; |
||||
let mut minutes = 0f64; |
||||
let mut seconds = 0f64; |
||||
let mut milliseconds = 0f64; |
||||
let mut microseconds = 0f64; |
||||
|
||||
// 4. If nanoseconds < 0, let sign be -1; else, let sign be 1.
|
||||
let sign = if nanoseconds < 0f64 { -1 } else { 1 }; |
||||
// 5. Set nanoseconds to abs(nanoseconds).
|
||||
nanoseconds = nanoseconds.abs(); |
||||
|
||||
match largest_unit { |
||||
// 9. If largestUnit is "year", "month", "week", "day", or "hour", then
|
||||
TemporalUnit::Year |
||||
| TemporalUnit::Month |
||||
| TemporalUnit::Week |
||||
| TemporalUnit::Day |
||||
| TemporalUnit::Hour => { |
||||
// a. Set microseconds to floor(nanoseconds / 1000).
|
||||
microseconds = (nanoseconds / 1000f64).floor(); |
||||
// b. Set nanoseconds to nanoseconds modulo 1000.
|
||||
nanoseconds %= 1000f64; |
||||
|
||||
// c. Set milliseconds to floor(microseconds / 1000).
|
||||
milliseconds = (microseconds / 1000f64).floor(); |
||||
// d. Set microseconds to microseconds modulo 1000.
|
||||
microseconds %= 1000f64; |
||||
|
||||
// e. Set seconds to floor(milliseconds / 1000).
|
||||
seconds = (milliseconds / 1000f64).floor(); |
||||
// f. Set milliseconds to milliseconds modulo 1000.
|
||||
milliseconds %= 1000f64; |
||||
|
||||
// g. Set minutes to floor(seconds / 60).
|
||||
minutes = (seconds / 60f64).floor(); |
||||
// h. Set seconds to seconds modulo 60.
|
||||
seconds %= 60f64; |
||||
|
||||
// i. Set hours to floor(minutes / 60).
|
||||
hours = (minutes / 60f64).floor(); |
||||
// j. Set minutes to minutes modulo 60.
|
||||
minutes %= 60f64; |
||||
|
||||
// k. Set days to floor(hours / 24).
|
||||
days = (hours / 24f64).floor(); |
||||
// l. Set hours to hours modulo 24.
|
||||
hours %= 24f64; |
||||
} |
||||
// 10. Else if largestUnit is "minute", then
|
||||
TemporalUnit::Minute => { |
||||
// a. Set microseconds to floor(nanoseconds / 1000).
|
||||
// b. Set nanoseconds to nanoseconds modulo 1000.
|
||||
microseconds = (nanoseconds / 1000f64).floor(); |
||||
nanoseconds %= 1000f64; |
||||
|
||||
// c. Set milliseconds to floor(microseconds / 1000).
|
||||
// d. Set microseconds to microseconds modulo 1000.
|
||||
milliseconds = (microseconds / 1000f64).floor(); |
||||
microseconds %= 1000f64; |
||||
|
||||
// e. Set seconds to floor(milliseconds / 1000).
|
||||
// f. Set milliseconds to milliseconds modulo 1000.
|
||||
seconds = (milliseconds / 1000f64).floor(); |
||||
milliseconds %= 1000f64; |
||||
|
||||
// g. Set minutes to floor(seconds / 60).
|
||||
// h. Set seconds to seconds modulo 60.
|
||||
minutes = (seconds / 60f64).floor(); |
||||
seconds %= 60f64; |
||||
} |
||||
// 11. Else if largestUnit is "second", then
|
||||
TemporalUnit::Second => { |
||||
// a. Set microseconds to floor(nanoseconds / 1000).
|
||||
// b. Set nanoseconds to nanoseconds modulo 1000.
|
||||
microseconds = (nanoseconds / 1000f64).floor(); |
||||
nanoseconds %= 1000f64; |
||||
|
||||
// c. Set milliseconds to floor(microseconds / 1000).
|
||||
// d. Set microseconds to microseconds modulo 1000.
|
||||
milliseconds = (microseconds / 1000f64).floor(); |
||||
microseconds %= 1000f64; |
||||
|
||||
// e. Set seconds to floor(milliseconds / 1000).
|
||||
// f. Set milliseconds to milliseconds modulo 1000.
|
||||
seconds = (milliseconds / 1000f64).floor(); |
||||
milliseconds %= 1000f64; |
||||
} |
||||
// 12. Else if largestUnit is "millisecond", then
|
||||
TemporalUnit::Millisecond => { |
||||
// a. Set microseconds to floor(nanoseconds / 1000).
|
||||
// b. Set nanoseconds to nanoseconds modulo 1000.
|
||||
microseconds = (nanoseconds / 1000f64).floor(); |
||||
nanoseconds %= 1000f64; |
||||
|
||||
// c. Set milliseconds to floor(microseconds / 1000).
|
||||
// d. Set microseconds to microseconds modulo 1000.
|
||||
milliseconds = (microseconds / 1000f64).floor(); |
||||
microseconds %= 1000f64; |
||||
} |
||||
// 13. Else if largestUnit is "microsecond", then
|
||||
TemporalUnit::Microsecond => { |
||||
// a. Set microseconds to floor(nanoseconds / 1000).
|
||||
// b. Set nanoseconds to nanoseconds modulo 1000.
|
||||
microseconds = (nanoseconds / 1000f64).floor(); |
||||
nanoseconds %= 1000f64; |
||||
} |
||||
// 14. Else,
|
||||
// a. Assert: largestUnit is "nanosecond".
|
||||
_ => debug_assert!(largest_unit == TemporalUnit::Nanosecond), |
||||
} |
||||
|
||||
let result_values = Vec::from(&[ |
||||
days, |
||||
hours, |
||||
minutes, |
||||
seconds, |
||||
milliseconds, |
||||
microseconds, |
||||
nanoseconds, |
||||
]); |
||||
// 15. For each value v of « days, hours, minutes, seconds, milliseconds, microseconds, nanoseconds », do
|
||||
for value in result_values { |
||||
// a. If 𝔽(v) is not finite, then
|
||||
if !value.is_finite() { |
||||
// i. If sign = 1, then
|
||||
if sign == 1 { |
||||
// 1. Return positive overflow.
|
||||
return Ok((f64::INFINITY, None)); |
||||
} |
||||
// ii. Else if sign = -1, then
|
||||
// 1. Return negative overflow.
|
||||
return Ok((f64::NEG_INFINITY, None)); |
||||
} |
||||
} |
||||
|
||||
let sign = f64::from(sign); |
||||
|
||||
// 16. Return ? CreateTimeDurationRecord(days, hours × sign, minutes × sign, seconds × sign, milliseconds × sign, microseconds × sign, nanoseconds × sign).
|
||||
let result = Self::new( |
||||
hours * sign, |
||||
minutes * sign, |
||||
seconds * sign, |
||||
milliseconds * sign, |
||||
microseconds * sign, |
||||
nanoseconds * sign, |
||||
)?; |
||||
|
||||
Ok((days, Some(result))) |
||||
} |
||||
} |
||||
|
||||
// ==== TimeDuration's public API ====
|
||||
|
||||
impl TimeDuration { |
||||
/// Creates a new validated `TimeDuration`.
|
||||
pub fn new( |
||||
hours: f64, |
||||
minutes: f64, |
||||
seconds: f64, |
||||
milliseconds: f64, |
||||
microseconds: f64, |
||||
nanoseconds: f64, |
||||
) -> TemporalResult<Self> { |
||||
let result = Self::new_unchecked( |
||||
hours, |
||||
minutes, |
||||
seconds, |
||||
milliseconds, |
||||
microseconds, |
||||
nanoseconds, |
||||
); |
||||
if !is_valid_duration(&result.into_iter().collect()) { |
||||
return Err( |
||||
TemporalError::range().with_message("Attempted to create an invalid TimeDuration.") |
||||
); |
||||
} |
||||
Ok(result) |
||||
} |
||||
|
||||
/// Creates a partial `TimeDuration` with all values set to `NaN`.
|
||||
#[must_use] |
||||
pub const fn partial() -> Self { |
||||
Self { |
||||
hours: f64::NAN, |
||||
minutes: f64::NAN, |
||||
seconds: f64::NAN, |
||||
milliseconds: f64::NAN, |
||||
microseconds: f64::NAN, |
||||
nanoseconds: f64::NAN, |
||||
} |
||||
} |
||||
|
||||
/// Returns a new `TimeDuration` representing the absolute value of the current.
|
||||
#[inline] |
||||
#[must_use] |
||||
pub fn abs(&self) -> Self { |
||||
Self { |
||||
hours: self.hours.abs(), |
||||
minutes: self.minutes.abs(), |
||||
seconds: self.seconds.abs(), |
||||
milliseconds: self.milliseconds.abs(), |
||||
microseconds: self.microseconds.abs(), |
||||
nanoseconds: self.nanoseconds.abs(), |
||||
} |
||||
} |
||||
|
||||
/// Balances a `TimeDuration` given a day value and the largest unit. `balance` will return
|
||||
/// the balanced `day` and `TimeDuration`.
|
||||
///
|
||||
/// # Errors:
|
||||
/// - Will error if provided duration is invalid
|
||||
pub fn balance(&self, days: f64, largest_unit: TemporalUnit) -> TemporalResult<(f64, Self)> { |
||||
let result = Self::balance_possibly_infinite_time_duration( |
||||
days, |
||||
self.hours, |
||||
self.minutes, |
||||
self.seconds, |
||||
self.milliseconds, |
||||
self.microseconds, |
||||
self.nanoseconds, |
||||
largest_unit, |
||||
)?; |
||||
let Some(time_duration) = result.1 else { |
||||
return Err(TemporalError::range().with_message("Invalid balance TimeDuration.")); |
||||
}; |
||||
Ok((result.0, time_duration)) |
||||
} |
||||
|
||||
/// Utility function for returning if values in a valid range.
|
||||
#[inline] |
||||
#[must_use] |
||||
pub fn is_within_range(&self) -> bool { |
||||
self.hours.abs() < 24f64 |
||||
&& self.minutes.abs() < 60f64 |
||||
&& self.seconds.abs() < 60f64 |
||||
&& self.milliseconds.abs() < 1000f64 |
||||
&& self.milliseconds.abs() < 1000f64 |
||||
&& self.milliseconds.abs() < 1000f64 |
||||
} |
||||
|
||||
/// Returns the `[[hours]]` value.
|
||||
#[must_use] |
||||
pub const fn hours(&self) -> f64 { |
||||
self.hours |
||||
} |
||||
|
||||
/// Returns the `[[minutes]]` value.
|
||||
#[must_use] |
||||
pub const fn minutes(&self) -> f64 { |
||||
self.minutes |
||||
} |
||||
|
||||
/// Returns the `[[seconds]]` value.
|
||||
#[must_use] |
||||
pub const fn seconds(&self) -> f64 { |
||||
self.seconds |
||||
} |
||||
|
||||
/// Returns the `[[milliseconds]]` value.
|
||||
#[must_use] |
||||
pub const fn milliseconds(&self) -> f64 { |
||||
self.milliseconds |
||||
} |
||||
|
||||
/// Returns the `[[microseconds]]` value.
|
||||
#[must_use] |
||||
pub const fn microseconds(&self) -> f64 { |
||||
self.microseconds |
||||
} |
||||
|
||||
/// Returns the `[[nanoseconds]]` value.
|
||||
#[must_use] |
||||
pub const fn nanoseconds(&self) -> f64 { |
||||
self.nanoseconds |
||||
} |
||||
|
||||
/// Returns the `TimeDuration`'s iterator.
|
||||
#[must_use] |
||||
pub fn iter(&self) -> TimeIter<'_> { |
||||
<&Self as IntoIterator>::into_iter(self) |
||||
} |
||||
} |
||||
|
||||
// ==== TimeDuration method impls ====
|
||||
|
||||
impl TimeDuration { |
||||
/// Rounds the current `TimeDuration` given a rounding increment, unit and rounding mode. `round` will return a tuple of the rounded `TimeDuration` and
|
||||
/// the `total` value of the smallest unit prior to rounding.
|
||||
#[inline] |
||||
pub fn round( |
||||
&self, |
||||
increment: f64, |
||||
unit: TemporalUnit, |
||||
rounding_mode: TemporalRoundingMode, |
||||
) -> TemporalResult<(Self, f64)> { |
||||
let fraction_seconds = match unit { |
||||
TemporalUnit::Year |
||||
| TemporalUnit::Month |
||||
| TemporalUnit::Week |
||||
| TemporalUnit::Day |
||||
| TemporalUnit::Auto => { |
||||
return Err(TemporalError::r#type() |
||||
.with_message("Invalid unit provided to for TimeDuration to round.")) |
||||
} |
||||
_ => self.nanoseconds().mul_add( |
||||
1_000_000_000f64, |
||||
self.microseconds().mul_add( |
||||
1_000_000f64, |
||||
self.milliseconds().mul_add(1000f64, self.seconds()), |
||||
), |
||||
), |
||||
}; |
||||
|
||||
match unit { |
||||
// 12. Else if unit is "hour", then
|
||||
TemporalUnit::Hour => { |
||||
// a. Let fractionalHours be (fractionalSeconds / 60 + minutes) / 60 + hours.
|
||||
let frac_hours = (fraction_seconds / 60f64 + self.minutes) / 60f64 + self.hours; |
||||
// b. Set hours to RoundNumberToIncrement(fractionalHours, increment, roundingMode).
|
||||
let rounded_hours = |
||||
utils::round_number_to_increment(frac_hours, increment, rounding_mode); |
||||
// c. Set total to fractionalHours.
|
||||
// d. Set minutes, seconds, milliseconds, microseconds, and nanoseconds to 0.
|
||||
let result = Self::new(rounded_hours, 0f64, 0f64, 0f64, 0f64, 0f64)?; |
||||
Ok((result, frac_hours)) |
||||
} |
||||
// 13. Else if unit is "minute", then
|
||||
TemporalUnit::Minute => { |
||||
// a. Let fractionalMinutes be fractionalSeconds / 60 + minutes.
|
||||
let frac_minutes = fraction_seconds / 60f64 + self.minutes; |
||||
// b. Set minutes to RoundNumberToIncrement(fractionalMinutes, increment, roundingMode).
|
||||
let rounded_minutes = |
||||
utils::round_number_to_increment(frac_minutes, increment, rounding_mode); |
||||
// c. Set total to fractionalMinutes.
|
||||
// d. Set seconds, milliseconds, microseconds, and nanoseconds to 0.
|
||||
let result = Self::new(self.hours, rounded_minutes, 0f64, 0f64, 0f64, 0f64)?; |
||||
|
||||
Ok((result, frac_minutes)) |
||||
} |
||||
// 14. Else if unit is "second", then
|
||||
TemporalUnit::Second => { |
||||
// a. Set seconds to RoundNumberToIncrement(fractionalSeconds, increment, roundingMode).
|
||||
let rounded_seconds = |
||||
utils::round_number_to_increment(fraction_seconds, increment, rounding_mode); |
||||
// b. Set total to fractionalSeconds.
|
||||
// c. Set milliseconds, microseconds, and nanoseconds to 0.
|
||||
let result = |
||||
Self::new(self.hours, self.minutes, rounded_seconds, 0f64, 0f64, 0f64)?; |
||||
|
||||
Ok((result, fraction_seconds)) |
||||
} |
||||
// 15. Else if unit is "millisecond", then
|
||||
TemporalUnit::Millisecond => { |
||||
// a. Let fractionalMilliseconds be nanoseconds × 10-6 + microseconds × 10-3 + milliseconds.
|
||||
let fraction_millis = self.nanoseconds.mul_add( |
||||
1_000_000f64, |
||||
self.microseconds.mul_add(1_000f64, self.milliseconds), |
||||
); |
||||
|
||||
// b. Set milliseconds to RoundNumberToIncrement(fractionalMilliseconds, increment, roundingMode).
|
||||
let rounded_millis = |
||||
utils::round_number_to_increment(fraction_millis, increment, rounding_mode); |
||||
|
||||
// c. Set total to fractionalMilliseconds.
|
||||
// d. Set microseconds and nanoseconds to 0.
|
||||
let result = Self::new( |
||||
self.hours, |
||||
self.minutes, |
||||
self.seconds, |
||||
rounded_millis, |
||||
0f64, |
||||
0f64, |
||||
)?; |
||||
Ok((result, fraction_millis)) |
||||
} |
||||
// 16. Else if unit is "microsecond", then
|
||||
TemporalUnit::Microsecond => { |
||||
// a. Let fractionalMicroseconds be nanoseconds × 10-3 + microseconds.
|
||||
let frac_micros = self.nanoseconds.mul_add(1_000f64, self.microseconds); |
||||
|
||||
// b. Set microseconds to RoundNumberToIncrement(fractionalMicroseconds, increment, roundingMode).
|
||||
let rounded_micros = |
||||
utils::round_number_to_increment(frac_micros, increment, rounding_mode); |
||||
|
||||
// c. Set total to fractionalMicroseconds.
|
||||
// d. Set nanoseconds to 0.
|
||||
let result = Self::new( |
||||
self.hours, |
||||
self.minutes, |
||||
self.seconds, |
||||
self.milliseconds, |
||||
rounded_micros, |
||||
0f64, |
||||
)?; |
||||
Ok((result, frac_micros)) |
||||
} |
||||
// 17. Else,
|
||||
TemporalUnit::Nanosecond => { |
||||
// a. Assert: unit is "nanosecond".
|
||||
// b. Set total to nanoseconds.
|
||||
let total = self.nanoseconds; |
||||
// c. Set nanoseconds to RoundNumberToIncrement(nanoseconds, increment, roundingMode).
|
||||
let rounded_nanos = |
||||
utils::round_number_to_increment(self.nanoseconds, increment, rounding_mode); |
||||
|
||||
let result = Self::new( |
||||
self.hours, |
||||
self.minutes, |
||||
self.seconds, |
||||
self.milliseconds, |
||||
self.microseconds, |
||||
rounded_nanos, |
||||
)?; |
||||
|
||||
Ok((result, total)) |
||||
} |
||||
_ => unreachable!("All other units early return error."), |
||||
} |
||||
} |
||||
} |
||||
|
||||
impl<'a> IntoIterator for &'a TimeDuration { |
||||
type Item = f64; |
||||
type IntoIter = TimeIter<'a>; |
||||
|
||||
fn into_iter(self) -> Self::IntoIter { |
||||
TimeIter { |
||||
time: self, |
||||
index: 0, |
||||
} |
||||
} |
||||
} |
||||
|
||||
/// An iterator over a `TimeDuration`.
|
||||
#[derive(Debug, Clone)] |
||||
pub struct TimeIter<'a> { |
||||
time: &'a TimeDuration, |
||||
index: usize, |
||||
} |
||||
|
||||
impl Iterator for TimeIter<'_> { |
||||
type Item = f64; |
||||
|
||||
fn next(&mut self) -> Option<Self::Item> { |
||||
let result = match self.index { |
||||
0 => Some(self.time.hours), |
||||
1 => Some(self.time.minutes), |
||||
2 => Some(self.time.seconds), |
||||
3 => Some(self.time.milliseconds), |
||||
4 => Some(self.time.microseconds), |
||||
5 => Some(self.time.nanoseconds), |
||||
_ => None, |
||||
}; |
||||
self.index += 1; |
||||
result |
||||
} |
||||
} |
Loading…
Reference in new issue