diff --git a/Cargo.lock b/Cargo.lock index 6639c118a89..3df939d6943 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3551,7 +3551,7 @@ checksum = "42a4d50cdb458045afc8131fd91b64904da29548bcb63c7236e0844936c13078" [[package]] name = "temporal_rs" version = "0.0.4" -source = "git+https://github.com/boa-dev/temporal.git?rev=c61468264e27bed14bd7717f2153a7178e2dfe5f#c61468264e27bed14bd7717f2153a7178e2dfe5f" +source = "git+https://github.com/boa-dev/temporal.git?rev=cb10eecbd68a5249f5f60f08ba9e09d2a24040a9#cb10eecbd68a5249f5f60f08ba9e09d2a24040a9" dependencies = [ "combine", "iana-time-zone", diff --git a/Cargo.toml b/Cargo.toml index d0d42315ac9..ae3548275ee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,7 +113,7 @@ intrusive-collections = "0.9.7" cfg-if = "1.0.0" either = "1.13.0" sys-locale = "0.3.2" -temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "c61468264e27bed14bd7717f2153a7178e2dfe5f", features = ["tzdb"] } +temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "cb10eecbd68a5249f5f60f08ba9e09d2a24040a9", features = ["tzdb"] } web-time = "1.1.0" criterion = "0.5.1" float-cmp = "0.10.0" diff --git a/core/engine/src/bigint.rs b/core/engine/src/bigint.rs index 43d97afb913..041d161069b 100644 --- a/core/engine/src/bigint.rs +++ b/core/engine/src/bigint.rs @@ -80,6 +80,15 @@ impl JsBigInt { self.inner.to_f64().unwrap_or(f64::INFINITY) } + /// Converts the `BigInt` to a i128 type. + /// + /// Returns `i128::MAX` if the `BigInt` is too big. + #[inline] + #[must_use] + pub fn to_i128(&self) -> i128 { + self.inner.to_i128().unwrap_or(i128::MAX) + } + /// Converts a string to a `BigInt` with the specified radix. #[inline] #[must_use] diff --git a/core/engine/src/builtins/temporal/plain_date/mod.rs b/core/engine/src/builtins/temporal/plain_date/mod.rs index a008fb98217..5a6c8b3c376 100644 --- a/core/engine/src/builtins/temporal/plain_date/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date/mod.rs @@ -22,7 +22,7 @@ use boa_profiler::Profiler; use temporal_rs::{ options::{ArithmeticOverflow, DisplayCalendar}, partial::PartialDate, - PlainDate as InnerDate, TinyAsciiStr, + Calendar, PlainDate as InnerDate, TinyAsciiStr, }; use super::{ @@ -258,7 +258,17 @@ impl BuiltInConstructor for PlainDate { .get_or_undefined(2) .to_finitef64(context)? .as_integer_with_truncation::(); - let calendar_slot = to_temporal_calendar_slot_value(args.get_or_undefined(3))?; + let calendar_slot = args + .get_or_undefined(3) + .map(|s| { + s.as_string() + .map(JsString::to_std_string_lossy) + .ok_or_else(|| JsNativeError::typ().with_message("calendar must be a string.")) + }) + .transpose()? + .map(|s| Calendar::from_utf8(s.as_bytes())) + .transpose()? + .unwrap_or_default(); let inner = InnerDate::try_new(year, month, day, calendar_slot)?; 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 462e3d3d038..50ff0fa13d4 100644 --- a/core/engine/src/builtins/temporal/plain_date_time/mod.rs +++ b/core/engine/src/builtins/temporal/plain_date_time/mod.rs @@ -29,7 +29,7 @@ use temporal_rs::{ TemporalRoundingMode, TemporalUnit, ToStringRoundingOptions, }, partial::PartialDateTime, - PlainDateTime as InnerDateTime, PlainTime, + Calendar, PlainDateTime as InnerDateTime, PlainTime, }; use super::{ @@ -370,7 +370,17 @@ impl BuiltInConstructor for PlainDateTime { Ok(finite.as_integer_with_truncation::()) })?; - let calendar_slot = to_temporal_calendar_slot_value(args.get_or_undefined(9))?; + let calendar_slot = args + .get_or_undefined(9) + .map(|s| { + s.as_string() + .map(JsString::to_std_string_lossy) + .ok_or_else(|| JsNativeError::typ().with_message("calendar must be a string.")) + }) + .transpose()? + .map(|s| Calendar::from_utf8(s.as_bytes())) + .transpose()? + .unwrap_or_default(); let dt = InnerDateTime::new( iso_year, diff --git a/core/engine/src/builtins/temporal/plain_month_day/mod.rs b/core/engine/src/builtins/temporal/plain_month_day/mod.rs index b874fb5cf35..0e75000c939 100644 --- a/core/engine/src/builtins/temporal/plain_month_day/mod.rs +++ b/core/engine/src/builtins/temporal/plain_month_day/mod.rs @@ -21,7 +21,7 @@ use boa_profiler::Profiler; use temporal_rs::{ options::{ArithmeticOverflow, DisplayCalendar}, partial::PartialDate, - PlainMonthDay as InnerMonthDay, TinyAsciiStr, + Calendar, PlainMonthDay as InnerMonthDay, TinyAsciiStr, }; use super::{calendar::to_temporal_calendar_slot_value, DateTimeValues}; @@ -134,7 +134,18 @@ impl BuiltInConstructor for PlainMonthDay { .to_finitef64(context)? .as_integer_with_truncation::(); - let calendar = to_temporal_calendar_slot_value(args.get_or_undefined(2))?; + let calendar = args + .get_or_undefined(2) + .map(|s| { + s.as_string() + .map(JsString::to_std_string_lossy) + .ok_or_else(|| JsNativeError::typ().with_message("calendar must be a string.")) + }) + .transpose()? + .map(|s| Calendar::from_utf8(s.as_bytes())) + .transpose()? + .unwrap_or_default(); + let inner = InnerMonthDay::new_with_overflow( m, d, diff --git a/core/engine/src/builtins/temporal/plain_year_month/mod.rs b/core/engine/src/builtins/temporal/plain_year_month/mod.rs index 859d7caa193..2431a0722f6 100644 --- a/core/engine/src/builtins/temporal/plain_year_month/mod.rs +++ b/core/engine/src/builtins/temporal/plain_year_month/mod.rs @@ -21,7 +21,7 @@ use boa_profiler::Profiler; use temporal_rs::{ options::{ArithmeticOverflow, DisplayCalendar}, - Duration, PlainYearMonth as InnerYearMonth, + Calendar, Duration, PlainYearMonth as InnerYearMonth, }; use super::{calendar::to_temporal_calendar_slot_value, to_temporal_duration, DateTimeValues}; @@ -187,7 +187,17 @@ impl BuiltInConstructor for PlainYearMonth { .as_integer_with_truncation::(); // 5. Let calendar be ? ToTemporalCalendarSlotValue(calendarLike, "iso8601"). - let calendar = to_temporal_calendar_slot_value(args.get_or_undefined(2))?; + let calendar = args + .get_or_undefined(2) + .map(|s| { + s.as_string() + .map(JsString::to_std_string_lossy) + .ok_or_else(|| JsNativeError::typ().with_message("calendar must be a string.")) + }) + .transpose()? + .map(|s| Calendar::from_utf8(s.as_bytes())) + .transpose()? + .unwrap_or_default(); // 6. Let ref be ? ToIntegerWithTruncation(referenceISODay). let ref_day = args diff --git a/core/engine/src/builtins/temporal/zoneddatetime/mod.rs b/core/engine/src/builtins/temporal/zoneddatetime/mod.rs index 8f5d374e5c8..cb2e8d47765 100644 --- a/core/engine/src/builtins/temporal/zoneddatetime/mod.rs +++ b/core/engine/src/builtins/temporal/zoneddatetime/mod.rs @@ -1,5 +1,3 @@ -use std::str::FromStr; - use crate::{ builtins::{ options::{get_option, get_options_object}, @@ -13,13 +11,12 @@ use crate::{ realm::Realm, string::StaticJsStrings, value::{IntoOrUndefined, PreferredType}, - Context, JsArgs, JsBigInt, JsData, JsError, JsNativeError, JsObject, JsResult, JsString, - JsSymbol, JsValue, JsVariant, + Context, JsArgs, JsBigInt, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol, + JsValue, JsVariant, }; use boa_gc::{Finalize, Trace}; use boa_profiler::Profiler; use cow_utils::CowUtils; -use num_traits::ToPrimitive; use temporal_rs::{ options::{ ArithmeticOverflow, Disambiguation, DisplayCalendar, DisplayOffset, DisplayTimeZone, @@ -31,7 +28,8 @@ use temporal_rs::{ use super::{ calendar::to_temporal_calendar_slot_value, create_temporal_date, create_temporal_datetime, - create_temporal_instant, create_temporal_time, to_partial_date_record, to_partial_time_record, + create_temporal_duration, create_temporal_instant, create_temporal_time, + options::get_difference_settings, to_partial_date_record, to_partial_time_record, to_temporal_duration, to_temporal_time, }; @@ -329,6 +327,8 @@ impl IntrinsicObject for ZonedDateTime { .method(Self::with_calendar, js_string!("withCalendar"), 1) .method(Self::add, js_string!("add"), 1) .method(Self::subtract, js_string!("subtract"), 1) + .method(Self::until, js_string!("until"), 1) + .method(Self::since, js_string!("since"), 1) .method(Self::equals, js_string!("equals"), 1) .method(Self::to_string, js_string!("toString"), 0) .method(Self::to_json, js_string!("toJSON"), 0) @@ -367,14 +367,8 @@ impl BuiltInConstructor for ZonedDateTime { .into()); } // 2. Set epochNanoseconds to ? ToBigInt(epochNanoseconds). - let epoch_nanos = args.get_or_undefined(0).to_bigint(context)?; // 3. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception. - // TODO: Better primitive for handling epochNanoseconds is needed in temporal_rs - let Some(nanos) = epoch_nanos.to_f64().to_i128() else { - return Err(JsNativeError::range() - .with_message("epochNanoseconds exceeded valid range.") - .into()); - }; + let epoch_nanos = args.get_or_undefined(0).to_bigint(context)?; // 4. If timeZone is not a String, throw a TypeError exception. let Some(timezone_str) = args.get_or_undefined(1).as_string() else { @@ -399,21 +393,18 @@ impl BuiltInConstructor for ZonedDateTime { // 9. If calendar is not a String, throw a TypeError exception. // 10. Set calendar to ? CanonicalizeCalendar(calendar). let calendar = args - .get(2) - .map(|v| { - if let Some(calendar_str) = v.as_string() { - Calendar::from_str(&calendar_str.to_std_string_escaped()) - .map_err(Into::::into) - } else { - Err(JsNativeError::typ() - .with_message("calendar must be a string.") - .into()) - } + .get_or_undefined(2) + .map(|s| { + s.as_string() + .map(JsString::to_std_string_lossy) + .ok_or_else(|| JsNativeError::typ().with_message("calendar must be a string.")) }) .transpose()? + .map(|s| Calendar::from_utf8(s.as_bytes())) + .transpose()? .unwrap_or_default(); - let inner = ZonedDateTimeInner::try_new(nanos, calendar, timezone)?; + let inner = ZonedDateTimeInner::try_new(epoch_nanos.to_i128(), calendar, timezone)?; // 11. Return ? CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar, NewTarget). create_temporal_zoneddatetime(inner, Some(new_target), context).map(Into::into) @@ -893,13 +884,10 @@ impl ZonedDateTime { let options = get_options_object(args.get_or_undefined(1))?; let overflow = get_option::(&options, js_string!("overflow"), context)?; - create_temporal_zoneddatetime( - zdt.inner - .add_with_provider(&duration, overflow, context.tz_provider())?, - None, - context, - ) - .map(Into::into) + let result = zdt + .inner + .add_with_provider(&duration, overflow, context.tz_provider())?; + create_temporal_zoneddatetime(result, None, context).map(Into::into) } /// 6.3.36 `Temporal.ZonedDateTime.prototype.subtract ( temporalDurationLike [ , options ] )` @@ -916,13 +904,48 @@ impl ZonedDateTime { let options = get_options_object(args.get_or_undefined(1))?; let overflow = get_option::(&options, js_string!("overflow"), context)?; - create_temporal_zoneddatetime( + let result = zdt.inner - .subtract_with_provider(&duration, overflow, context.tz_provider())?, - None, - context, - ) - .map(Into::into) + .subtract_with_provider(&duration, overflow, context.tz_provider())?; + create_temporal_zoneddatetime(result, None, context).map(Into::into) + } + + fn since(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + 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.") + })?; + + let other = to_temporal_zoneddatetime(args.get_or_undefined(0), None, context)?; + + let options = get_options_object(args.get_or_undefined(1))?; + let settings = get_difference_settings(&options, context)?; + + let result = zdt + .inner + .since_with_provider(&other, settings, context.tz_provider())?; + create_temporal_duration(result, None, context).map(Into::into) + } + + fn until(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult { + 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.") + })?; + + let other = to_temporal_zoneddatetime(args.get_or_undefined(0), None, context)?; + + let options = get_options_object(args.get_or_undefined(1))?; + let settings = get_difference_settings(&options, context)?; + + let result = zdt + .inner + .until_with_provider(&other, settings, context.tz_provider())?; + create_temporal_duration(result, None, context).map(Into::into) } /// 6.3.40 `Temporal.ZonedDateTime.prototype.equals ( other )`