From 68689756478d259886a21dbb9a27309ffa9a84a4 Mon Sep 17 00:00:00 2001 From: Kevin Ness <46825870+nekevss@users.noreply.github.com> Date: Tue, 6 May 2025 20:38:05 -0500 Subject: [PATCH] Bump temporal_rs and add ZonedDateTime.prototype.round impl --- Cargo.lock | 6 +- Cargo.toml | 2 +- .../src/builtins/temporal/duration/mod.rs | 94 +++++++++---------- .../builtins/temporal/plain_date_time/mod.rs | 2 +- .../builtins/temporal/zoneddatetime/mod.rs | 89 +++++++++++++++--- 5 files changed, 121 insertions(+), 72 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4727aeeee6a..8c905b39eef 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3637,7 +3637,7 @@ checksum = "1ac9aa371f599d22256307c24a9d748c041e548cbf599f35d890f9d365361790" [[package]] name = "temporal_provider" version = "0.0.6" -source = "git+https://github.com/boa-dev/temporal.git?rev=ca7a601580cbb240c751bfc0de000a1a48e9766d#ca7a601580cbb240c751bfc0de000a1a48e9766d" +source = "git+https://github.com/boa-dev/temporal.git?rev=6e7e341e15625cbc85a8842b794224c16da58fb6#6e7e341e15625cbc85a8842b794224c16da58fb6" dependencies = [ "databake 0.2.0", "parse-zoneinfo", @@ -3651,10 +3651,10 @@ dependencies = [ [[package]] name = "temporal_rs" version = "0.0.6" -source = "git+https://github.com/boa-dev/temporal.git?rev=ca7a601580cbb240c751bfc0de000a1a48e9766d#ca7a601580cbb240c751bfc0de000a1a48e9766d" +source = "git+https://github.com/boa-dev/temporal.git?rev=6e7e341e15625cbc85a8842b794224c16da58fb6#6e7e341e15625cbc85a8842b794224c16da58fb6" dependencies = [ "combine", - "iana-time-zone", + "core_maths", "icu_calendar 2.0.0-beta2", "ixdtf", "jiff-tzdb", diff --git a/Cargo.toml b/Cargo.toml index ad7c8962ea2..81c615ae54b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -120,7 +120,7 @@ intrusive-collections = "0.9.7" cfg-if = "1.0.0" either = "1.15.0" sys-locale = "0.3.2" -temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "ca7a601580cbb240c751bfc0de000a1a48e9766d", default-features = false, features = [ +temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "6e7e341e15625cbc85a8842b794224c16da58fb6", default-features = false, features = [ "tzdb", ] } web-time = "1.1.0" diff --git a/core/engine/src/builtins/temporal/duration/mod.rs b/core/engine/src/builtins/temporal/duration/mod.rs index 262443d6073..033bd183bf1 100644 --- a/core/engine/src/builtins/temporal/duration/mod.rs +++ b/core/engine/src/builtins/temporal/duration/mod.rs @@ -292,7 +292,7 @@ impl BuiltInConstructor for Duration { let microseconds = args.get_or_undefined(8).map_or(Ok(0), |v| { let finite = v.to_finitef64(context)?; finite - .as_integer_if_integral::() + .as_integer_if_integral::() .map_err(JsError::from) })?; @@ -300,21 +300,21 @@ impl BuiltInConstructor for Duration { let nanoseconds = args.get_or_undefined(9).map_or(Ok(0), |v| { let finite = v.to_finitef64(context)?; finite - .as_integer_if_integral::() + .as_integer_if_integral::() .map_err(JsError::from) })?; let record = InnerDuration::new( - years.try_into()?, - months.try_into()?, - weeks.try_into()?, - days.try_into()?, - hours.try_into()?, - minutes.try_into()?, - seconds.try_into()?, - milliseconds.try_into()?, - microseconds.try_into()?, - nanoseconds.try_into()?, + years, + months, + weeks, + days, + hours, + minutes, + seconds, + milliseconds, + microseconds, + nanoseconds, )?; // 12. Return ? CreateTemporalDuration(y, mo, w, d, h, m, s, ms, mis, ns, NewTarget). @@ -337,16 +337,16 @@ impl Duration { let inner = &duration.inner; match field { - DateTimeValues::Year => Ok(JsValue::new(inner.years().as_inner())), - DateTimeValues::Month => Ok(JsValue::new(inner.months().as_inner())), - DateTimeValues::Week => Ok(JsValue::new(inner.weeks().as_inner())), - DateTimeValues::Day => Ok(JsValue::new(inner.days().as_inner())), - DateTimeValues::Hour => Ok(JsValue::new(inner.hours().as_inner())), - DateTimeValues::Minute => Ok(JsValue::new(inner.minutes().as_inner())), - DateTimeValues::Second => Ok(JsValue::new(inner.seconds().as_inner())), - DateTimeValues::Millisecond => Ok(JsValue::new(inner.milliseconds().as_inner())), - DateTimeValues::Microsecond => Ok(JsValue::new(inner.microseconds().as_inner())), - DateTimeValues::Nanosecond => Ok(JsValue::new(inner.nanoseconds().as_inner())), + DateTimeValues::Year => Ok(JsValue::new(inner.years())), + DateTimeValues::Month => Ok(JsValue::new(inner.months())), + DateTimeValues::Week => Ok(JsValue::new(inner.weeks())), + DateTimeValues::Day => Ok(JsValue::new(inner.days())), + DateTimeValues::Hour => Ok(JsValue::new(inner.hours())), + DateTimeValues::Minute => Ok(JsValue::new(inner.minutes())), + DateTimeValues::Second => Ok(JsValue::new(inner.seconds())), + DateTimeValues::Millisecond => Ok(JsValue::new(inner.milliseconds())), + DateTimeValues::Microsecond => Ok(JsValue::new(inner.microseconds() as f64)), + DateTimeValues::Nanosecond => Ok(JsValue::new(inner.nanoseconds() as f64)), DateTimeValues::MonthCode => unreachable!( "Any other DateTimeValue fields on Duration would be an implementation error." ), @@ -1030,10 +1030,9 @@ pub(crate) fn to_temporal_partial_duration( .get(js_string!("days"), context)? .map(|v| { let finite = v.to_finitef64(context)?; - let integral_int = finite + finite .as_integer_if_integral::() - .map_err(JsError::from)?; - integral_int.try_into().map_err(JsError::from) + .map_err(JsError::from) }) .transpose()?; @@ -1043,10 +1042,9 @@ pub(crate) fn to_temporal_partial_duration( .get(js_string!("hours"), context)? .map(|v| { let finite = v.to_finitef64(context)?; - let integral_int = finite + finite .as_integer_if_integral::() - .map_err(JsError::from)?; - integral_int.try_into().map_err(JsError::from) + .map_err(JsError::from) }) .transpose()?; @@ -1056,10 +1054,9 @@ pub(crate) fn to_temporal_partial_duration( .get(js_string!("microseconds"), context)? .map(|v| { let finite = v.to_finitef64(context)?; - let integral_int = finite + finite .as_integer_if_integral::() - .map_err(JsError::from)?; - integral_int.try_into().map_err(JsError::from) + .map_err(JsError::from) }) .transpose()?; @@ -1069,10 +1066,9 @@ pub(crate) fn to_temporal_partial_duration( .get(js_string!("milliseconds"), context)? .map(|v| { let finite = v.to_finitef64(context)?; - let integral_int = finite + finite .as_integer_if_integral::() - .map_err(JsError::from)?; - integral_int.try_into().map_err(JsError::from) + .map_err(JsError::from) }) .transpose()?; @@ -1082,10 +1078,9 @@ pub(crate) fn to_temporal_partial_duration( .get(js_string!("minutes"), context)? .map(|v| { let finite = v.to_finitef64(context)?; - let integral_int = finite + finite .as_integer_if_integral::() - .map_err(JsError::from)?; - integral_int.try_into().map_err(JsError::from) + .map_err(JsError::from) }) .transpose()?; @@ -1095,10 +1090,9 @@ pub(crate) fn to_temporal_partial_duration( .get(js_string!("months"), context)? .map(|v| { let finite = v.to_finitef64(context)?; - let integral_int = finite + finite .as_integer_if_integral::() - .map_err(JsError::from)?; - integral_int.try_into().map_err(JsError::from) + .map_err(JsError::from) }) .transpose()?; @@ -1108,10 +1102,9 @@ pub(crate) fn to_temporal_partial_duration( .get(js_string!("nanoseconds"), context)? .map(|v| { let finite = v.to_finitef64(context)?; - let integral_int = finite + finite .as_integer_if_integral::() - .map_err(JsError::from)?; - integral_int.try_into().map_err(JsError::from) + .map_err(JsError::from) }) .transpose()?; @@ -1121,10 +1114,9 @@ pub(crate) fn to_temporal_partial_duration( .get(js_string!("seconds"), context)? .map(|v| { let finite = v.to_finitef64(context)?; - let integral_int = finite + finite .as_integer_if_integral::() - .map_err(JsError::from)?; - integral_int.try_into().map_err(JsError::from) + .map_err(JsError::from) }) .transpose()?; @@ -1134,10 +1126,9 @@ pub(crate) fn to_temporal_partial_duration( .get(js_string!("weeks"), context)? .map(|v| { let finite = v.to_finitef64(context)?; - let integral_int = finite + finite .as_integer_if_integral::() - .map_err(JsError::from)?; - integral_int.try_into().map_err(JsError::from) + .map_err(JsError::from) }) .transpose()?; @@ -1147,10 +1138,9 @@ pub(crate) fn to_temporal_partial_duration( .get(js_string!("years"), context)? .map(|v| { let finite = v.to_finitef64(context)?; - let integral_int = finite + finite .as_integer_if_integral::() - .map_err(JsError::from)?; - integral_int.try_into().map_err(JsError::from) + .map_err(JsError::from) }) .transpose()?; diff --git a/core/engine/src/builtins/temporal/plain_date_time/mod.rs b/core/engine/src/builtins/temporal/plain_date_time/mod.rs index 19207d5ea9d..dad513b3a94 100644 --- a/core/engine/src/builtins/temporal/plain_date_time/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date_time/mod.rs @@ -299,7 +299,7 @@ impl IntrinsicObject for PlainDateTime { .static_method(Self::from, js_string!("from"), 1) .static_method(Self::compare, js_string!("compare"), 2) .method(Self::with, js_string!("with"), 1) - .method(Self::with_plain_time, js_string!("withPlainTime"), 1) + .method(Self::with_plain_time, js_string!("withPlainTime"), 0) .method(Self::with_calendar, js_string!("withCalendar"), 1) .method(Self::add, js_string!("add"), 1) .method(Self::subtract, js_string!("subtract"), 1) diff --git a/core/engine/src/builtins/temporal/zoneddatetime/mod.rs b/core/engine/src/builtins/temporal/zoneddatetime/mod.rs index 86c671cd279..a6036f92588 100644 --- a/core/engine/src/builtins/temporal/zoneddatetime/mod.rs +++ b/core/engine/src/builtins/temporal/zoneddatetime/mod.rs @@ -22,7 +22,8 @@ use cow_utils::CowUtils; use temporal_rs::{ options::{ ArithmeticOverflow, Disambiguation, DisplayCalendar, DisplayOffset, DisplayTimeZone, - OffsetDisambiguation, RoundingMode, ToStringRoundingOptions, Unit, + OffsetDisambiguation, RoundingIncrement, RoundingMode, RoundingOptions, + ToStringRoundingOptions, Unit, }, partial::{PartialDate, PartialTime, PartialZonedDateTime}, provider::{TimeZoneProvider, TransitionDirection}, @@ -33,7 +34,7 @@ use super::{ calendar::{get_temporal_calendar_slot_value_with_default, to_temporal_calendar_slot_value}, create_temporal_date, create_temporal_datetime, create_temporal_duration, create_temporal_instant, create_temporal_time, is_partial_temporal_object, - options::get_difference_settings, + options::{get_difference_settings, get_temporal_unit, TemporalUnitGroup}, to_temporal_duration, to_temporal_time, }; @@ -903,18 +904,22 @@ impl ZonedDateTime { // 19. Let resolvedOptions be ? GetOptionsObject(options). let resolved_options = get_options_object(args.get_or_undefined(1))?; // 20. Let disambiguation be ? GetTemporalDisambiguationOption(resolvedOptions). - let _disambiguation = - get_option::(&resolved_options, js_string!("disambiguation"), context)? - .unwrap_or_default(); + let disambiguation = + get_option::(&resolved_options, js_string!("disambiguation"), context)?; // 21. Let offset be ? GetTemporalOffsetOption(resolvedOptions, prefer). - let _offset = - get_option::(&resolved_options, js_string!("offset"), context)? - .unwrap_or(OffsetDisambiguation::Prefer); + let offset = + get_option::(&resolved_options, js_string!("offset"), context)?; // 22. Let overflow be ? GetTemporalOverflowOption(resolvedOptions). - let _overflow = + let overflow = get_option::(&resolved_options, js_string!("overflow"), context)?; - let result = zdt.inner.with(partial)?; + let result = zdt.inner.with( + partial, + disambiguation, + offset, + overflow, + context.tz_provider(), + )?; create_temporal_zoneddatetime(result, None, context).map(Into::into) } @@ -1055,17 +1060,71 @@ impl ZonedDateTime { } /// 6.3.39 `Temporal.ZonedDateTime.prototype.round ( roundTo )` - fn round(this: &JsValue, _args: &[JsValue], _context: &mut Context) -> JsResult { - let _zdt = this + fn round(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + // 1. Let zonedDateTime be the this value. + // 2. Perform ? RequireInternalSlot(zonedDateTime, [[InitializedTemporalZonedDateTime]]). + let zdt = this .as_object() .and_then(JsObject::downcast_ref::) .ok_or_else(|| { JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.") })?; - Err(JsNativeError::error() - .with_message("Not yet implemented.") - .into()) + let round_to = match args.first().map(JsValue::variant) { + // 3. If roundTo is undefined, then + None | Some(JsVariant::Undefined) => { + // a. Throw a TypeError exception. + return Err(JsNativeError::typ() + .with_message("roundTo cannot be undefined.") + .into()); + } + // 4. If Type(roundTo) is String, then + Some(JsVariant::String(rt)) => { + // a. Let paramString be roundTo. + let param_string = rt.clone(); + // b. Set roundTo to OrdinaryObjectCreate(null). + let new_round_to = JsObject::with_null_proto(); + // c. Perform ! CreateDataPropertyOrThrow(roundTo, "smallestUnit", paramString). + new_round_to.create_data_property_or_throw( + js_string!("smallestUnit"), + param_string, + context, + )?; + new_round_to + } + // 5. Else, + Some(round_to) => { + // a. Set roundTo to ? GetOptionsObject(roundTo). + get_options_object(&JsValue::from(round_to))? + } + }; + + // 6. NOTE: The following steps read options and perform independent validation + // in alphabetical order (GetRoundingIncrementOption reads "roundingIncrement" + // and GetRoundingModeOption reads "roundingMode"). + let mut options = RoundingOptions::default(); + + // 7. Let roundingIncrement be ? GetRoundingIncrementOption(roundTo). + options.increment = + get_option::(&round_to, js_string!("roundingIncrement"), context)?; + + // 8. Let roundingMode be ? GetRoundingModeOption(roundTo, half-expand). + options.rounding_mode = + get_option::(&round_to, js_string!("roundingMode"), context)?; + + // 9. Let smallestUnit be ? GetTemporalUnitValuedOption(roundTo, "smallestUnit", time, required, « day »). + options.smallest_unit = get_temporal_unit( + &round_to, + js_string!("smallestUnit"), + TemporalUnitGroup::Time, + Some(vec![Unit::Day]), + context, + )?; + + let result = zdt + .inner + .round_with_provider(options, context.tz_provider())?; + create_temporal_zoneddatetime(result, None, context).map(Into::into) } /// 6.3.40 `Temporal.ZonedDateTime.prototype.equals ( other )`