mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: Temporal.PlainYearMonth add/subtract——时长转换复用、低阶单位拒绝与按日 1 的 (年,月) 范围
This commit is contained in:
@@ -8772,6 +8772,71 @@ pub fn plain_year_month_equals<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
|
||||
NativeResult::Ok(JsValue::bool(equal))
|
||||
}
|
||||
|
||||
/// add/subtract 的 (年, 月) 可表示性检查,按日 = 1 计算(ISODateWithinLimits)。
|
||||
/// 与 ISOYearMonthWithinLimits 的差异仅在 -271821 年:该年 4 月 1 日早于最早
|
||||
/// 可表示的日期时间,故 4 月及以前越界(5 月起在界内);+275760 年须 9 月及以前。
|
||||
fn iso_year_month_day1_within_limits(year: i64, month: i64) -> bool {
|
||||
if !(-271_821_i64..=275_760_i64).contains(&year) {
|
||||
return false;
|
||||
}
|
||||
!((year == -271_821 && month < 5) || (year == 275_760 && month > 9))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainYearMonth.prototype.add/subtract(durationLike [, options])` 核心。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. receiver branding(TypeError)。
|
||||
/// 2. ToTemporalDuration(串 / bag / 实例;TypeError、RangeError 各自语义)。
|
||||
/// 3. 读 overflow(规范顺序先于后续算法校验;ISO 日历下取值不可观测,仅校验)。
|
||||
/// 4. weeks / days / 时间分量任一非零 → RangeError。
|
||||
/// 5. receiver (年, 月) 按日 1 检查可表示性 → RangeError。
|
||||
/// 6. 年、月分量折算绝对月数后相加再平衡(div/rem_euclid)。
|
||||
/// 7. 结果 (年, 月) 按日 1 检查可表示性 → RangeError。
|
||||
///
|
||||
/// # 边界与副作用
|
||||
/// - 结果参考日恒 1,日历标识取自 receiver 槽 3。
|
||||
/// - subtract 与 add 共用本函数,sign 参数取反时长分量。
|
||||
fn plain_year_month_apply_duration<H: VmHost>(vm: &mut H, args: &[u8], sign: i64) -> NativeResult {
|
||||
let ptr = native_try!(receiver_obj(vm, args));
|
||||
let obj = unsafe { &*ptr };
|
||||
native_try!(ensure_plain_year_month(vm, obj));
|
||||
let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let values = match duration_like_values(vm, val) {
|
||||
Ok(values) => values,
|
||||
Err(error) => return NativeResult::Err(error),
|
||||
};
|
||||
let _overflow = native_try!(temporal_overflow(vm, args));
|
||||
if values[2..].iter().any(|value| *value != 0.0) {
|
||||
return NativeResult::Err(crate::error::create_range_error(
|
||||
vm,
|
||||
"year-month cannot add weeks, days, or time units",
|
||||
));
|
||||
}
|
||||
let year = get_double_prop(obj, 0) as i64;
|
||||
let month = get_double_prop(obj, 1) as i64;
|
||||
if !iso_year_month_day1_within_limits(year, month) {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "year-month is out of range"));
|
||||
}
|
||||
let months = (values[0] as i64) * 12 + values[1] as i64;
|
||||
let absolute = year * 12 + month - 1 + sign * months;
|
||||
let result_year = absolute.div_euclid(12);
|
||||
let result_month = absolute.rem_euclid(12) + 1;
|
||||
if !iso_year_month_day1_within_limits(result_year, result_month) {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "year-month is out of range"));
|
||||
}
|
||||
make_plain_year_month(vm, result_year as i32, result_month as u32, 1, &get_calendar_id(obj, 3))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainYearMonth.prototype.add(durationLike)`。
|
||||
pub fn plain_year_month_add<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
plain_year_month_apply_duration(vm, args, 1)
|
||||
}
|
||||
|
||||
/// `Temporal.PlainYearMonth.prototype.subtract(durationLike)`。
|
||||
pub fn plain_year_month_subtract<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
plain_year_month_apply_duration(vm, args, -1)
|
||||
}
|
||||
|
||||
/// `Temporal.PlainMonthDay.prototype.equals(other)`:比较 (月, 日, 参考年) 三元与日历标识;
|
||||
/// other 经 ToTemporalMonthDay(串 / 字段对象 / 实例)转换。
|
||||
pub fn plain_month_day_equals<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
|
||||
@@ -1143,6 +1143,12 @@ pub fn bind_temporal(core: &Arc<KernelCore>, session: &KernelSession, global: &m
|
||||
oxide_builtins::temporal::plain_year_month_equals::<crate::vm::Vm> as *const (),
|
||||
1,
|
||||
),
|
||||
("add", oxide_builtins::temporal::plain_year_month_add::<crate::vm::Vm> as *const (), 1),
|
||||
(
|
||||
"subtract",
|
||||
oxide_builtins::temporal::plain_year_month_subtract::<crate::vm::Vm> as *const (),
|
||||
1,
|
||||
),
|
||||
],
|
||||
);
|
||||
bind_well_known_data_property(
|
||||
|
||||
@@ -3324,3 +3324,85 @@ fn plain_year_month_equals_matrix() {
|
||||
"true|false|false|false|true|true|true|true|true|true|TypeError|TypeError|TypeError|RangeError|TypeError|RangeError|TypeError|true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_year_month_add_matrix() {
|
||||
let mut vm = Vm::new();
|
||||
// add:ToTemporalDuration(串/bag/实例)→ 读 overflow → weeks/days/时间分量
|
||||
// 非零 RangeError;(年,月) 折绝对月数平衡;接收方与结果均按日 1 查可表示性
|
||||
// (-271821-04 加零时长也 RangeError);结果参考日恒 1。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(() => {
|
||||
const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } };
|
||||
const ym = (s) => Temporal.PlainYearMonth.from(s);
|
||||
return [
|
||||
ym('2019-11').add({months: 2}).toString(),
|
||||
ym('2019-11').add({years: 1}).toString(),
|
||||
ym('2019-11').add({months: -2}).toString(),
|
||||
ym('2000-05').add('P3M').toString(),
|
||||
ym('1994-11').add(Temporal.Duration.from('P18Y7M')).toString(),
|
||||
new Temporal.PlainYearMonth(2025, 8).add(new Temporal.Duration()).toString(),
|
||||
ym('2019-11').add({years: 1, months: 2}).toString(),
|
||||
ym('1970-01').add('-P273790Y8M').toString(),
|
||||
ym('1970-01').add('P3285488M').toString(),
|
||||
kind(() => ym('2019-11').add({days: 1})),
|
||||
kind(() => ym('2019-11').add({hours: 1})),
|
||||
kind(() => ym('2019-11').add(new Temporal.Duration(0, 0, 0, -1))),
|
||||
kind(() => ym('2019-11').add({years: 1, months: -3})),
|
||||
kind(() => ym('2019-11').add({year: 1})),
|
||||
kind(() => ym('2019-11').add({})),
|
||||
kind(() => ym('2019-11').add(7)),
|
||||
kind(() => ym('2019-11').add('')),
|
||||
kind(() => ym('2019-11').add({months: 1.5})),
|
||||
kind(() => ym('2019-11').add({months: Infinity})),
|
||||
kind(() => ym('2019-11').add('P4294967296M')),
|
||||
kind(() => ym('+275760-09').add({months: 1})),
|
||||
kind(() => ym('-271821-04').add(new Temporal.Duration())),
|
||||
kind(() => ym('2019-11').add({months: 1}, null)),
|
||||
ym('2023-05').add({months: 12}, {overflow: 'reject'}).toString(),
|
||||
ym('2023-05').add({months: 12}, {overflow: 'constrain'}).toString(),
|
||||
].join('|');
|
||||
})()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
str_val(&vm, r),
|
||||
"2020-01|2020-11|2019-09|2000-08|2013-06|2025-08|2021-01|-271821-05|+275760-09|RangeError|RangeError|RangeError|RangeError|TypeError|TypeError|TypeError|RangeError|RangeError|RangeError|RangeError|RangeError|RangeError|TypeError|2024-05|2024-05"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_year_month_subtract_matrix() {
|
||||
let mut vm = Vm::new();
|
||||
// subtract 与 add 同核(sign 取反);日历保留;低阶单位与非零时间分量
|
||||
// RangeError;单数键 TypeError;接收方/结果按日 1 可表示性。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(() => {
|
||||
const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } };
|
||||
const ym = (s) => Temporal.PlainYearMonth.from(s);
|
||||
return [
|
||||
ym('2013-06').subtract(Temporal.Duration.from('P18Y7M')).toString(),
|
||||
ym('2000-05').subtract('P3M').toString(),
|
||||
ym('2019-11').subtract({years: 1, months: 2}).toString(),
|
||||
ym('+275760-09').subtract({months: 1}).toString(),
|
||||
ym('+275760-09').subtract({years: 1}).toString(),
|
||||
ym('1970-01').subtract('P273790Y8M').toString(),
|
||||
kind(() => ym('2019-11').subtract({weeks: 1})),
|
||||
kind(() => ym('2019-11').subtract({seconds: 1})),
|
||||
kind(() => ym('2019-11').subtract({months: -1, years: 1})),
|
||||
kind(() => ym('2019-11').subtract({month: 1})),
|
||||
kind(() => ym('-271821-04').subtract({months: 1})),
|
||||
kind(() => ym('-271821-04').subtract(new Temporal.Duration())),
|
||||
kind(() => ym('2019-11').subtract({months: 1}, 1)),
|
||||
kind(() => ym('2019-11').subtract('junk')),
|
||||
].join('|');
|
||||
})()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
str_val(&vm, r),
|
||||
"1994-11|2000-02|2018-09|+275760-08|+275759-09|-271821-05|RangeError|RangeError|RangeError|TypeError|RangeError|RangeError|TypeError|RangeError"
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user