Browse Source

Implement `DifferenceInstant` and related refactor (#3568)

* Work on building out instant methods

* Needed to rebase
pull/3591/head
Kevin 10 months ago committed by GitHub
parent
commit
d71334e0f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 16
      core/temporal/src/components/date.rs
  2. 1137
      core/temporal/src/components/duration.rs
  3. 461
      core/temporal/src/components/duration/date.rs
  4. 545
      core/temporal/src/components/duration/time.rs
  5. 58
      core/temporal/src/components/instant.rs
  6. 2
      core/temporal/src/components/mod.rs
  7. 13
      core/temporal/src/options.rs

16
core/temporal/src/components/date.rs

@ -13,6 +13,8 @@ use crate::{
}; };
use std::{any::Any, str::FromStr}; use std::{any::Any, str::FromStr};
use super::duration::TimeDuration;
/// The native Rust implementation of `Temporal.PlainDate`. /// The native Rust implementation of `Temporal.PlainDate`.
#[derive(Debug, Default, Clone)] #[derive(Debug, Default, Clone)]
pub struct Date<C: CalendarProtocol> { pub struct Date<C: CalendarProtocol> {
@ -156,12 +158,20 @@ impl<C: CalendarProtocol> Date<C> {
// 3. Let overflow be ? ToTemporalOverflow(options). // 3. Let overflow be ? ToTemporalOverflow(options).
// 4. Let days be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day").[[Days]]. // 4. Let days be ? BalanceTimeDuration(duration.[[Days]], duration.[[Hours]], duration.[[Minutes]], duration.[[Seconds]], duration.[[Milliseconds]], duration.[[Microseconds]], duration.[[Nanoseconds]], "day").[[Days]].
let (days, _) = duration.balance_time_duration(TemporalUnit::Day)?; let (days, _) = TimeDuration::new_unchecked(
duration.hours(),
duration.minutes(),
duration.seconds(),
duration.milliseconds(),
duration.microseconds(),
duration.nanoseconds(),
)
.balance(duration.days(), TemporalUnit::Day)?;
// 5. Let result be ? AddISODate(plainDate.[[ISOYear]], plainDate.[[ISOMonth]], plainDate.[[ISODay]], 0, 0, 0, days, overflow). // 5. Let result be ? AddISODate(plainDate.[[ISOYear]], plainDate.[[ISOMonth]], plainDate.[[ISODay]], 0, 0, 0, days, overflow).
let result = self let result = self
.iso .iso
.add_iso_date(&DateDuration::new(0f64, 0f64, 0f64, days), overflow)?; .add_iso_date(&DateDuration::new(0f64, 0f64, 0f64, days)?, overflow)?;
Ok(Self::new_unchecked(result, self.calendar().clone())) Ok(Self::new_unchecked(result, self.calendar().clone()))
} }
@ -202,7 +212,7 @@ impl<C: CalendarProtocol> Date<C> {
0f64, 0f64,
0f64, 0f64,
f64::from(days), f64::from(days),
))); )?));
} }
self.calendar() self.calendar()

1137
core/temporal/src/components/duration.rs

File diff suppressed because it is too large Load Diff

461
core/temporal/src/components/duration/date.rs

@ -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
}
}

545
core/temporal/src/components/duration/time.rs

@ -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
}
}

58
core/temporal/src/components/instant.rs

@ -1,16 +1,63 @@
//! An implementation of the Temporal Instant. //! An implementation of the Temporal Instant.
use crate::{TemporalError, TemporalResult}; use crate::{
components::duration::TimeDuration,
options::{DifferenceSettings, TemporalUnit},
TemporalError, TemporalResult,
};
use num_bigint::BigInt; use num_bigint::BigInt;
use num_traits::ToPrimitive; use num_traits::ToPrimitive;
/// The native Rust implementation of `Temporal.Instant` /// The native Rust implementation of `Temporal.Instant`
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub struct Instant { pub struct Instant {
pub(crate) nanos: BigInt, pub(crate) nanos: BigInt,
} }
// ==== Private API ====
impl Instant {
// TODO: Add test for `diff_instant`.
// NOTE(nekevss): As the below is internal, op will be left as a boolean
// with a `since` op being true and `until` being false.
/// Internal operation to handle `since` and `until` difference ops.
#[allow(unused)]
pub(crate) fn diff_instant(
&self,
op: bool,
other: &Self,
settings: DifferenceSettings, // TODO: Determine DifferenceSettings fate -> is there a better way to approach this.
) -> TemporalResult<TimeDuration> {
let diff = self
.nanos
.to_f64()
.expect("valid instant is representable by f64.")
- other
.nanos
.to_f64()
.expect("Valid instant nanos is representable by f64.");
let nanos = diff.rem_euclid(1000f64);
let micros = (diff / 1000f64).trunc().rem_euclid(1000f64);
let millis = (diff / 1_000_000f64).trunc().rem_euclid(1000f64);
let secs = (diff / 1_000_000_000f64).trunc();
if settings.smallest_unit == TemporalUnit::Nanosecond {
let (_, result) = TimeDuration::new_unchecked(0f64, 0f64, secs, millis, micros, nanos)
.balance(0f64, settings.largest_unit)?;
return Ok(result);
}
let (round_result, _) = TimeDuration::new(0f64, 0f64, secs, millis, micros, nanos)?.round(
settings.rounding_increment,
settings.smallest_unit,
settings.rounding_mode,
)?;
let (_, result) = round_result.balance(0f64, settings.largest_unit)?;
Ok(result)
}
}
// ==== Public API ==== // ==== Public API ====
impl Instant { impl Instant {
@ -60,6 +107,8 @@ impl Instant {
} }
} }
// ==== Utility Functions ====
/// Utility for determining if the nanos are within a valid range. /// Utility for determining if the nanos are within a valid range.
#[inline] #[inline]
#[must_use] #[must_use]
@ -67,6 +116,8 @@ pub(crate) fn is_valid_epoch_nanos(nanos: &BigInt) -> bool {
nanos <= &BigInt::from(crate::NS_MAX_INSTANT) && nanos >= &BigInt::from(crate::NS_MIN_INSTANT) nanos <= &BigInt::from(crate::NS_MAX_INSTANT) && nanos >= &BigInt::from(crate::NS_MIN_INSTANT)
} }
// ==== Instant Tests ====
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::{components::Instant, NS_MAX_INSTANT, NS_MIN_INSTANT}; use crate::{components::Instant, NS_MAX_INSTANT, NS_MIN_INSTANT};
@ -76,7 +127,8 @@ mod tests {
#[test] #[test]
#[allow(clippy::float_cmp)] #[allow(clippy::float_cmp)]
fn max_and_minimum_instant_bounds() { fn max_and_minimum_instant_bounds() {
// This test is primarily to assert that the `expect` in the epoch methods is valid. // This test is primarily to assert that the `expect` in the epoch methods is
// valid, i.e., a valid instant is within the range of an f64.
let max = BigInt::from(NS_MAX_INSTANT); let max = BigInt::from(NS_MAX_INSTANT);
let min = BigInt::from(NS_MIN_INSTANT); let min = BigInt::from(NS_MIN_INSTANT);
let max_instant = Instant::new(max.clone()).unwrap(); let max_instant = Instant::new(max.clone()).unwrap();

2
core/temporal/src/components/mod.rs

@ -17,11 +17,11 @@
// TODO: Expand upon above introduction. // TODO: Expand upon above introduction.
pub mod calendar; pub mod calendar;
pub mod duration;
pub mod tz; pub mod tz;
mod date; mod date;
mod datetime; mod datetime;
pub(crate) mod duration;
mod instant; mod instant;
mod month_day; mod month_day;
mod time; mod time;

13
core/temporal/src/options.rs

@ -7,6 +7,19 @@ use core::{fmt, str::FromStr};
use crate::TemporalError; use crate::TemporalError;
// NOTE: Currently the `DifferenceSetting` is the record returned from 13.47 `GetDifferenceSetting`.
// This should be reassessed once Instant is added to the builtin `Temporal.Instant`.
/// The settings for a difference Op
#[derive(Debug, Clone, Copy)]
pub struct DifferenceSettings {
pub(crate) rounding_mode: TemporalRoundingMode,
pub(crate) rounding_increment: f64,
pub(crate) largest_unit: TemporalUnit,
pub(crate) smallest_unit: TemporalUnit,
}
// ==== Options enums and methods ====
/// The relevant unit that should be used for the operation that /// The relevant unit that should be used for the operation that
/// this option is provided as a value. /// this option is provided as a value.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]

Loading…
Cancel
Save