mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-24 18:51:13 +08:00
feat: Temporal.Duration 补全——compare/subtract/equals/sign/blank/toJSON/toLocaleString + relativeTo 相对日历对齐
This commit is contained in:
@@ -891,7 +891,14 @@ pub fn bind_temporal(core: &Arc<KernelCore>, session: &KernelSession, global: &m
|
||||
world,
|
||||
duration_ctor,
|
||||
core,
|
||||
&[("from", oxide_builtins::temporal::duration_from::<crate::vm::Vm> as *const (), 1)],
|
||||
&[
|
||||
("from", oxide_builtins::temporal::duration_from::<crate::vm::Vm> as *const (), 1),
|
||||
(
|
||||
"compare",
|
||||
oxide_builtins::temporal::duration_compare::<crate::vm::Vm> as *const (),
|
||||
2,
|
||||
),
|
||||
],
|
||||
);
|
||||
let duration_proto_ptr = world.duration_proto.as_ptr() as *mut JsObject;
|
||||
let duration_proto = unsafe { &mut *duration_proto_ptr };
|
||||
@@ -915,6 +922,8 @@ pub fn bind_temporal(core: &Arc<KernelCore>, session: &KernelSession, global: &m
|
||||
"nanoseconds",
|
||||
oxide_builtins::temporal::duration_nanoseconds::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
("sign", oxide_builtins::temporal::duration_sign::<crate::vm::Vm> as *const ()),
|
||||
("blank", oxide_builtins::temporal::duration_blank::<crate::vm::Vm> as *const ()),
|
||||
] {
|
||||
bind_accessor_getter(core, session, duration_proto, name, getter);
|
||||
}
|
||||
@@ -926,13 +935,35 @@ pub fn bind_temporal(core: &Arc<KernelCore>, session: &KernelSession, global: &m
|
||||
("abs", oxide_builtins::temporal::duration_abs::<crate::vm::Vm> as *const (), 0),
|
||||
("negated", oxide_builtins::temporal::duration_negated::<crate::vm::Vm> as *const (), 0),
|
||||
("add", oxide_builtins::temporal::duration_add::<crate::vm::Vm> as *const (), 1),
|
||||
(
|
||||
"subtract",
|
||||
oxide_builtins::temporal::duration_subtract::<crate::vm::Vm> as *const (),
|
||||
1,
|
||||
),
|
||||
("equals", oxide_builtins::temporal::duration_equals::<crate::vm::Vm> as *const (), 1),
|
||||
("round", oxide_builtins::temporal::duration_round::<crate::vm::Vm> as *const (), 1),
|
||||
("with", oxide_builtins::temporal::duration_with::<crate::vm::Vm> as *const (), 1),
|
||||
("total", oxide_builtins::temporal::duration_total::<crate::vm::Vm> as *const (), 1),
|
||||
("toString", oxide_builtins::temporal::duration_to_string::<crate::vm::Vm> as *const (), 0),
|
||||
("toJSON", oxide_builtins::temporal::duration_to_json::<crate::vm::Vm> as *const (), 0),
|
||||
(
|
||||
"toLocaleString",
|
||||
oxide_builtins::temporal::duration_to_locale_string::<crate::vm::Vm> as *const (),
|
||||
0,
|
||||
),
|
||||
("valueOf", oxide_builtins::temporal::duration_value_of::<crate::vm::Vm> as *const (), 0),
|
||||
],
|
||||
);
|
||||
bind_well_known_data_property(
|
||||
core,
|
||||
duration_proto,
|
||||
9,
|
||||
JsValue::perm_string(
|
||||
core.perm_interner()
|
||||
.string_ptr(core.perm_interner().intern("Temporal.Duration").0),
|
||||
),
|
||||
PropAttributes::new(false, false, true),
|
||||
);
|
||||
|
||||
// 把子对象挂到 Temporal 命名空间对象上,再把 Temporal 挂到 global。
|
||||
bind_global_value(core, temporal, "Now", JsValue::from_js_object(now_ptr));
|
||||
|
||||
@@ -2435,3 +2435,179 @@ fn zoned_date_time_add_error_paths() {
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "true|true|true|true|true");
|
||||
}
|
||||
|
||||
// -- Temporal.Duration 本体补全(69.2) --
|
||||
|
||||
#[test]
|
||||
fn duration_compare_time_only_fast_path() {
|
||||
let mut vm = Vm::new();
|
||||
// 纯时间单位无 relativeTo:按归一纳秒比大小(days 折 24h)。
|
||||
assert_eq!(num(&mut vm, "Temporal.Duration.compare('P1D', 'PT24H')"), 0.0);
|
||||
assert_eq!(num(&mut vm, "Temporal.Duration.compare('P1D', 'PT23H')"), 1.0);
|
||||
assert_eq!(num(&mut vm, "Temporal.Duration.compare('PT23H', 'P1D')"), -1.0);
|
||||
assert_eq!(num(&mut vm, "Temporal.Duration.compare('P2D', 'PT48H')"), 0.0);
|
||||
assert_eq!(num(&mut vm, "Temporal.Duration.compare({days:1}, {hours:24})"), 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_compare_calendar_units_require_relative() {
|
||||
let mut vm = Vm::new();
|
||||
// 无 relativeTo 且含日历单位 → RangeError。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"try { Temporal.Duration.compare({years:1},{months:12}); 'no-throw' } catch (e) { e.constructor.name }",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "RangeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_compare_relative_calendar_units() {
|
||||
let mut vm = Vm::new();
|
||||
// relativeto-month 对拍:P1M vs P30D 取决于相对点所在月份长度。
|
||||
assert_eq!(
|
||||
num(&mut vm, "Temporal.Duration.compare('P1M','P30D',{relativeTo:'2018-04-01'})"),
|
||||
0.0
|
||||
);
|
||||
assert_eq!(
|
||||
num(&mut vm, "Temporal.Duration.compare('P1M','P30D',{relativeTo:'2018-03-01'})"),
|
||||
1.0
|
||||
);
|
||||
assert_eq!(
|
||||
num(&mut vm, "Temporal.Duration.compare('P1M','P30D',{relativeTo:'2018-02-01'})"),
|
||||
-1.0
|
||||
);
|
||||
// string 与 PlainDate 对象等价。
|
||||
assert_eq!(
|
||||
num(
|
||||
&mut vm,
|
||||
"Temporal.Duration.compare('P1M','P30D',{relativeTo: Temporal.PlainDate.from('2018-04-01')})"
|
||||
),
|
||||
0.0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_compare_relative_zdt_and_instant() {
|
||||
let mut vm = Vm::new();
|
||||
// 1970-04-01T00:00Z:P1M(到 5/1 为 30 天)vs P30D → 0。
|
||||
assert_eq!(
|
||||
num(
|
||||
&mut vm,
|
||||
"Temporal.Duration.compare('P1M','P30D',{relativeTo: new Temporal.ZonedDateTime(7776000000000000n,'UTC')})"
|
||||
),
|
||||
0.0
|
||||
);
|
||||
// Instant relativeTo(UTC 分解)同结果。
|
||||
assert_eq!(
|
||||
num(
|
||||
&mut vm,
|
||||
"Temporal.Duration.compare('P1M','P30D',{relativeTo: Temporal.Instant.fromEpochNanoseconds(7776000000000000n)})"
|
||||
),
|
||||
0.0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_subtract_equals_add_negated() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"const a = new Temporal.Duration(0,0,0,5,3);
|
||||
const b = {days:2,hours:1};
|
||||
a.subtract(b).toString() + '|' + a.add(Temporal.Duration.from(b).negated()).toString()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "P3DT2H|P3DT2H");
|
||||
// 含日历单位 → RangeError(继承 add 限制)。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"try { new Temporal.Duration(1,0,0,0).subtract({days:1}); 'no-throw' } catch (e) { e.constructor.name }",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "RangeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_equals_compares_all_components() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"new Temporal.Duration(1,2,3,4).equals('P1Y2M3W4D') + '|' +
|
||||
new Temporal.Duration(1,2,3,4).equals('P1Y2M3W4DT1S') + '|' +
|
||||
new Temporal.Duration().equals('PT0S') + '|' +
|
||||
(() => { try { Temporal.Duration.prototype.equals.call({}, 'P1D'); return 'no-throw'; }
|
||||
catch (e) { return e instanceof TypeError; } })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "true|false|true|true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_sign_and_blank() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"new Temporal.Duration().sign + '|' + new Temporal.Duration().blank + '|' +
|
||||
new Temporal.Duration(-1,0,0,0).sign + '|' + new Temporal.Duration(-1,0,0,0).blank + '|' +
|
||||
Temporal.Duration.from('PT1H').sign",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "0|true|-1|false|1");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_to_json_and_locale_string_and_tag() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"const d = new Temporal.Duration(1,2,3,4,5);
|
||||
d.toJSON() + '|' + d.toLocaleString() + '|' + d[Symbol.toStringTag] + '|' +
|
||||
(d.toJSON() === d.toString()) + '|' + (d.toLocaleString('zh', {}) === d.toString())",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "P1Y2M3W4DT5H|P1Y2M3W4DT5H|Temporal.Duration|true|true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_round_relative_calendar_units() {
|
||||
let mut vm = Vm::new();
|
||||
// P1M 相对 2018-04-01 舍到 day:4/1 + P1M = 5/1,round day → P1M。
|
||||
// P11M round {largestUnit:'year'}:smallest 默认 nanosecond,无日历舍入 → P11M。
|
||||
// 缺 relativeTo 的日历单位舍入 → RangeError。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"Temporal.Duration.from('P1M').round({smallestUnit:'day', relativeTo:'2018-04-01'}).toString() + '|' +
|
||||
Temporal.Duration.from('P11M').round({largestUnit:'year', relativeTo:'2021-01-01'}).toString() + '|' +
|
||||
(() => { try { Temporal.Duration.from('P1M').round({smallestUnit:'day'}); return 'no-throw'; }
|
||||
catch (e) { return e instanceof RangeError; } })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "P1M|P11M|true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_round_relative_smallest_calendar_unit() {
|
||||
let mut vm = Vm::new();
|
||||
// P11M round {largestUnit:'year', smallestUnit:'year'}:2021-01-01 下 11 个月 < 1 年,
|
||||
// halfExpand 折 334/365 → 舍到 1 年。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"Temporal.Duration.from('P11M').round({largestUnit:'year', smallestUnit:'year', relativeTo:'2021-01-01'}).toString() + '|' +
|
||||
Temporal.Duration.from('P11M').round({largestUnit:'year', smallestUnit:'month', relativeTo:'2021-01-01'}).toString()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "P1Y|P11M");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn duration_total_relative_zdt() {
|
||||
let mut vm = Vm::new();
|
||||
// ZDT relativeTo 取本地日期:1970-04-01 下 P1M total months → 1。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"Temporal.Duration.from('P1M').total({unit:'months', relativeTo: new Temporal.ZonedDateTime(7776000000000000n,'UTC')})",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(r.as_double(), 1.0);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user