feat: Temporal PlainDate 与 PlainDateTime 增加换槽与纯拷贝方法

This commit is contained in:
2026-10-14 09:29:40 +00:00
parent db57f55085
commit 7dbb22a165
6 changed files with 354 additions and 31 deletions
@@ -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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
let a_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
@@ -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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(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<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
NativeResult::Err(crate::error::create_type_error(vm, "Temporal.PlainDateTime has no valueOf"))