Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement since and until methods for ZonedDateTime #4136

Merged
merged 3 commits into from
Jan 20, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
9 changes: 9 additions & 0 deletions core/engine/src/bigint.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
14 changes: 12 additions & 2 deletions core/engine/src/builtins/temporal/plain_date/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -258,7 +258,17 @@ impl BuiltInConstructor for PlainDate {
.get_or_undefined(2)
.to_finitef64(context)?
.as_integer_with_truncation::<u8>();
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)?;

Expand Down
14 changes: 12 additions & 2 deletions core/engine/src/builtins/temporal/plain_date_time/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ use temporal_rs::{
TemporalRoundingMode, TemporalUnit, ToStringRoundingOptions,
},
partial::PartialDateTime,
PlainDateTime as InnerDateTime, PlainTime,
Calendar, PlainDateTime as InnerDateTime, PlainTime,
};

use super::{
Expand Down Expand Up @@ -370,7 +370,17 @@ impl BuiltInConstructor for PlainDateTime {
Ok(finite.as_integer_with_truncation::<u16>())
})?;

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,
Expand Down
15 changes: 13 additions & 2 deletions core/engine/src/builtins/temporal/plain_month_day/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -134,7 +134,18 @@ impl BuiltInConstructor for PlainMonthDay {
.to_finitef64(context)?
.as_integer_with_truncation::<u8>();

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,
Expand Down
14 changes: 12 additions & 2 deletions core/engine/src/builtins/temporal/plain_year_month/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -187,7 +187,17 @@ impl BuiltInConstructor for PlainYearMonth {
.as_integer_with_truncation::<u8>();

// 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
Expand Down
97 changes: 60 additions & 37 deletions core/engine/src/builtins/temporal/zoneddatetime/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::str::FromStr;

use crate::{
builtins::{
options::{get_option, get_options_object},
Expand All @@ -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,
Expand All @@ -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,
};

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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::<JsError>::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)
Expand Down Expand Up @@ -893,13 +884,10 @@ impl ZonedDateTime {
let options = get_options_object(args.get_or_undefined(1))?;
let overflow = get_option::<ArithmeticOverflow>(&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 ] )`
Expand All @@ -916,13 +904,48 @@ impl ZonedDateTime {
let options = get_options_object(args.get_or_undefined(1))?;
let overflow = get_option::<ArithmeticOverflow>(&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<JsValue> {
let zdt = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.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<JsValue> {
let zdt = this
.as_object()
.and_then(JsObject::downcast_ref::<Self>)
.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 )`
Expand Down
Loading