mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-26 11:41:13 +08:00
feat: Temporal PlainDate 与 PlainDateTime 增加 with 方法
This commit is contained in:
+1
-1
@@ -25,7 +25,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.19.9"
|
version = "0.19.10"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
rust-version = "1.80"
|
rust-version = "1.80"
|
||||||
|
|||||||
@@ -8,8 +8,9 @@ use oxide_types::value::JsValue;
|
|||||||
use super::common::native_try;
|
use super::common::native_try;
|
||||||
use super::common::{
|
use super::common::{
|
||||||
days_from_civil, days_in_month, ensure_plain_date, get_calendar_id, get_double_prop, initialize_temporal_receiver,
|
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, temporal_calendar_id_strict,
|
is_ctor_call, parse_plain_date_string, plain_date_time_object_parts, receiver_obj, reject_partial_object_with_calendar_or_time_zone,
|
||||||
temporal_number_component, temporal_overflow, zoned_date_time_plain_parts,
|
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,
|
||||||
};
|
};
|
||||||
use super::difference::{difference_core, parse_difference_settings, IsoDateTime};
|
use super::difference::{difference_core, parse_difference_settings, IsoDateTime};
|
||||||
use super::duration::duration_like_values;
|
use super::duration::duration_like_values;
|
||||||
@@ -498,6 +499,105 @@ pub fn plain_date_equals<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
NativeResult::Ok(JsValue::bool(equal))
|
NativeResult::Ok(JsValue::bool(equal))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `Temporal.PlainDate.prototype.with(temporalDateLike[, options])`:覆盖 year/month/day/monthCode;
|
||||||
|
/// undefined 字段不覆盖;calendar/timeZone 键与 Temporal 实例、字符串参数均抛 TypeError;
|
||||||
|
/// 四键全 undefined 抛 TypeError;overflow 读自 options(缺省 constrain)。
|
||||||
|
/// 结果保留 receiver 日历。
|
||||||
|
///
|
||||||
|
/// # 步骤
|
||||||
|
/// 1. receiver 品牌校验;partial 参数经 reject_partial_object_with_calendar_or_time_zone
|
||||||
|
/// 排除日历/时区载体(其内部按序读 calendar、timeZone 两键)。
|
||||||
|
/// 2. 按规范读序逐字段读并就地转换:day → month → monthCode → year(数值字段
|
||||||
|
/// ToNumber,monthCode ToString),转换错误先于 options 校验抛出。
|
||||||
|
/// 3. month/day 小于 1 抛 RangeError(先于 options 形态错误)。
|
||||||
|
/// 4. 读 overflow;monthCode 语义校验(L 后缀、超界、与 month 冲突)在 options 之后。
|
||||||
|
/// 5. 与 receiver 字段合并后经 resolve_date_time_parts 钳制/校验;显式 day count
|
||||||
|
/// 范围检查(ignore_time 路径不查 ISODateWithinLimits)。
|
||||||
|
pub fn plain_date_with<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 value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||||
|
native_try!(reject_partial_object_with_calendar_or_time_zone(vm, value));
|
||||||
|
let vptr = value.as_js_object_ptr();
|
||||||
|
if vptr.is_null() {
|
||||||
|
return NativeResult::Err(crate::error::create_type_error(vm, "invalid argument"));
|
||||||
|
}
|
||||||
|
let vobj = unsafe { &*vptr };
|
||||||
|
// 读序由规范固定:day → month → monthCode → year,每字段读后立即转换。
|
||||||
|
let day_raw = native_try!(temporal_option_value(vm, vobj, value, "day"));
|
||||||
|
let day = if day_raw.is_undefined() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(native_try!(temporal_number_component(vm, day_raw)))
|
||||||
|
};
|
||||||
|
let month_raw = native_try!(temporal_option_value(vm, vobj, value, "month"));
|
||||||
|
let month = if month_raw.is_undefined() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(native_try!(temporal_number_component(vm, month_raw)))
|
||||||
|
};
|
||||||
|
let month_code_raw = native_try!(temporal_option_value(vm, vobj, value, "monthCode"));
|
||||||
|
let month_code = if month_code_raw.is_undefined() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(native_try!(temporal_option_string(vm, month_code_raw)))
|
||||||
|
};
|
||||||
|
let year_raw = native_try!(temporal_option_value(vm, vobj, value, "year"));
|
||||||
|
let year = if year_raw.is_undefined() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(native_try!(temporal_number_component(vm, year_raw)))
|
||||||
|
};
|
||||||
|
if day.is_none() && month.is_none() && month_code.is_none() && year.is_none() {
|
||||||
|
return NativeResult::Err(crate::error::create_type_error(vm, "no properties present"));
|
||||||
|
}
|
||||||
|
// 月/日小于 1 的 RangeError 先于 options 校验抛出。
|
||||||
|
if let Some(month) = month {
|
||||||
|
if month < 1.0 {
|
||||||
|
return NativeResult::Err(crate::error::create_range_error(vm, "invalid month"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(day) = day {
|
||||||
|
if day < 1.0 {
|
||||||
|
return NativeResult::Err(crate::error::create_range_error(vm, "invalid day"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let constrain = native_try!(temporal_overflow(vm, args));
|
||||||
|
// 与 receiver 字段合并:bag 字段优先,undefined 字段回落 receiver 槽值。
|
||||||
|
// receiver 月只在 bag 未提供 month 与 monthCode 时参与合并,避免与
|
||||||
|
// bag 的 monthCode 触发误冲突(冲突检查仅作用于 bag 字段)。
|
||||||
|
let month = if month.is_none() && month_code.is_none() {
|
||||||
|
Some(get_double_prop(obj, 1))
|
||||||
|
} else {
|
||||||
|
month
|
||||||
|
};
|
||||||
|
let (year, month, day, _, _) = native_try!(resolve_date_time_parts(
|
||||||
|
vm,
|
||||||
|
None,
|
||||||
|
DateTimeParts {
|
||||||
|
year: year.or(Some(get_double_prop(obj, 0))),
|
||||||
|
month,
|
||||||
|
month_code,
|
||||||
|
day: day.or(Some(get_double_prop(obj, 2))),
|
||||||
|
hour: 0.0,
|
||||||
|
minute: 0.0,
|
||||||
|
second: 0.0,
|
||||||
|
millisecond: 0.0,
|
||||||
|
microsecond: 0.0,
|
||||||
|
nanosecond: 0.0,
|
||||||
|
},
|
||||||
|
constrain,
|
||||||
|
true,
|
||||||
|
));
|
||||||
|
// ignore_time 路径不查 ISODateWithinLimits,day count 表示范围须显式检查。
|
||||||
|
let day_count = days_from_civil(i128::from(year), i128::from(month), i128::from(day));
|
||||||
|
if !(-100_000_001..=100_000_000).contains(&day_count) {
|
||||||
|
return NativeResult::Err(crate::error::create_range_error(vm, "ISO date is out of range"));
|
||||||
|
}
|
||||||
|
make_plain_date(vm, year, month, day, &get_calendar_id(obj, 3))
|
||||||
|
}
|
||||||
|
|
||||||
/// `Temporal.PlainDate.compare(a, b)`:静态比较,返回 -1/0/1。
|
/// `Temporal.PlainDate.compare(a, b)`:静态比较,返回 -1/0/1。
|
||||||
pub fn plain_date_compare<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
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() };
|
let a_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ use super::{
|
|||||||
duration_like_values, ensure_plain_date_time, format_iso_year, get_calendar_id, get_double_prop,
|
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,
|
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,
|
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, round_instant_ns,
|
parse_temporal_string_impl, plain_date_time_object_parts, plain_time_components, receiver_obj,
|
||||||
temporal_calendar_id_strict, temporal_option_number, temporal_option_string, temporal_option_value,
|
reject_partial_object_with_calendar_or_time_zone, resolve_date_time_parts, round_instant_ns,
|
||||||
temporal_overflow, valid_iso_date, valid_plain_date_time_range, valid_plain_time, zoned_date_time_plain_parts,
|
temporal_calendar_id_strict, temporal_number_component, temporal_option_number, temporal_option_string,
|
||||||
FractionalSecondDigitsInput, IsoDateTime,
|
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 基础方法 ─────────────────────
|
// ───────────────────── PlainDateTime 基础方法 ─────────────────────
|
||||||
|
|
||||||
@@ -219,6 +220,143 @@ pub fn plain_date_time_equals<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResul
|
|||||||
NativeResult::Ok(JsValue::bool(equal))
|
NativeResult::Ok(JsValue::bool(equal))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `Temporal.PlainDateTime.prototype.with(temporalDateTimeLike[, options])`:覆盖日期与时间全字段;
|
||||||
|
/// undefined 字段不覆盖;calendar/timeZone 键与 Temporal 实例、字符串参数均抛 TypeError;
|
||||||
|
/// 十键全 undefined 抛 TypeError;overflow 读自 options(缺省 constrain)。
|
||||||
|
/// 结果保留 receiver 日历。
|
||||||
|
///
|
||||||
|
/// # 步骤
|
||||||
|
/// 1. receiver 品牌校验;partial 参数经 reject_partial_object_with_calendar_or_time_zone
|
||||||
|
/// 排除日历/时区载体(其内部按序读 calendar、timeZone 两键)。
|
||||||
|
/// 2. 按规范读序逐字段读并就地转换:day → hour → microsecond → millisecond → minute
|
||||||
|
/// → month → monthCode → nanosecond → second → year(数值字段 ToNumber,
|
||||||
|
/// monthCode ToString),转换错误先于 options 校验抛出。
|
||||||
|
/// 3. month/day 小于 1 抛 RangeError(先于 options 形态错误)。
|
||||||
|
/// 4. 读 overflow;monthCode 语义校验(L 后缀、超界、与 month 冲突)在 options 之后。
|
||||||
|
/// 5. 与 receiver 字段合并(时间字段缺省取 receiver 的纳秒分解)后经
|
||||||
|
/// resolve_date_time_parts 钳制/校验(含 PlainDateTime 范围严格不等式边界)。
|
||||||
|
pub fn plain_date_time_with<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 value = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||||
|
native_try!(reject_partial_object_with_calendar_or_time_zone(vm, value));
|
||||||
|
let vptr = value.as_js_object_ptr();
|
||||||
|
if vptr.is_null() {
|
||||||
|
return NativeResult::Err(crate::error::create_type_error(vm, "invalid argument"));
|
||||||
|
}
|
||||||
|
let vobj = unsafe { &*vptr };
|
||||||
|
// 读序由规范固定:day → hour → microsecond → millisecond → minute → month
|
||||||
|
// → monthCode → nanosecond → second → year,每字段读后立即转换。
|
||||||
|
let day_raw = native_try!(temporal_option_value(vm, vobj, value, "day"));
|
||||||
|
let day = if day_raw.is_undefined() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(native_try!(temporal_number_component(vm, day_raw)))
|
||||||
|
};
|
||||||
|
let hour_raw = native_try!(temporal_option_value(vm, vobj, value, "hour"));
|
||||||
|
let hour = if hour_raw.is_undefined() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(native_try!(temporal_number_component(vm, hour_raw)))
|
||||||
|
};
|
||||||
|
let microsecond_raw = native_try!(temporal_option_value(vm, vobj, value, "microsecond"));
|
||||||
|
let microsecond = if microsecond_raw.is_undefined() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(native_try!(temporal_number_component(vm, microsecond_raw)))
|
||||||
|
};
|
||||||
|
let millisecond_raw = native_try!(temporal_option_value(vm, vobj, value, "millisecond"));
|
||||||
|
let millisecond = if millisecond_raw.is_undefined() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(native_try!(temporal_number_component(vm, millisecond_raw)))
|
||||||
|
};
|
||||||
|
let minute_raw = native_try!(temporal_option_value(vm, vobj, value, "minute"));
|
||||||
|
let minute = if minute_raw.is_undefined() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(native_try!(temporal_number_component(vm, minute_raw)))
|
||||||
|
};
|
||||||
|
let month_raw = native_try!(temporal_option_value(vm, vobj, value, "month"));
|
||||||
|
let month = if month_raw.is_undefined() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(native_try!(temporal_number_component(vm, month_raw)))
|
||||||
|
};
|
||||||
|
let month_code_raw = native_try!(temporal_option_value(vm, vobj, value, "monthCode"));
|
||||||
|
let month_code = if month_code_raw.is_undefined() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(native_try!(temporal_option_string(vm, month_code_raw)))
|
||||||
|
};
|
||||||
|
let nanosecond_raw = native_try!(temporal_option_value(vm, vobj, value, "nanosecond"));
|
||||||
|
let nanosecond = if nanosecond_raw.is_undefined() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(native_try!(temporal_number_component(vm, nanosecond_raw)))
|
||||||
|
};
|
||||||
|
let second_raw = native_try!(temporal_option_value(vm, vobj, value, "second"));
|
||||||
|
let second = if second_raw.is_undefined() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(native_try!(temporal_number_component(vm, second_raw)))
|
||||||
|
};
|
||||||
|
let year_raw = native_try!(temporal_option_value(vm, vobj, value, "year"));
|
||||||
|
let year = if year_raw.is_undefined() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(native_try!(temporal_number_component(vm, year_raw)))
|
||||||
|
};
|
||||||
|
if [day_raw, hour_raw, microsecond_raw, millisecond_raw, minute_raw, month_raw, month_code_raw, nanosecond_raw, second_raw, year_raw]
|
||||||
|
.iter()
|
||||||
|
.all(|raw| raw.is_undefined())
|
||||||
|
{
|
||||||
|
return NativeResult::Err(crate::error::create_type_error(vm, "no properties present"));
|
||||||
|
}
|
||||||
|
// 月/日小于 1 的 RangeError 先于 options 校验抛出。
|
||||||
|
if let Some(month) = month {
|
||||||
|
if month < 1.0 {
|
||||||
|
return NativeResult::Err(crate::error::create_range_error(vm, "invalid month"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(day) = day {
|
||||||
|
if day < 1.0 {
|
||||||
|
return NativeResult::Err(crate::error::create_range_error(vm, "invalid day"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let constrain = native_try!(temporal_overflow(vm, args));
|
||||||
|
// 与 receiver 字段合并:bag 字段优先,undefined 字段回落 receiver 槽值
|
||||||
|
// (时间字段取 receiver 午夜后纳秒的分解)。receiver 月只在 bag 未提供
|
||||||
|
// month 与 monthCode 时参与合并,避免与 bag 的 monthCode 触发误冲突。
|
||||||
|
let month = if month.is_none() && month_code.is_none() {
|
||||||
|
Some(get_double_prop(obj, 1))
|
||||||
|
} else {
|
||||||
|
month
|
||||||
|
};
|
||||||
|
let (hour_r, minute_r, second_r, millisecond_r, microsecond_r, nanosecond_r) =
|
||||||
|
plain_time_components(get_double_prop(obj, 3));
|
||||||
|
let (year, month, day, total_ns, _) = native_try!(resolve_date_time_parts(
|
||||||
|
vm,
|
||||||
|
None,
|
||||||
|
DateTimeParts {
|
||||||
|
year: year.or(Some(get_double_prop(obj, 0))),
|
||||||
|
month,
|
||||||
|
month_code,
|
||||||
|
day: day.or(Some(get_double_prop(obj, 2))),
|
||||||
|
hour: hour.unwrap_or(hour_r as f64),
|
||||||
|
minute: minute.unwrap_or(minute_r as f64),
|
||||||
|
second: second.unwrap_or(second_r as f64),
|
||||||
|
millisecond: millisecond.unwrap_or(millisecond_r as f64),
|
||||||
|
microsecond: microsecond.unwrap_or(microsecond_r as f64),
|
||||||
|
nanosecond: nanosecond.unwrap_or(nanosecond_r as f64),
|
||||||
|
},
|
||||||
|
constrain,
|
||||||
|
false,
|
||||||
|
));
|
||||||
|
make_plain_date_time(vm, year, month, day, total_ns, &get_calendar_id(obj, 4))
|
||||||
|
}
|
||||||
|
|
||||||
fn plain_date_time_parts<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<(i32, u32, u32, f64), JsValue> {
|
fn plain_date_time_parts<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<(i32, u32, u32, f64), JsValue> {
|
||||||
let ptr = receiver_obj(vm, args)?;
|
let ptr = receiver_obj(vm, args)?;
|
||||||
let obj = unsafe { &*ptr };
|
let obj = unsafe { &*ptr };
|
||||||
|
|||||||
@@ -567,6 +567,7 @@ pub fn bind_temporal(env: BindEnv<'_>, global: &mut JsObject) {
|
|||||||
("subtract", oxide_builtins::temporal::plain_date_subtract::<crate::vm::Vm> as *const (), 1),
|
("subtract", oxide_builtins::temporal::plain_date_subtract::<crate::vm::Vm> as *const (), 1),
|
||||||
("until", oxide_builtins::temporal::plain_date_until::<crate::vm::Vm> as *const (), 1),
|
("until", oxide_builtins::temporal::plain_date_until::<crate::vm::Vm> as *const (), 1),
|
||||||
("since", oxide_builtins::temporal::plain_date_since::<crate::vm::Vm> as *const (), 1),
|
("since", oxide_builtins::temporal::plain_date_since::<crate::vm::Vm> as *const (), 1),
|
||||||
|
("with", oxide_builtins::temporal::plain_date_with::<crate::vm::Vm> as *const (), 1),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -812,6 +813,11 @@ pub fn bind_temporal(env: BindEnv<'_>, global: &mut JsObject) {
|
|||||||
oxide_builtins::temporal::plain_date_time_equals::<crate::vm::Vm> as *const (),
|
oxide_builtins::temporal::plain_date_time_equals::<crate::vm::Vm> as *const (),
|
||||||
1,
|
1,
|
||||||
),
|
),
|
||||||
|
(
|
||||||
|
"with",
|
||||||
|
oxide_builtins::temporal::plain_date_time_with::<crate::vm::Vm> as *const (),
|
||||||
|
1,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
bind_well_known_data_property(
|
bind_well_known_data_property(
|
||||||
|
|||||||
@@ -3897,6 +3897,76 @@ fn plain_time_with_reject_invalid_components_throw_range_error() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn plain_date_with_field_update_overflow_and_forms() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
// 字段更新:year/month/day/monthCode 覆盖、undefined 不覆盖、unrecognized 键忽略;
|
||||||
|
// overflow:constrain 钳日、reject 抛 RangeError;day/year 越界 RangeError;
|
||||||
|
// 三形态:空袋/字符串/Temporal 实例/calendar 键均 TypeError,month 与 monthCode 冲突
|
||||||
|
// RangeError;year 超 day count 表示范围 RangeError。
|
||||||
|
let r = eval(
|
||||||
|
&mut vm,
|
||||||
|
"(() => {
|
||||||
|
const d = new Temporal.PlainDate(1976, 11, 18);
|
||||||
|
const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } };
|
||||||
|
const val = (fn) => { try { return String(fn()); } catch (e) { return e.constructor.name; } };
|
||||||
|
return [
|
||||||
|
val(() => d.with({ year: 2019, nonsense: true }).toString()),
|
||||||
|
val(() => d.with({ month: 4, day: 31 }, { overflow: 'constrain' }).toString()),
|
||||||
|
kind(() => d.with({ month: 4, day: 31 }, { overflow: 'reject' })),
|
||||||
|
kind(() => d.with({ day: -1 }, { overflow: 'reject' })),
|
||||||
|
kind(() => d.with({ year: 300000 })),
|
||||||
|
kind(() => d.with({})),
|
||||||
|
kind(() => d.with('2019-05-17')),
|
||||||
|
kind(() => d.with(Temporal.PlainDate.from('2019-05-17'))),
|
||||||
|
kind(() => d.with({ year: 2021, calendar: 'iso8601' })),
|
||||||
|
val(() => d.with({ monthCode: 'M03' }).toString()),
|
||||||
|
kind(() => d.with({ month: 5, monthCode: 'M06' })),
|
||||||
|
].join('|');
|
||||||
|
})()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
str_val(&vm, r),
|
||||||
|
"2019-11-18|1976-04-30|RangeError|RangeError|RangeError|TypeError|TypeError|TypeError|TypeError|1976-03-18|RangeError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn plain_date_time_with_field_update_overflow_and_forms() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
// 字段更新:日期与时间全字段覆盖、undefined 不覆盖;
|
||||||
|
// overflow:constrain 钳时/日、reject 抛 RangeError;
|
||||||
|
// 三形态:空袋/字符串/timeZone 键 TypeError,month 与 monthCode 冲突 RangeError;
|
||||||
|
// 边界:-271821-04-19T00:00:00.000000001 减纳秒到范围外边界抛 RangeError。
|
||||||
|
let r = eval(
|
||||||
|
&mut vm,
|
||||||
|
"(() => {
|
||||||
|
const dt = new Temporal.PlainDateTime(1976, 11, 18, 15, 23, 30, 123, 456, 789);
|
||||||
|
const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } };
|
||||||
|
const val = (fn) => { try { return String(fn()); } catch (e) { return e.constructor.name; } };
|
||||||
|
const min = new Temporal.PlainDateTime(-271821, 4, 19, 0, 0, 0, 0, 0, 1);
|
||||||
|
return [
|
||||||
|
val(() => dt.with({ year: 2019, hour: 5 }).toString()),
|
||||||
|
val(() => dt.with({ month: 2, day: 31 }, { overflow: 'constrain' }).toString()),
|
||||||
|
kind(() => dt.with({ month: 2, day: 31 }, { overflow: 'reject' })),
|
||||||
|
kind(() => dt.with({ hour: -1 }, { overflow: 'reject' })),
|
||||||
|
val(() => dt.with({ hour: 25, minute: 90 }, { overflow: 'constrain' }).toString()),
|
||||||
|
kind(() => dt.with({})),
|
||||||
|
kind(() => dt.with('1995-04-07T12:34')),
|
||||||
|
kind(() => dt.with({ year: 2021, timeZone: 'UTC' })),
|
||||||
|
kind(() => dt.with({ month: 5, monthCode: 'M06' })),
|
||||||
|
kind(() => min.with({ nanosecond: 0 })),
|
||||||
|
].join('|');
|
||||||
|
})()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
str_val(&vm, r),
|
||||||
|
"2019-11-18T05:23:30.123456789|1976-02-29T15:23:30.123456789|RangeError|RangeError|1976-11-18T23:59:30.123456789|TypeError|TypeError|TypeError|RangeError|RangeError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn zoned_date_time_with_reject_negative_time_component_throws_range_error() {
|
fn zoned_date_time_with_reject_negative_time_component_throws_range_error() {
|
||||||
let mut vm = Vm::new();
|
let mut vm = Vm::new();
|
||||||
|
|||||||
Reference in New Issue
Block a user