Skip to content

Commit e34e525

Browse files
authored
Implement since and until methods for ZonedDateTime (#4136)
* Implement since and until methods for ZonedDateTime * Strictly parse calendar in constructors * cargo fmt
1 parent 6eb7ba1 commit e34e525

File tree

8 files changed

+120
-47
lines changed

8 files changed

+120
-47
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,7 @@ intrusive-collections = "0.9.7"
113113
cfg-if = "1.0.0"
114114
either = "1.13.0"
115115
sys-locale = "0.3.2"
116-
temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "c61468264e27bed14bd7717f2153a7178e2dfe5f", features = ["tzdb"] }
116+
temporal_rs = { git = "https://github.com/boa-dev/temporal.git", rev = "cb10eecbd68a5249f5f60f08ba9e09d2a24040a9", features = ["tzdb"] }
117117
web-time = "1.1.0"
118118
criterion = "0.5.1"
119119
float-cmp = "0.10.0"

core/engine/src/bigint.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,15 @@ impl JsBigInt {
8080
self.inner.to_f64().unwrap_or(f64::INFINITY)
8181
}
8282

83+
/// Converts the `BigInt` to a i128 type.
84+
///
85+
/// Returns `i128::MAX` if the `BigInt` is too big.
86+
#[inline]
87+
#[must_use]
88+
pub fn to_i128(&self) -> i128 {
89+
self.inner.to_i128().unwrap_or(i128::MAX)
90+
}
91+
8392
/// Converts a string to a `BigInt` with the specified radix.
8493
#[inline]
8594
#[must_use]

core/engine/src/builtins/temporal/plain_date/mod.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use boa_profiler::Profiler;
2222
use temporal_rs::{
2323
options::{ArithmeticOverflow, DisplayCalendar},
2424
partial::PartialDate,
25-
PlainDate as InnerDate, TinyAsciiStr,
25+
Calendar, PlainDate as InnerDate, TinyAsciiStr,
2626
};
2727

2828
use super::{
@@ -258,7 +258,17 @@ impl BuiltInConstructor for PlainDate {
258258
.get_or_undefined(2)
259259
.to_finitef64(context)?
260260
.as_integer_with_truncation::<u8>();
261-
let calendar_slot = to_temporal_calendar_slot_value(args.get_or_undefined(3))?;
261+
let calendar_slot = args
262+
.get_or_undefined(3)
263+
.map(|s| {
264+
s.as_string()
265+
.map(JsString::to_std_string_lossy)
266+
.ok_or_else(|| JsNativeError::typ().with_message("calendar must be a string."))
267+
})
268+
.transpose()?
269+
.map(|s| Calendar::from_utf8(s.as_bytes()))
270+
.transpose()?
271+
.unwrap_or_default();
262272

263273
let inner = InnerDate::try_new(year, month, day, calendar_slot)?;
264274

core/engine/src/builtins/temporal/plain_date_time/mod.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ use temporal_rs::{
2929
TemporalRoundingMode, TemporalUnit, ToStringRoundingOptions,
3030
},
3131
partial::PartialDateTime,
32-
PlainDateTime as InnerDateTime, PlainTime,
32+
Calendar, PlainDateTime as InnerDateTime, PlainTime,
3333
};
3434

3535
use super::{
@@ -370,7 +370,17 @@ impl BuiltInConstructor for PlainDateTime {
370370
Ok(finite.as_integer_with_truncation::<u16>())
371371
})?;
372372

373-
let calendar_slot = to_temporal_calendar_slot_value(args.get_or_undefined(9))?;
373+
let calendar_slot = args
374+
.get_or_undefined(9)
375+
.map(|s| {
376+
s.as_string()
377+
.map(JsString::to_std_string_lossy)
378+
.ok_or_else(|| JsNativeError::typ().with_message("calendar must be a string."))
379+
})
380+
.transpose()?
381+
.map(|s| Calendar::from_utf8(s.as_bytes()))
382+
.transpose()?
383+
.unwrap_or_default();
374384

375385
let dt = InnerDateTime::new(
376386
iso_year,

core/engine/src/builtins/temporal/plain_month_day/mod.rs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use boa_profiler::Profiler;
2121
use temporal_rs::{
2222
options::{ArithmeticOverflow, DisplayCalendar},
2323
partial::PartialDate,
24-
PlainMonthDay as InnerMonthDay, TinyAsciiStr,
24+
Calendar, PlainMonthDay as InnerMonthDay, TinyAsciiStr,
2525
};
2626

2727
use super::{calendar::to_temporal_calendar_slot_value, DateTimeValues};
@@ -134,7 +134,18 @@ impl BuiltInConstructor for PlainMonthDay {
134134
.to_finitef64(context)?
135135
.as_integer_with_truncation::<u8>();
136136

137-
let calendar = to_temporal_calendar_slot_value(args.get_or_undefined(2))?;
137+
let calendar = args
138+
.get_or_undefined(2)
139+
.map(|s| {
140+
s.as_string()
141+
.map(JsString::to_std_string_lossy)
142+
.ok_or_else(|| JsNativeError::typ().with_message("calendar must be a string."))
143+
})
144+
.transpose()?
145+
.map(|s| Calendar::from_utf8(s.as_bytes()))
146+
.transpose()?
147+
.unwrap_or_default();
148+
138149
let inner = InnerMonthDay::new_with_overflow(
139150
m,
140151
d,

core/engine/src/builtins/temporal/plain_year_month/mod.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use boa_profiler::Profiler;
2121

2222
use temporal_rs::{
2323
options::{ArithmeticOverflow, DisplayCalendar},
24-
Duration, PlainYearMonth as InnerYearMonth,
24+
Calendar, Duration, PlainYearMonth as InnerYearMonth,
2525
};
2626

2727
use super::{calendar::to_temporal_calendar_slot_value, to_temporal_duration, DateTimeValues};
@@ -187,7 +187,17 @@ impl BuiltInConstructor for PlainYearMonth {
187187
.as_integer_with_truncation::<u8>();
188188

189189
// 5. Let calendar be ? ToTemporalCalendarSlotValue(calendarLike, "iso8601").
190-
let calendar = to_temporal_calendar_slot_value(args.get_or_undefined(2))?;
190+
let calendar = args
191+
.get_or_undefined(2)
192+
.map(|s| {
193+
s.as_string()
194+
.map(JsString::to_std_string_lossy)
195+
.ok_or_else(|| JsNativeError::typ().with_message("calendar must be a string."))
196+
})
197+
.transpose()?
198+
.map(|s| Calendar::from_utf8(s.as_bytes()))
199+
.transpose()?
200+
.unwrap_or_default();
191201

192202
// 6. Let ref be ? ToIntegerWithTruncation(referenceISODay).
193203
let ref_day = args

core/engine/src/builtins/temporal/zoneddatetime/mod.rs

Lines changed: 60 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::str::FromStr;
2-
31
use crate::{
42
builtins::{
53
options::{get_option, get_options_object},
@@ -13,13 +11,12 @@ use crate::{
1311
realm::Realm,
1412
string::StaticJsStrings,
1513
value::{IntoOrUndefined, PreferredType},
16-
Context, JsArgs, JsBigInt, JsData, JsError, JsNativeError, JsObject, JsResult, JsString,
17-
JsSymbol, JsValue, JsVariant,
14+
Context, JsArgs, JsBigInt, JsData, JsNativeError, JsObject, JsResult, JsString, JsSymbol,
15+
JsValue, JsVariant,
1816
};
1917
use boa_gc::{Finalize, Trace};
2018
use boa_profiler::Profiler;
2119
use cow_utils::CowUtils;
22-
use num_traits::ToPrimitive;
2320
use temporal_rs::{
2421
options::{
2522
ArithmeticOverflow, Disambiguation, DisplayCalendar, DisplayOffset, DisplayTimeZone,
@@ -31,7 +28,8 @@ use temporal_rs::{
3128

3229
use super::{
3330
calendar::to_temporal_calendar_slot_value, create_temporal_date, create_temporal_datetime,
34-
create_temporal_instant, create_temporal_time, to_partial_date_record, to_partial_time_record,
31+
create_temporal_duration, create_temporal_instant, create_temporal_time,
32+
options::get_difference_settings, to_partial_date_record, to_partial_time_record,
3533
to_temporal_duration, to_temporal_time,
3634
};
3735

@@ -329,6 +327,8 @@ impl IntrinsicObject for ZonedDateTime {
329327
.method(Self::with_calendar, js_string!("withCalendar"), 1)
330328
.method(Self::add, js_string!("add"), 1)
331329
.method(Self::subtract, js_string!("subtract"), 1)
330+
.method(Self::until, js_string!("until"), 1)
331+
.method(Self::since, js_string!("since"), 1)
332332
.method(Self::equals, js_string!("equals"), 1)
333333
.method(Self::to_string, js_string!("toString"), 0)
334334
.method(Self::to_json, js_string!("toJSON"), 0)
@@ -367,14 +367,8 @@ impl BuiltInConstructor for ZonedDateTime {
367367
.into());
368368
}
369369
// 2. Set epochNanoseconds to ? ToBigInt(epochNanoseconds).
370-
let epoch_nanos = args.get_or_undefined(0).to_bigint(context)?;
371370
// 3. If IsValidEpochNanoseconds(epochNanoseconds) is false, throw a RangeError exception.
372-
// TODO: Better primitive for handling epochNanoseconds is needed in temporal_rs
373-
let Some(nanos) = epoch_nanos.to_f64().to_i128() else {
374-
return Err(JsNativeError::range()
375-
.with_message("epochNanoseconds exceeded valid range.")
376-
.into());
377-
};
371+
let epoch_nanos = args.get_or_undefined(0).to_bigint(context)?;
378372

379373
// 4. If timeZone is not a String, throw a TypeError exception.
380374
let Some(timezone_str) = args.get_or_undefined(1).as_string() else {
@@ -399,21 +393,18 @@ impl BuiltInConstructor for ZonedDateTime {
399393
// 9. If calendar is not a String, throw a TypeError exception.
400394
// 10. Set calendar to ? CanonicalizeCalendar(calendar).
401395
let calendar = args
402-
.get(2)
403-
.map(|v| {
404-
if let Some(calendar_str) = v.as_string() {
405-
Calendar::from_str(&calendar_str.to_std_string_escaped())
406-
.map_err(Into::<JsError>::into)
407-
} else {
408-
Err(JsNativeError::typ()
409-
.with_message("calendar must be a string.")
410-
.into())
411-
}
396+
.get_or_undefined(2)
397+
.map(|s| {
398+
s.as_string()
399+
.map(JsString::to_std_string_lossy)
400+
.ok_or_else(|| JsNativeError::typ().with_message("calendar must be a string."))
412401
})
413402
.transpose()?
403+
.map(|s| Calendar::from_utf8(s.as_bytes()))
404+
.transpose()?
414405
.unwrap_or_default();
415406

416-
let inner = ZonedDateTimeInner::try_new(nanos, calendar, timezone)?;
407+
let inner = ZonedDateTimeInner::try_new(epoch_nanos.to_i128(), calendar, timezone)?;
417408

418409
// 11. Return ? CreateTemporalZonedDateTime(epochNanoseconds, timeZone, calendar, NewTarget).
419410
create_temporal_zoneddatetime(inner, Some(new_target), context).map(Into::into)
@@ -893,13 +884,10 @@ impl ZonedDateTime {
893884
let options = get_options_object(args.get_or_undefined(1))?;
894885
let overflow = get_option::<ArithmeticOverflow>(&options, js_string!("overflow"), context)?;
895886

896-
create_temporal_zoneddatetime(
897-
zdt.inner
898-
.add_with_provider(&duration, overflow, context.tz_provider())?,
899-
None,
900-
context,
901-
)
902-
.map(Into::into)
887+
let result = zdt
888+
.inner
889+
.add_with_provider(&duration, overflow, context.tz_provider())?;
890+
create_temporal_zoneddatetime(result, None, context).map(Into::into)
903891
}
904892

905893
/// 6.3.36 `Temporal.ZonedDateTime.prototype.subtract ( temporalDurationLike [ , options ] )`
@@ -916,13 +904,48 @@ impl ZonedDateTime {
916904
let options = get_options_object(args.get_or_undefined(1))?;
917905
let overflow = get_option::<ArithmeticOverflow>(&options, js_string!("overflow"), context)?;
918906

919-
create_temporal_zoneddatetime(
907+
let result =
920908
zdt.inner
921-
.subtract_with_provider(&duration, overflow, context.tz_provider())?,
922-
None,
923-
context,
924-
)
925-
.map(Into::into)
909+
.subtract_with_provider(&duration, overflow, context.tz_provider())?;
910+
create_temporal_zoneddatetime(result, None, context).map(Into::into)
911+
}
912+
913+
fn since(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
914+
let zdt = this
915+
.as_object()
916+
.and_then(JsObject::downcast_ref::<Self>)
917+
.ok_or_else(|| {
918+
JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.")
919+
})?;
920+
921+
let other = to_temporal_zoneddatetime(args.get_or_undefined(0), None, context)?;
922+
923+
let options = get_options_object(args.get_or_undefined(1))?;
924+
let settings = get_difference_settings(&options, context)?;
925+
926+
let result = zdt
927+
.inner
928+
.since_with_provider(&other, settings, context.tz_provider())?;
929+
create_temporal_duration(result, None, context).map(Into::into)
930+
}
931+
932+
fn until(this: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
933+
let zdt = this
934+
.as_object()
935+
.and_then(JsObject::downcast_ref::<Self>)
936+
.ok_or_else(|| {
937+
JsNativeError::typ().with_message("the this object must be a ZonedDateTime object.")
938+
})?;
939+
940+
let other = to_temporal_zoneddatetime(args.get_or_undefined(0), None, context)?;
941+
942+
let options = get_options_object(args.get_or_undefined(1))?;
943+
let settings = get_difference_settings(&options, context)?;
944+
945+
let result = zdt
946+
.inner
947+
.until_with_provider(&other, settings, context.tz_provider())?;
948+
create_temporal_duration(result, None, context).map(Into::into)
926949
}
927950

928951
/// 6.3.40 `Temporal.ZonedDateTime.prototype.equals ( other )`

0 commit comments

Comments
 (0)