diff --git a/src/components/calendar.rs b/src/components/calendar.rs index b0284d868..e1332ef8c 100644 --- a/src/components/calendar.rs +++ b/src/components/calendar.rs @@ -17,6 +17,7 @@ use crate::{ }, iso::IsoDate, options::{ArithmeticOverflow, TemporalUnit}, + parsers::parse_allowed_calendar_formats, TemporalError, TemporalResult, }; @@ -200,8 +201,13 @@ impl Calendar { impl FromStr for Calendar { type Err = TemporalError; - // 13.39 ParseTemporalCalendarString ( string ) + // 13.34 ParseTemporalCalendarString ( string ) fn from_str(s: &str) -> Result { + if let Some(s) = parse_allowed_calendar_formats(s) { + return s + .map(Calendar::from_utf8) + .unwrap_or(Ok(Calendar::default())); + } Calendar::from_utf8(s.as_bytes()) } } diff --git a/src/components/date.rs b/src/components/date.rs index e8dd18de3..c88f50ec3 100644 --- a/src/components/date.rs +++ b/src/components/date.rs @@ -5,11 +5,11 @@ use crate::{ iso::{IsoDate, IsoDateTime, IsoTime}, options::{ ArithmeticOverflow, DifferenceOperation, DifferenceSettings, DisplayCalendar, - ResolvedRoundingOptions, TemporalUnit, + ResolvedRoundingOptions, TemporalUnit, UnitGroup, }, parsers::{parse_date_time, IxdtfStringBuilder}, primitive::FiniteF64, - Sign, TemporalError, TemporalResult, TemporalUnwrap, TimeZone, + TemporalError, TemporalResult, TemporalUnwrap, TimeZone, }; use alloc::{format, string::String}; use core::str::FromStr; @@ -260,9 +260,10 @@ impl PlainDate { // 4. Let resolvedOptions be ? SnapshotOwnProperties(? GetOptionsObject(options), null). // 5. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, DATE, « », "day", "day"). - let (sign, resolved) = ResolvedRoundingOptions::from_diff_settings( + let resolved = ResolvedRoundingOptions::from_diff_settings( settings, op, + UnitGroup::Date, TemporalUnit::Day, TemporalUnit::Day, )?; @@ -303,9 +304,9 @@ impl PlainDate { } let result = Duration::from_normalized(duration, TemporalUnit::Day)?; // 13. Return ! CreateTemporalDuration(sign × duration.[[Years]], sign × duration.[[Months]], sign × duration.[[Weeks]], sign × duration.[[Days]], 0, 0, 0, 0, 0, 0). - match sign { - Sign::Positive | Sign::Zero => Ok(result), - Sign::Negative => Ok(result.negated()), + match op { + DifferenceOperation::Until => Ok(result), + DifferenceOperation::Since => Ok(result.negated()), } } } diff --git a/src/components/datetime.rs b/src/components/datetime.rs index 48a3a23bc..b6498d19b 100644 --- a/src/components/datetime.rs +++ b/src/components/datetime.rs @@ -5,10 +5,10 @@ use crate::{ iso::{IsoDate, IsoDateTime, IsoTime}, options::{ ArithmeticOverflow, DifferenceOperation, DifferenceSettings, DisplayCalendar, - ResolvedRoundingOptions, RoundingOptions, TemporalUnit, ToStringRoundingOptions, + ResolvedRoundingOptions, RoundingOptions, TemporalUnit, ToStringRoundingOptions, UnitGroup, }, parsers::{parse_date_time, IxdtfStringBuilder}, - temporal_assert, Sign, TemporalError, TemporalResult, TemporalUnwrap, TimeZone, + temporal_assert, TemporalError, TemporalResult, TemporalUnwrap, TimeZone, }; use alloc::string::String; use core::{cmp::Ordering, str::FromStr}; @@ -131,9 +131,10 @@ impl PlainDateTime { } // 5. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, datetime, « », "nanosecond", "day"). - let (sign, options) = ResolvedRoundingOptions::from_diff_settings( + let options = ResolvedRoundingOptions::from_diff_settings( settings, op, + UnitGroup::DateTime, TemporalUnit::Day, TemporalUnit::Nanosecond, )?; @@ -149,9 +150,9 @@ impl PlainDateTime { let result = Duration::from_normalized(norm_record, options.largest_unit)?; // Step 12 - match sign { - Sign::Positive | Sign::Zero => Ok(result), - Sign::Negative => Ok(result.negated()), + match op { + DifferenceOperation::Until => Ok(result), + DifferenceOperation::Since => Ok(result.negated()), } } diff --git a/src/components/duration/normalized.rs b/src/components/duration/normalized.rs index 77c19c17c..814b02819 100644 --- a/src/components/duration/normalized.rs +++ b/src/components/duration/normalized.rs @@ -256,8 +256,9 @@ impl NormalizedDurationRecord { /// Equivalent: `CreateNormalizedDurationRecord` & `CombineDateAndNormalizedTimeDuration`. pub(crate) fn new(date: DateDuration, norm: NormalizedTimeDuration) -> TemporalResult { if date.sign() != Sign::Zero && norm.sign() != Sign::Zero && date.sign() != norm.sign() { - return Err(TemporalError::range() - .with_message("DateDuration and NormalizedTimeDuration must agree.")); + return Err(TemporalError::range().with_message( + "DateDuration and NormalizedTimeDuration must agree if both are not zero.", + )); } Ok(Self { date, norm }) } @@ -778,18 +779,18 @@ impl NormalizedDurationRecord { // 4. Let largestUnitIndex be the ordinal index of the row of Table 22 whose "Singular" column contains largestUnit. // 5. Let smallestUnitIndex be the ordinal index of the row of Table 22 whose "Singular" column contains smallestUnit. // 6. Let unitIndex be smallestUnitIndex - 1. - let mut unit = smallest_unit + 1; + let mut smallest_unit = smallest_unit + 1; // 7. Let done be false. // 8. Repeat, while unitIndex ≤ largestUnitIndex and done is false, - while unit != TemporalUnit::Auto && unit <= largest_unit { + while smallest_unit != TemporalUnit::Auto && largest_unit < smallest_unit { // a. Let unit be the value in the "Singular" column of Table 22 in the row whose ordinal index is unitIndex. // b. If unit is not "week", or largestUnit is "week", then - if unit == TemporalUnit::Week || largest_unit != TemporalUnit::Week { - unit = unit + 1; + if smallest_unit == TemporalUnit::Week || largest_unit != TemporalUnit::Week { + smallest_unit = smallest_unit + 1; continue; } - let end_duration = match unit { + let end_duration = match smallest_unit { // i. If unit is "year", then TemporalUnit::Year => { // 1. Let years be duration.[[Years]] + sign. @@ -887,7 +888,7 @@ impl NormalizedDurationRecord { break; } // c. Set unitIndex to unitIndex - 1. - unit = unit + 1; + smallest_unit = smallest_unit + 1; } Ok(duration) diff --git a/src/components/instant.rs b/src/components/instant.rs index 6126ce391..6832c0607 100644 --- a/src/components/instant.rs +++ b/src/components/instant.rs @@ -10,12 +10,12 @@ use crate::{ iso::{IsoDate, IsoDateTime, IsoTime}, options::{ ArithmeticOverflow, DifferenceOperation, DifferenceSettings, DisplayOffset, - ResolvedRoundingOptions, RoundingOptions, TemporalUnit, ToStringRoundingOptions, + ResolvedRoundingOptions, RoundingOptions, TemporalUnit, ToStringRoundingOptions, UnitGroup, }, parsers::{parse_instant, IxdtfStringBuilder}, primitive::FiniteF64, rounding::{IncrementRounder, Round}, - Sign, TemporalError, TemporalResult, TemporalUnwrap, TimeZone, NS_MAX_INSTANT, + TemporalError, TemporalResult, TemporalUnwrap, TimeZone, NS_MAX_INSTANT, }; use ixdtf::parsers::records::UtcOffsetRecordOrZ; @@ -24,11 +24,12 @@ use num_traits::FromPrimitive; use super::{ duration::normalized::{NormalizedDurationRecord, NormalizedTimeDuration}, timezone::TimeZoneProvider, + DateDuration, }; -const NANOSECONDS_PER_SECOND: f64 = 1e9; -const NANOSECONDS_PER_MINUTE: f64 = 60f64 * NANOSECONDS_PER_SECOND; -const NANOSECONDS_PER_HOUR: f64 = 60f64 * NANOSECONDS_PER_MINUTE; +const NANOSECONDS_PER_SECOND: i128 = 1_000_000_000; +const NANOSECONDS_PER_MINUTE: i128 = 60 * NANOSECONDS_PER_SECOND; +const NANOSECONDS_PER_HOUR: i128 = 60 * NANOSECONDS_PER_MINUTE; #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] pub struct EpochNanoseconds(pub(crate) i128); @@ -83,19 +84,14 @@ impl Instant { // TODO: Update to `i128`? /// Adds a `TimeDuration` to the current `Instant`. /// - /// Temporal-Proposal equivalent: `AddDurationToOrSubtractDurationFrom`. + /// Temporal-Proposal equivalent: `AddInstant`. pub(crate) fn add_to_instant(&self, duration: &TimeDuration) -> TemporalResult { - let current_nanos = self.epoch_nanoseconds() as f64; - let result = current_nanos - + duration.nanoseconds.0 - + (duration.microseconds.0 * 1000f64) - + (duration.milliseconds.0 * 1_000_000f64) - + (duration.seconds.0 * NANOSECONDS_PER_SECOND) - + (duration.minutes.0 * NANOSECONDS_PER_MINUTE) - + (duration.hours.0 * NANOSECONDS_PER_HOUR); + let norm = NormalizedTimeDuration::from_time_duration(duration); + let result = self.epoch_nanoseconds() + norm.0; Ok(Self::from(EpochNanoseconds::try_from(result)?)) } + /// `temporal_rs` equivalent of `DifferenceInstant` pub(crate) fn diff_instant_internal( &self, other: &Self, @@ -104,7 +100,10 @@ impl Instant { let diff = NormalizedTimeDuration::from_nanosecond_difference(other.as_i128(), self.as_i128())?; let (round_record, _) = diff.round(FiniteF64::default(), resolved_options)?; - Ok(round_record) + NormalizedDurationRecord::new( + DateDuration::default(), + round_record.normalized_time_duration(), + ) } // TODO: Add test for `diff_instant`. @@ -121,9 +120,10 @@ impl Instant { // 2. Set other to ? ToTemporalInstant(other). // 3. Let resolvedOptions be ? SnapshotOwnProperties(? GetOptionsObject(options), null). // 4. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, time, « », "nanosecond", "second"). - let (sign, resolved_options) = ResolvedRoundingOptions::from_diff_settings( + let resolved_options = ResolvedRoundingOptions::from_diff_settings( options, op, + UnitGroup::Time, TemporalUnit::Second, TemporalUnit::Nanosecond, )?; @@ -138,9 +138,9 @@ impl Instant { // 6. Let norm be diffRecord.[[NormalizedTimeDuration]]. // 7. Let result be ! BalanceTimeDuration(norm, settings.[[LargestUnit]]). // 8. Return ! CreateTemporalDuration(0, 0, 0, 0, sign × result.[[Hours]], sign × result.[[Minutes]], sign × result.[[Seconds]], sign × result.[[Milliseconds]], sign × result.[[Microseconds]], sign × result.[[Nanoseconds]]). - match sign { - Sign::Positive | Sign::Zero => Ok(result), - Sign::Negative => Ok(result.negated()), + match op { + DifferenceOperation::Until => Ok(result), + DifferenceOperation::Since => Ok(result.negated()), } } @@ -338,12 +338,12 @@ impl FromStr for Instant { // Find the offset let offset = match ixdtf_record.offset { UtcOffsetRecordOrZ::Offset(offset) => { - f64::from(offset.hour) * NANOSECONDS_PER_HOUR - + f64::from(offset.minute) * NANOSECONDS_PER_MINUTE - + f64::from(offset.second) * NANOSECONDS_PER_SECOND - + f64::from(offset.nanosecond) + offset.hour as i128 * NANOSECONDS_PER_HOUR + + i128::from(offset.minute) * NANOSECONDS_PER_MINUTE + + i128::from(offset.second) * NANOSECONDS_PER_SECOND + + i128::from(offset.nanosecond) } - UtcOffsetRecordOrZ::Z => 0.0, + UtcOffsetRecordOrZ::Z => 0, }; let nanoseconds = IsoDateTime::new_unchecked(iso_date, iso_time) .as_nanoseconds() diff --git a/src/components/time.rs b/src/components/time.rs index cd2ebf11a..26dae0915 100644 --- a/src/components/time.rs +++ b/src/components/time.rs @@ -5,11 +5,11 @@ use crate::{ iso::IsoTime, options::{ ArithmeticOverflow, DifferenceOperation, DifferenceSettings, ResolvedRoundingOptions, - RoundingIncrement, TemporalRoundingMode, TemporalUnit, ToStringRoundingOptions, + RoundingIncrement, TemporalRoundingMode, TemporalUnit, ToStringRoundingOptions, UnitGroup, }, parsers::{parse_time, IxdtfStringBuilder}, primitive::FiniteF64, - Sign, TemporalError, TemporalResult, + TemporalError, TemporalResult, }; use alloc::string::String; use core::str::FromStr; @@ -124,9 +124,10 @@ impl PlainTime { // 2. Set other to ? ToTemporalTime(other). // 3. Let resolvedOptions be ? SnapshotOwnProperties(? GetOptionsObject(options), null). // 4. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, TIME, « », "nanosecond", "hour"). - let (sign, resolved) = ResolvedRoundingOptions::from_diff_settings( + let resolved = ResolvedRoundingOptions::from_diff_settings( settings, op, + UnitGroup::Time, TemporalUnit::Hour, TemporalUnit::Nanosecond, )?; @@ -151,9 +152,9 @@ impl PlainTime { let result = TimeDuration::from_normalized(normalized_time, resolved.largest_unit)?.1; // 8. Return ! CreateTemporalDuration(0, 0, 0, 0, sign × result.[[Hours]], sign × result.[[Minutes]], sign × result.[[Seconds]], sign × result.[[Milliseconds]], sign × result.[[Microseconds]], sign × result.[[Nanoseconds]]). - match sign { - Sign::Positive | Sign::Zero => Ok(Duration::from(result)), - Sign::Negative => Ok(Duration::from(result.negated())), + match op { + DifferenceOperation::Until => Ok(Duration::from(result)), + DifferenceOperation::Since => Ok(Duration::from(result.negated())), } } } diff --git a/src/components/zoneddatetime.rs b/src/components/zoneddatetime.rs index de75b7d74..b545e83a2 100644 --- a/src/components/zoneddatetime.rs +++ b/src/components/zoneddatetime.rs @@ -13,9 +13,10 @@ use crate::{ }, iso::{IsoDate, IsoDateTime, IsoTime}, options::{ - ArithmeticOverflow, Disambiguation, DisplayCalendar, DisplayOffset, DisplayTimeZone, - OffsetDisambiguation, ResolvedRoundingOptions, RoundingIncrement, TemporalRoundingMode, - TemporalUnit, ToStringRoundingOptions, + ArithmeticOverflow, DifferenceOperation, DifferenceSettings, Disambiguation, + DisplayCalendar, DisplayOffset, DisplayTimeZone, OffsetDisambiguation, + ResolvedRoundingOptions, RoundingIncrement, TemporalRoundingMode, TemporalUnit, + ToStringRoundingOptions, UnitGroup, }, parsers::{self, IxdtfStringBuilder}, partial::{PartialDate, PartialTime}, @@ -210,7 +211,8 @@ impl ZonedDateTime { let mut is_success = false; // 10. Repeat, while dayCorrection ≤ maxDayCorrection and success is false, while day_correction <= max_correction && !is_success { - // a. Let intermediateDate be BalanceISODate(endDateTime.[[ISODate]].[[Year]], endDateTime.[[ISODate]].[[Month]], endDateTime.[[ISODate]].[[Day]] - dayCorrection × sign). + // a. Let intermediateDate be BalanceISODate(endDateTime.[[ISODate]].[[Year]], + // endDateTime.[[ISODate]].[[Month]], endDateTime.[[ISODate]].[[Day]] - dayCorrection × sign). let intermediate = IsoDate::balance( end.date.year, end.date.month.into(), @@ -249,6 +251,74 @@ impl ZonedDateTime { .date_until(&start.date, &intermediate_dt.date, date_largest)?; NormalizedDurationRecord::new(*date_diff.date(), time_duration) } + + /// `temporal_rs` equivalent to `DifferenceTemporalZonedDateTime`. + pub(crate) fn diff_internal_with_provider( + &self, + op: DifferenceOperation, + other: &Self, + options: DifferenceSettings, + provider: &impl TimeZoneProvider, + ) -> TemporalResult { + // NOTE: for order of operations, this should be asserted prior to this point + // by any engine implementors, but asserting out of caution. + if self.calendar != other.calendar { + return Err(TemporalError::range() + .with_message("Calendar must be the same when diffing two ZonedDateTimes")); + } + + // 4. Set settings be ? GetDifferenceSettings(operation, resolvedOptions, datetime, « », nanosecond, hour). + let resolved_options = ResolvedRoundingOptions::from_diff_settings( + options, + op, + UnitGroup::DateTime, + TemporalUnit::Hour, + TemporalUnit::Nanosecond, + )?; + + // 5. If TemporalUnitCategory(settings.[[LargestUnit]]) is time, then + if resolved_options.largest_unit.is_time_unit() { + // a. Let internalDuration be DifferenceInstant(zonedDateTime.[[EpochNanoseconds]], other.[[EpochNanoseconds]], settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]). + let internal = self + .instant + .diff_instant_internal(&other.instant, resolved_options)?; + // b. Let result be ! TemporalDurationFromInternal(internalDuration, settings.[[LargestUnit]]). + let result = Duration::from_normalized(internal, resolved_options.largest_unit)?; + // c. If operation is since, set result to CreateNegatedTemporalDuration(result). + // d. Return result. + match op { + DifferenceOperation::Since => return Ok(result.negated()), + DifferenceOperation::Until => return Ok(result), + } + } + + // 6. NOTE: To calculate differences in two different time zones, + // settings.[[LargestUnit]] must be a time unit, because day lengths + // can vary between time zones due to DST and other UTC offset shifts. + // 7. If TimeZoneEquals(zonedDateTime.[[TimeZone]], other.[[TimeZone]]) is false, then + if self.tz != other.tz { + // a. Throw a RangeError exception. + return Err(TemporalError::range() + .with_message("Time zones cannot be different if unit is a date unit.")); + } + + // 8. If zonedDateTime.[[EpochNanoseconds]] = other.[[EpochNanoseconds]], then + if self.instant == other.instant { + // a. Return ! CreateTemporalDuration(0, 0, 0, 0, 0, 0, 0, 0, 0, 0). + return Ok(Duration::default()); + } + + // 9. Let internalDuration be ? DifferenceZonedDateTimeWithRounding(zonedDateTime.[[EpochNanoseconds]], other.[[EpochNanoseconds]], zonedDateTime.[[TimeZone]], zonedDateTime.[[Calendar]], settings.[[LargestUnit]], settings.[[RoundingIncrement]], settings.[[SmallestUnit]], settings.[[RoundingMode]]). + let internal = self.diff_with_rounding(other, resolved_options, provider)?; + // 10. Let result be ! TemporalDurationFromInternal(internalDuration, hour). + let result = Duration::from_normalized(internal, TemporalUnit::Hour)?; + // 11. If operation is since, set result to CreateNegatedTemporalDuration(result). + // 12. Return result. + match op { + DifferenceOperation::Since => Ok(result.negated()), + DifferenceOperation::Until => Ok(result), + } + } } // ==== Public API ==== @@ -890,6 +960,26 @@ impl ZonedDateTime { ) } + /// Returns a [`Duration`] representing the period of time from this `ZonedDateTime` since the other `ZonedDateTime`. + pub fn since_with_provider( + &self, + other: &Self, + options: DifferenceSettings, + provider: &impl TimeZoneProvider, + ) -> TemporalResult { + self.diff_internal_with_provider(DifferenceOperation::Since, other, options, provider) + } + + /// Returns a [`Duration`] representing the period of time from this `ZonedDateTime` since the other `ZonedDateTime`. + pub fn until_with_provider( + &self, + other: &Self, + options: DifferenceSettings, + provider: &impl TimeZoneProvider, + ) -> TemporalResult { + self.diff_internal_with_provider(DifferenceOperation::Until, other, options, provider) + } + /// Return a `ZonedDateTime` representing the start of the day /// for the current `ZonedDateTime`. pub fn start_of_day_with_provider( @@ -1406,7 +1496,7 @@ mod tests { .until( &midnight_disambiguated.instant, DifferenceSettings { - largest_unit: Some(TemporalUnit::Year), + largest_unit: Some(TemporalUnit::Hour), smallest_unit: Some(TemporalUnit::Nanosecond), ..Default::default() }, diff --git a/src/iso.rs b/src/iso.rs index adaef2290..057b2a33c 100644 --- a/src/iso.rs +++ b/src/iso.rs @@ -465,6 +465,11 @@ impl IsoDate { i32::from(intermediate.1) + i32::from(sign), ); } + + if largest_unit == TemporalUnit::Month { + months += years * 12; + years = 0; + } } // 9. Set intermediate to BalanceISOYearMonth(y1 + years, m1 + months). @@ -925,12 +930,7 @@ fn utc_epoch_nanos(date: IsoDate, time: &IsoTime) -> TemporalResult i128 { let ms = time.to_epoch_ms(); let epoch_ms = utils::epoch_days_to_epoch_ms(date.to_epoch_days(), ms); - - let epoch_nanos = epoch_ms.mul_add( - 1_000_000f64, - f64::from(time.microsecond).mul_add(1_000f64, f64::from(time.nanosecond)), - ); - epoch_nanos as i128 + (epoch_ms * 1_000_000.0) as i128 + time.microsecond as i128 * 1_000 + time.nanosecond as i128 } // ==== `IsoDate` specific utiltiy functions ==== diff --git a/src/options.rs b/src/options.rs index 885d645c4..3c1c32e8f 100644 --- a/src/options.rs +++ b/src/options.rs @@ -4,7 +4,7 @@ //! operation may be completed. use crate::parsers::Precision; -use crate::{Sign, TemporalError, TemporalResult, MS_PER_DAY, NS_PER_DAY}; +use crate::{TemporalError, TemporalResult, MS_PER_DAY, NS_PER_DAY}; use core::ops::Add; use core::{fmt, str::FromStr}; @@ -177,30 +177,29 @@ impl ResolvedRoundingOptions { pub(crate) fn from_diff_settings( options: DifferenceSettings, operation: DifferenceOperation, + unit_group: UnitGroup, fallback_largest: TemporalUnit, fallback_smallest: TemporalUnit, - ) -> TemporalResult<(Sign, Self)> { + ) -> TemporalResult { // 4. Let resolvedOptions be ? SnapshotOwnProperties(? GetOptionsObject(options), null). + let largest_unit = options + .largest_unit + .map(|unit| unit.assert_unit_group(unit_group)) + .transpose()?; // 5. Let settings be ? GetDifferenceSettings(operation, resolvedOptions, DATE, « », "day", "day"). let increment = options.increment.unwrap_or_default(); - let (sign, rounding_mode) = match operation { - DifferenceOperation::Since => { - let mode = options - .rounding_mode - .unwrap_or(TemporalRoundingMode::Trunc) - .negate(); - (Sign::Negative, mode) + let rounding_mode = match operation { + DifferenceOperation::Since => options + .rounding_mode + .unwrap_or(TemporalRoundingMode::Trunc) + .negate(), + DifferenceOperation::Until => { + options.rounding_mode.unwrap_or(TemporalRoundingMode::Trunc) } - DifferenceOperation::Until => ( - Sign::Positive, - options.rounding_mode.unwrap_or(TemporalRoundingMode::Trunc), - ), }; let smallest_unit = options.smallest_unit.unwrap_or(fallback_smallest); // Use the defaultlargestunit which is max smallestlargestdefault and smallestunit - let largest_unit = options - .largest_unit - .unwrap_or(smallest_unit.max(fallback_largest)); + let largest_unit = largest_unit.unwrap_or(smallest_unit.max(fallback_largest)); // 11. If LargerOfTwoTemporalUnits(largestUnit, smallestUnit) is not largestUnit, throw a RangeError exception. // 12. Let maximum be MaximumTemporalDurationRoundingIncrement(smallestUnit). @@ -223,7 +222,7 @@ impl ResolvedRoundingOptions { rounding_mode, }; - Ok((sign, resolved)) + Ok(resolved) } pub(crate) fn from_duration_options( @@ -345,6 +344,12 @@ impl ResolvedRoundingOptions { // ==== Options enums and methods ==== +pub enum UnitGroup { + Date, + Time, + DateTime, +} + /// The relevant unit that should be used for the operation that /// this option is provided as a value. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] @@ -437,6 +442,19 @@ impl TemporalUnit { Hour | Minute | Second | Millisecond | Microsecond | Nanosecond ) } + + #[inline] + pub fn assert_unit_group(self, group: UnitGroup) -> TemporalResult { + match group { + UnitGroup::Date if !self.is_calendar_unit() || self != TemporalUnit::Day => { + Err(TemporalError::range().with_message("Unit must be a date unit.")) + } + UnitGroup::Time if !self.is_time_unit() => { + Err(TemporalError::range().with_message("Unit must be a time unit.")) + } + _ => Ok(self), + } + } } impl From for TemporalUnit { diff --git a/src/parsers.rs b/src/parsers.rs index 0c31bfa02..601e3e509 100644 --- a/src/parsers.rs +++ b/src/parsers.rs @@ -766,6 +766,20 @@ pub(crate) fn parse_time(source: &str) -> TemporalResult { } } +#[inline] +pub(crate) fn parse_allowed_calendar_formats(s: &str) -> Option> { + if let Ok(r) = parse_ixdtf(s, ParseVariant::DateTime).map(|r| r.calendar) { + return Some(r); + } else if let Ok(r) = IxdtfParser::from_str(s).parse_time().map(|r| r.calendar) { + return Some(r); + } else if let Ok(r) = parse_ixdtf(s, ParseVariant::YearMonth).map(|r| r.calendar) { + return Some(r); + } else if let Ok(r) = parse_ixdtf(s, ParseVariant::MonthDay).map(|r| r.calendar) { + return Some(r); + } + Some(None) +} + // TODO: ParseTimeZoneString, ParseZonedDateTimeString #[cfg(test)] diff --git a/src/primitive.rs b/src/primitive.rs index c6233a92e..623757eae 100644 --- a/src/primitive.rs +++ b/src/primitive.rs @@ -56,7 +56,11 @@ impl FiniteF64 { } pub fn copysign(&self, other: f64) -> Self { - Self(self.0.copysign(other)) + if !self.is_zero() { + Self(self.0.copysign(other)) + } else { + *self + } } pub(crate) fn as_date_value(&self) -> TemporalResult {