From 7dbb22a165b210bfa7190a3feb2f1bccdbf7c297 Mon Sep 17 00:00:00 2001 From: pjh456 <147148383@qq.com> Date: Wed, 14 Oct 2026 09:29:40 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20Temporal=20PlainDate=20=E4=B8=8E=20Plai?= =?UTF-8?q?nDateTime=20=E5=A2=9E=E5=8A=A0=E6=8D=A2=E6=A7=BD=E4=B8=8E?= =?UTF-8?q?=E7=BA=AF=E6=8B=B7=E8=B4=9D=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Cargo.lock | 42 ++--- Cargo.toml | 2 +- .../oxide_builtins/src/temporal/plain_date.rs | 91 +++++++++- .../src/temporal/plain_date_time.rs | 65 +++++++- crates/oxide_vm/src/bindings/bind_temporal.rs | 30 ++++ .../oxide_vm/tests/builtins_temporal_tests.rs | 155 ++++++++++++++++++ 6 files changed, 354 insertions(+), 31 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index afc22393..c5c025bf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1160,7 +1160,7 @@ dependencies = [ [[package]] name = "oxide_builtins" -version = "0.19.10" +version = "0.19.11" dependencies = [ "chrono", "fast-float", @@ -1185,7 +1185,7 @@ dependencies = [ [[package]] name = "oxide_bytecode" -version = "0.19.10" +version = "0.19.11" dependencies = [ "num-bigint", "oxide_log", @@ -1193,7 +1193,7 @@ dependencies = [ [[package]] name = "oxide_cfg" -version = "0.19.10" +version = "0.19.11" dependencies = [ "oxide_bytecode", "oxide_emit", @@ -1204,7 +1204,7 @@ dependencies = [ [[package]] name = "oxide_cli" -version = "0.19.10" +version = "0.19.11" dependencies = [ "ansi_term", "bumpalo", @@ -1228,7 +1228,7 @@ dependencies = [ [[package]] name = "oxide_code_cache" -version = "0.19.10" +version = "0.19.11" dependencies = [ "lru", "oxide_bytecode", @@ -1237,7 +1237,7 @@ dependencies = [ [[package]] name = "oxide_compiler" -version = "0.19.10" +version = "0.19.11" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1258,7 +1258,7 @@ dependencies = [ [[package]] name = "oxide_dce" -version = "0.19.10" +version = "0.19.11" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1272,7 +1272,7 @@ dependencies = [ [[package]] name = "oxide_emit" -version = "0.19.10" +version = "0.19.11" dependencies = [ "num-bigint", "oxc_allocator", @@ -1288,7 +1288,7 @@ dependencies = [ [[package]] name = "oxide_engine" -version = "0.19.10" +version = "0.19.11" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1300,7 +1300,7 @@ dependencies = [ [[package]] name = "oxide_fold" -version = "0.19.10" +version = "0.19.11" dependencies = [ "num-bigint", "oxide_bytecode", @@ -1312,7 +1312,7 @@ dependencies = [ [[package]] name = "oxide_ir" -version = "0.19.10" +version = "0.19.11" dependencies = [ "oxide_bytecode", "oxide_log", @@ -1321,7 +1321,7 @@ dependencies = [ [[package]] name = "oxide_kernel" -version = "0.19.10" +version = "0.19.11" dependencies = [ "bumpalo", "dashmap", @@ -1335,7 +1335,7 @@ dependencies = [ [[package]] name = "oxide_liveness" -version = "0.19.10" +version = "0.19.11" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1347,7 +1347,7 @@ dependencies = [ [[package]] name = "oxide_log" -version = "0.19.10" +version = "0.19.11" dependencies = [ "tracing", "tracing-appender", @@ -1356,11 +1356,11 @@ dependencies = [ [[package]] name = "oxide_net" -version = "0.19.10" +version = "0.19.11" [[package]] name = "oxide_parser" -version = "0.19.10" +version = "0.19.11" dependencies = [ "oxc_allocator", "oxc_ast", @@ -1375,7 +1375,7 @@ dependencies = [ [[package]] name = "oxide_regalloc" -version = "0.19.10" +version = "0.19.11" dependencies = [ "oxide_bytecode", "oxide_cfg", @@ -1391,7 +1391,7 @@ dependencies = [ [[package]] name = "oxide_runtime_api" -version = "0.19.10" +version = "0.19.11" dependencies = [ "num-bigint", "num-traits", @@ -1404,7 +1404,7 @@ dependencies = [ [[package]] name = "oxide_test262" -version = "0.19.10" +version = "0.19.11" dependencies = [ "oxide_bytecode", "oxide_compiler", @@ -1424,7 +1424,7 @@ dependencies = [ [[package]] name = "oxide_types" -version = "0.19.10" +version = "0.19.11" dependencies = [ "bumpalo", "dashmap", @@ -1437,7 +1437,7 @@ dependencies = [ [[package]] name = "oxide_vm" -version = "0.19.10" +version = "0.19.11" dependencies = [ "bumpalo", "chrono", diff --git a/Cargo.toml b/Cargo.toml index 4a149697..a0761b14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,7 +25,7 @@ members = [ ] [workspace.package] -version = "0.19.10" +version = "0.19.11" edition = "2021" license = "MIT" rust-version = "1.80" diff --git a/crates/oxide_builtins/src/temporal/plain_date.rs b/crates/oxide_builtins/src/temporal/plain_date.rs index bb2700c9..f3ab42f7 100644 --- a/crates/oxide_builtins/src/temporal/plain_date.rs +++ b/crates/oxide_builtins/src/temporal/plain_date.rs @@ -9,13 +9,13 @@ use super::common::native_try; use super::common::{ days_from_civil, days_in_month, ensure_plain_date, get_calendar_id, get_double_prop, initialize_temporal_receiver, is_ctor_call, parse_plain_date_string, plain_date_time_object_parts, receiver_obj, - reject_partial_object_with_calendar_or_time_zone, resolve_date_time_parts, temporal_calendar_id_strict, - temporal_number_component, temporal_option_string, temporal_option_value, temporal_overflow, - zoned_date_time_plain_parts, DateTimeParts, + reject_partial_object_with_calendar_or_time_zone, resolve_date_time_parts, temporal_calendar_id, + temporal_calendar_id_strict, temporal_number_component, temporal_option_string, temporal_option_value, + temporal_overflow, zoned_date_time_plain_parts, DateTimeParts, }; use super::difference::{difference_core, parse_difference_settings, IsoDateTime}; use super::duration::duration_like_values; -use super::make_plain_date; +use super::{make_plain_date, make_plain_date_time, make_plain_month_day, make_plain_year_month, plain_time_like_ns}; /// 校验 ISO 日期分量(month 1-12、day 按月份与闰年,不依赖 chrono 年份范围)。 pub(crate) fn valid_iso_date(year: i32, month: u32, day: u32) -> bool { @@ -599,6 +599,89 @@ pub fn plain_date_with(vm: &mut H, args: &[u8]) -> NativeResult { make_plain_date(vm, year, month, day, &get_calendar_id(obj, 3)) } +/// `Temporal.PlainDate.prototype.withCalendar(calendar)`:只换日历槽,日期分量不变。 +/// +/// # 步骤 +/// 1. receiver 品牌校验;缺参 / undefined 抛 TypeError。 +/// 2. 参数经 temporal_calendar_id(宽松版):字符串走白名单或 ISO 日期(-时间)串, +/// 六类 Temporal 对象直读日历槽(不触发 getter),其他对象 / 原始值抛 TypeError。 +/// 3. 读 receiver 槽 0-2 重建,日历槽替换。 +/// +/// # 边界与前提 +/// - 非法日历串 → RangeError;日历 ID 大小写折叠。 +pub fn plain_date_with_calendar(vm: &mut H, args: &[u8]) -> NativeResult { + let ptr = native_try!(receiver_obj(vm, args)); + let obj = unsafe { &*ptr }; + native_try!(ensure_plain_date(vm, obj)); + let calendar_value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + if calendar_value.is_undefined() { + return NativeResult::Err(crate::error::create_type_error(vm, "calendar is required")); + } + let calendar = native_try!(temporal_calendar_id(vm, calendar_value)).unwrap_or_else(|| "iso8601".to_string()); + make_plain_date( + vm, + get_double_prop(obj, 0) as i32, + get_double_prop(obj, 1) as u32, + get_double_prop(obj, 2) as u32, + &calendar, + ) +} + +/// `Temporal.PlainDate.prototype.toPlainDateTime(timeLike)`:日期分量 + 时间槽组合。 +/// +/// # 步骤 +/// 1. receiver 品牌校验;读槽 0-2 与日历槽 3 入本地(plain_time_like_ns 的 bag +/// 路径有用户调用窗口,receiver 槽须先于窗口读入)。 +/// 2. plain_time_like_ns 取当日纳秒(undefined → 午夜)。 +/// 3. valid_plain_date_time_range 严格不等式越界抛 RangeError;make_plain_date_time 重建。 +/// +/// # 边界与前提 +/// - 组合越 PlainDateTime 表示范围(min 午夜抛、min 加 1 纳秒过、max 全满过)→ RangeError。 +/// - 日历槽继承 receiver。 +pub fn plain_date_to_plain_date_time(vm: &mut H, args: &[u8]) -> NativeResult { + let ptr = native_try!(receiver_obj(vm, args)); + let obj = unsafe { &*ptr }; + native_try!(ensure_plain_date(vm, obj)); + let year = get_double_prop(obj, 0) as i32; + let month = get_double_prop(obj, 1) as u32; + let day = get_double_prop(obj, 2) as u32; + let calendar = get_calendar_id(obj, 3); + let time_like = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let time_ns = native_try!(plain_time_like_ns(vm, time_like)); + if !valid_plain_date_time_range(year, month, day, time_ns) { + return NativeResult::Err(crate::error::create_range_error(vm, "invalid date-time")); + } + make_plain_date_time(vm, year, month, day, time_ns, &calendar) +} + +/// `Temporal.PlainDate.prototype.toPlainMonthDay()`:纯拷贝月/日,参考年固定 1972。 +pub fn plain_date_to_plain_month_day(vm: &mut H, args: &[u8]) -> NativeResult { + let ptr = native_try!(receiver_obj(vm, args)); + let obj = unsafe { &*ptr }; + native_try!(ensure_plain_date(vm, obj)); + make_plain_month_day( + vm, + get_double_prop(obj, 1) as u32, + get_double_prop(obj, 2) as u32, + 1972, + &get_calendar_id(obj, 3), + ) +} + +/// `Temporal.PlainDate.prototype.toPlainYearMonth()`:纯拷贝年/月,参考日固定 1。 +pub fn plain_date_to_plain_year_month(vm: &mut H, args: &[u8]) -> NativeResult { + let ptr = native_try!(receiver_obj(vm, args)); + let obj = unsafe { &*ptr }; + native_try!(ensure_plain_date(vm, obj)); + make_plain_year_month( + vm, + get_double_prop(obj, 0) as i32, + get_double_prop(obj, 1) as u32, + 1, + &get_calendar_id(obj, 3), + ) +} + /// `Temporal.PlainDate.compare(a, b)`:静态比较,返回 -1/0/1。 pub fn plain_date_compare(vm: &mut H, args: &[u8]) -> NativeResult { let a_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; diff --git a/crates/oxide_builtins/src/temporal/plain_date_time.rs b/crates/oxide_builtins/src/temporal/plain_date_time.rs index f680e0e0..2cb23c1f 100644 --- a/crates/oxide_builtins/src/temporal/plain_date_time.rs +++ b/crates/oxide_builtins/src/temporal/plain_date_time.rs @@ -11,11 +11,11 @@ use super::{ duration_like_values, ensure_plain_date_time, format_iso_year, get_calendar_id, get_double_prop, initialize_temporal_receiver, instant_rounding_mode, is_ctor_call, is_leap_year_iso, make_plain_date, make_plain_date_time, make_plain_time, parse_difference_settings, parse_fractional_second_digits, - parse_temporal_string_impl, plain_date_time_object_parts, plain_time_components, receiver_obj, - reject_partial_object_with_calendar_or_time_zone, resolve_date_time_parts, round_instant_ns, - temporal_calendar_id_strict, temporal_number_component, temporal_option_number, temporal_option_string, - temporal_option_value, temporal_overflow, valid_iso_date, valid_plain_date_time_range, valid_plain_time, - zoned_date_time_plain_parts, DateTimeParts, FractionalSecondDigitsInput, IsoDateTime, + parse_temporal_string_impl, plain_date_time_object_parts, plain_time_components, plain_time_like_ns, + receiver_obj, reject_partial_object_with_calendar_or_time_zone, resolve_date_time_parts, round_instant_ns, + temporal_calendar_id, temporal_calendar_id_strict, temporal_number_component, temporal_option_number, + temporal_option_string, temporal_option_value, temporal_overflow, valid_iso_date, valid_plain_date_time_range, + valid_plain_time, zoned_date_time_plain_parts, DateTimeParts, FractionalSecondDigitsInput, IsoDateTime, }; // ───────────────────── PlainDateTime 基础方法 ───────────────────── @@ -530,6 +530,61 @@ pub fn plain_date_time_to_plain_time(vm: &mut H, args: &[u8]) -> Nati make_plain_time(vm, total_ns) } +/// `Temporal.PlainDateTime.prototype.withCalendar(calendar)`:只换日历槽,日期时间分量不变。 +/// +/// # 步骤 +/// 1. receiver 品牌校验;缺参 / undefined 抛 TypeError。 +/// 2. 参数经 temporal_calendar_id(宽松版):字符串走白名单或 ISO 日期(-时间)串, +/// 六类 Temporal 对象直读日历槽(不触发 getter),其他对象 / 原始值抛 TypeError。 +/// 3. 读 receiver 槽 0-3 重建,日历槽替换。 +/// +/// # 边界与前提 +/// - 非法日历串 → RangeError;日历 ID 大小写折叠。 +pub fn plain_date_time_with_calendar(vm: &mut H, args: &[u8]) -> NativeResult { + let ptr = native_try!(receiver_obj(vm, args)); + let obj = unsafe { &*ptr }; + native_try!(ensure_plain_date_time(vm, obj)); + let calendar_value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + if calendar_value.is_undefined() { + return NativeResult::Err(crate::error::create_type_error(vm, "calendar is required")); + } + let calendar = native_try!(temporal_calendar_id(vm, calendar_value)).unwrap_or_else(|| "iso8601".to_string()); + make_plain_date_time( + vm, + get_double_prop(obj, 0) as i32, + get_double_prop(obj, 1) as u32, + get_double_prop(obj, 2) as u32, + get_double_prop(obj, 3), + &calendar, + ) +} + +/// `Temporal.PlainDateTime.prototype.withPlainTime(plainTimeLike)`:只换时间槽,日期分量不变。 +/// +/// # 步骤 +/// 1. receiver 品牌校验;读槽 0-2 与日历槽 4 入本地(plain_time_like_ns 的 bag +/// 路径有用户调用窗口,receiver 槽须先于窗口读入)。 +/// 2. plain_time_like_ns 取当日纳秒(undefined → 午夜)。 +/// 3. valid_plain_date_time_range 严格不等式越界抛 RangeError;make_plain_date_time 重建。 +/// +/// # 边界与前提 +/// - 组合越 PlainDateTime 表示范围 → RangeError;日历槽继承 receiver。 +pub fn plain_date_time_with_plain_time(vm: &mut H, args: &[u8]) -> NativeResult { + let ptr = native_try!(receiver_obj(vm, args)); + let obj = unsafe { &*ptr }; + native_try!(ensure_plain_date_time(vm, obj)); + let year = get_double_prop(obj, 0) as i32; + let month = get_double_prop(obj, 1) as u32; + let day = get_double_prop(obj, 2) as u32; + let calendar = get_calendar_id(obj, 4); + let time_like = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() }; + let time_ns = native_try!(plain_time_like_ns(vm, time_like)); + if !valid_plain_date_time_range(year, month, day, time_ns) { + return NativeResult::Err(crate::error::create_range_error(vm, "invalid date-time")); + } + make_plain_date_time(vm, year, month, day, time_ns, &calendar) +} + /// Temporal 对象禁止隐式转换为原始值。 pub fn plain_date_time_value_of(vm: &mut H, _args: &[u8]) -> NativeResult { NativeResult::Err(crate::error::create_type_error(vm, "Temporal.PlainDateTime has no valueOf")) diff --git a/crates/oxide_vm/src/bindings/bind_temporal.rs b/crates/oxide_vm/src/bindings/bind_temporal.rs index 06c96304..f47a1a6c 100644 --- a/crates/oxide_vm/src/bindings/bind_temporal.rs +++ b/crates/oxide_vm/src/bindings/bind_temporal.rs @@ -568,6 +568,26 @@ pub fn bind_temporal(env: BindEnv<'_>, global: &mut JsObject) { ("until", oxide_builtins::temporal::plain_date_until:: as *const (), 1), ("since", oxide_builtins::temporal::plain_date_since:: as *const (), 1), ("with", oxide_builtins::temporal::plain_date_with:: as *const (), 1), + ( + "withCalendar", + oxide_builtins::temporal::plain_date_with_calendar:: as *const (), + 1, + ), + ( + "toPlainDateTime", + oxide_builtins::temporal::plain_date_to_plain_date_time:: as *const (), + 0, + ), + ( + "toPlainMonthDay", + oxide_builtins::temporal::plain_date_to_plain_month_day:: as *const (), + 0, + ), + ( + "toPlainYearMonth", + oxide_builtins::temporal::plain_date_to_plain_year_month:: as *const (), + 0, + ), ], ); @@ -814,6 +834,16 @@ pub fn bind_temporal(env: BindEnv<'_>, global: &mut JsObject) { 1, ), ("with", oxide_builtins::temporal::plain_date_time_with:: as *const (), 1), + ( + "withCalendar", + oxide_builtins::temporal::plain_date_time_with_calendar:: as *const (), + 1, + ), + ( + "withPlainTime", + oxide_builtins::temporal::plain_date_time_with_plain_time:: as *const (), + 0, + ), ], ); bind_well_known_data_property( diff --git a/crates/oxide_vm/tests/builtins_temporal_tests.rs b/crates/oxide_vm/tests/builtins_temporal_tests.rs index cf2e329c..f003f2fb 100644 --- a/crates/oxide_vm/tests/builtins_temporal_tests.rs +++ b/crates/oxide_vm/tests/builtins_temporal_tests.rs @@ -4179,3 +4179,158 @@ fn relativeto_bag_zoned_payload() { .unwrap(); assert_eq!(str_val(&vm, r), "24|24|24|24|24|23|-1|24|25|25|RangeError|RangeError"); } + +// -- PlainDate/PlainDateTime withCalendar / withPlainTime / toPlain* 族 -- + +#[test] +fn plain_date_with_calendar_inherits_slot_and_forms() { + let mut vm = Vm::new(); + // 换日历槽:日期分量不变、返回新对象;大小写不敏感;ISO 日期串与纯时间串接受; + // Temporal 对象直读日历槽(不触发 getter)。 + let r = eval( + &mut vm, + "const d = new Temporal.PlainDate(1976, 11, 18, 'hebrew'); + const w = d.withCalendar('iso8601'); + (w !== d) + '|' + w.calendarId + '|' + w.year + '|' + w.month + '|' + w.day + '|' + + d.withCalendar('iSo8601').calendarId + '|' + + d.withCalendar('2020-01-01').calendarId + '|' + + d.withCalendar('15:23').calendarId + '|' + + d.withCalendar(new Temporal.PlainMonthDay(3, 1, 'japanese')).calendarId + '|' + + d.withCalendar(new Temporal.PlainYearMonth(2020, 5, 'buddhist')).calendarId", + ) + .unwrap(); + assert_eq!(str_val(&vm, r), "true|iso8601|1976|11|18|iso8601|iso8601|iso8601|japanese|buddhist"); +} + +#[test] +fn plain_date_with_calendar_errors() { + let mut vm = Vm::new(); + // 缺参/undefined/非字符串非对象 → TypeError;非法串 → RangeError。 + let r = eval( + &mut vm, + "const d = new Temporal.PlainDate(1976, 11, 18); + (() => { try { d.withCalendar(); return 'no'; } + catch (e) { return e instanceof TypeError; } })() + '|' + + (() => { try { d.withCalendar(undefined); return 'no'; } + catch (e) { return e instanceof TypeError; } })() + '|' + + (() => { try { d.withCalendar(42); return 'no'; } + catch (e) { return e instanceof TypeError; } })() + '|' + + (() => { try { d.withCalendar('notacal'); return 'no'; } + catch (e) { return e instanceof RangeError; } })()", + ) + .unwrap(); + assert_eq!(str_val(&vm, r), "true|true|true|true"); +} + +#[test] +fn plain_date_time_with_calendar_inherits_slot_and_forms() { + let mut vm = Vm::new(); + // 换日历槽:日期时间分量不变、返回新对象;日历继承 receiver 槽(hebrew)。 + let r = eval( + &mut vm, + "const p = new Temporal.PlainDateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789, 'hebrew'); + const w = p.withCalendar('iso8601'); + (w !== p) + '|' + w.calendarId + '|' + w.year + '|' + w.month + '|' + w.day + '|' + + w.hour + '|' + w.minute + '|' + w.second + '|' + w.nanosecond + '|' + + p.withCalendar('iSo8601').calendarId + '|' + + p.withCalendar(new Temporal.PlainDate(2020, 1, 1, 'islamic')).calendarId", + ) + .unwrap(); + assert_eq!( + str_val(&vm, r), + "true|iso8601|1976|11|18|15|23|30|789|iso8601|islamic" + ); +} + +#[test] +fn plain_date_time_with_calendar_errors() { + let mut vm = Vm::new(); + // 缺参/undefined/非字符串非对象 → TypeError;非法串 → RangeError。 + let r = eval( + &mut vm, + "const p = new Temporal.PlainDateTime(1976, 11, 18); + (() => { try { p.withCalendar(); return 'no'; } + catch (e) { return e instanceof TypeError; } })() + '|' + + (() => { try { p.withCalendar(undefined); return 'no'; } + catch (e) { return e instanceof TypeError; } })() + '|' + + (() => { try { p.withCalendar(42); return 'no'; } + catch (e) { return e instanceof TypeError; } })() + '|' + + (() => { try { p.withCalendar('notacal'); return 'no'; } + catch (e) { return e instanceof RangeError; } })()", + ) + .unwrap(); + assert_eq!(str_val(&vm, r), "true|true|true|true"); +} + +#[test] +fn plain_date_time_with_plain_time_forms_and_range() { + let mut vm = Vm::new(); + // 缺参/undefined → 午夜;bag 缺省字段 0、second=60 钳制 59;字符串 / PlainTime / + // ZDT(自身时区本地时间)形态;空袋 TypeError;组合越范围 RangeError; + // 日历继承 receiver 槽。 + let r = eval( + &mut vm, + "const p = new Temporal.PlainDateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789, 'hebrew'); + p.withPlainTime().toString() + '|' + + p.withPlainTime(undefined).toString() + '|' + + p.withPlainTime({ minute: 30 }).toString() + '|' + + p.withPlainTime({ hour: 23, minute: 59, second: 60 }).toString() + '|' + + p.withPlainTime('12:34').toString() + '|' + + p.withPlainTime(new Temporal.PlainTime(11, 22)).hour + '|' + + p.withPlainTime(new Temporal.ZonedDateTime(0n, 'UTC')).toString() + '|' + + p.withPlainTime({ minute: 5 }).calendarId + '|' + + (() => { try { p.withPlainTime({}); return 'no'; } + catch (e) { return e instanceof TypeError; } })() + '|' + + (() => { try { new Temporal.PlainDateTime(-271821, 4, 19, 0, 0, 0, 0, 0, 1).withPlainTime(); return 'no'; } + catch (e) { return e instanceof RangeError; } })()", + ) + .unwrap(); + assert_eq!( + str_val(&vm, r), + "1976-11-18T00:00:00|1976-11-18T00:00:00|1976-11-18T00:30:00|1976-11-18T23:59:59|1976-11-18T12:34:00|11|1976-11-18T00:00:00|hebrew|true|true" + ); +} + +#[test] +fn plain_date_to_plain_date_time_limits_and_calendar() { + let mut vm = Vm::new(); + // 严格不等式边界:min 午夜抛、min 加 1 纳秒过、max 全满过;日历继承 receiver 槽。 + let r = eval( + &mut vm, + "(() => { + const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } }; + const min = new Temporal.PlainDate(-271821, 4, 19, 'hebrew'); + const max = new Temporal.PlainDate(275760, 9, 13); + return [ + kind(() => min.toPlainDateTime()), + min.toPlainDateTime({ nanosecond: 1 }).toString(), + min.toPlainDateTime('23:59:59.999999999').toString(), + min.toPlainDateTime({ nanosecond: 1 }).calendarId, + max.toPlainDateTime('23:59:59.999999999').toString(), + max.toPlainDateTime().toString(), + ].join('|'); + })()", + ) + .unwrap(); + assert_eq!( + str_val(&vm, r), + "RangeError|-271821-04-19T00:00:00.000000001|-271821-04-19T23:59:59.999999999|hebrew|+275760-09-13T23:59:59.999999999|+275760-09-13T00:00:00" + ); +} + +#[test] +fn plain_date_to_plain_month_day_and_year_month() { + let mut vm = Vm::new(); + // 纯拷贝:月/日与年/月分量不变;参考年 1972、参考日 1(非 iso8601 日历 + // toString 补参考值与日历注解);日历继承 receiver 槽。 + let r = eval( + &mut vm, + "const d = new Temporal.PlainDate(1976, 11, 18, 'hebrew'); + const md = d.toPlainMonthDay(); + const ym = d.toPlainYearMonth(); + md.monthCode + '|' + md.day + '|' + md.calendarId + '|' + md.toString() + '|' + + ym.year + '|' + ym.month + '|' + ym.calendarId + '|' + ym.toString()", + ) + .unwrap(); + assert_eq!(str_val(&vm, r), "M11|18|hebrew|1972-11-18[u-ca=hebrew]|1976|11|hebrew|1976-11-01[u-ca=hebrew]"); +}