feat: Temporal.PlainTime add/subtract 时间域叠加
This commit is contained in:
@@ -4636,6 +4636,61 @@ pub fn plain_time_since<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
plain_time_difference(vm, args, true)
|
||||
}
|
||||
|
||||
// ───────────────────── PlainTime add / subtract ─────────────────────
|
||||
|
||||
/// add/subtract 核心:receiver 午夜后纳秒叠加时长的"时间域"增量。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. branding receiver 取槽 0 ns。
|
||||
/// 2. `duration_like_values` 归一 duration,`temporal_overflow` 解析 options(读序:duration → options)。
|
||||
/// 3. 仅取 hours 起的时间字段换算纳秒增量(日期字段 days 及以上规范忽略)。
|
||||
/// 4. 按 sign 叠加后 `rem_euclid(DAY_NS)` 保持 0-24 域 → `make_plain_time`。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - duration 全 0 → 值不变的新对象(blank-duration 语义)。
|
||||
/// - duration 含日期字段(y/m/w/d)不报错,规范对 PlainTime 直接忽略。
|
||||
fn plain_time_apply_duration<H: VmHost>(vm: &mut H, args: &[u8], sign: i64) -> NativeResult {
|
||||
let ptr = match receiver_obj(vm, args) {
|
||||
Ok(p) => p,
|
||||
Err(error) => return NativeResult::Err(error),
|
||||
};
|
||||
let obj = unsafe { &*ptr };
|
||||
if let Err(error) = ensure_plain_time(vm, obj) {
|
||||
return NativeResult::Err(error);
|
||||
}
|
||||
let time_ns = get_double_prop(obj, 0) as i128;
|
||||
let duration_like = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let values = match duration_like_values(vm, duration_like) {
|
||||
Ok(values) => values,
|
||||
Err(error) => return NativeResult::Err(error),
|
||||
};
|
||||
if let Err(error) = temporal_overflow(vm, args) {
|
||||
return NativeResult::Err(error);
|
||||
}
|
||||
const DAY_NS: i128 = 86_400_000_000_000;
|
||||
// 时间字段(hours 起)单独换算纳秒;日期字段 days 及以上对 PlainTime 无意义直接忽略。
|
||||
let [_, _, _, _, h, min, s, ms, us, ns] = values;
|
||||
let time_delta = duration_component_integer(h).unwrap_or(0) * 3_600_000_000_000
|
||||
+ duration_component_integer(min).unwrap_or(0) * 60_000_000_000
|
||||
+ duration_component_integer(s).unwrap_or(0) * 1_000_000_000
|
||||
+ duration_component_integer(ms).unwrap_or(0) * 1_000_000
|
||||
+ duration_component_integer(us).unwrap_or(0) * 1_000
|
||||
+ duration_component_integer(ns).unwrap_or(0);
|
||||
// 时间溢出跨午夜:rem_euclid 保持 0-24 域。
|
||||
let new_time_ns = (time_ns + time_delta * sign as i128).rem_euclid(DAY_NS);
|
||||
make_plain_time(vm, new_time_ns as f64)
|
||||
}
|
||||
|
||||
/// `Temporal.PlainTime.prototype.add(durationLike, options)`。
|
||||
pub fn plain_time_add<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
plain_time_apply_duration(vm, args, 1)
|
||||
}
|
||||
|
||||
/// `Temporal.PlainTime.prototype.subtract(durationLike, options)`。
|
||||
pub fn plain_time_subtract<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
plain_time_apply_duration(vm, args, -1)
|
||||
}
|
||||
|
||||
// ───────────────────── PlainDateTime 基础方法 ─────────────────────
|
||||
|
||||
/// `Temporal.PlainDateTime` 构造器:保存 ISO 日期与午夜后纳秒。
|
||||
@@ -7340,4 +7395,18 @@ mod tests {
|
||||
assert_eq!(rounded.rem_euclid(DAY_NS), 0);
|
||||
assert_eq!(format_plain_time_iso(rounded.rem_euclid(DAY_NS), true, Some(0)), "00:00:00");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_time_apply_duration_ignores_date_units() {
|
||||
// 时间域加总仅取 hours 起字段;days 及以上对 PlainTime 忽略(与 instant_round 日期单位报错不同)。
|
||||
let mut values = [0.0; 10];
|
||||
values[3] = 5.0; // days
|
||||
values[4] = 2.0; // hours
|
||||
values[5] = 30.0; // minutes
|
||||
let time_delta = duration_component_integer(values[4]).unwrap() * 3_600_000_000_000
|
||||
+ duration_component_integer(values[5]).unwrap() * 60_000_000_000;
|
||||
assert_eq!(time_delta, 9_000_000_000_000); // 2h30m
|
||||
// 忽略 days:time_delta 不含 DAY_NS 分量。
|
||||
assert_eq!(time_delta % 86_400_000_000_000, 9_000_000_000_000);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -694,6 +694,8 @@ pub fn bind_temporal(core: &Arc<KernelCore>, session: &KernelSession, global: &m
|
||||
("equals", oxide_builtins::temporal::plain_time_equals::<crate::vm::Vm> as *const (), 1),
|
||||
("until", oxide_builtins::temporal::plain_time_until::<crate::vm::Vm> as *const (), 1),
|
||||
("since", oxide_builtins::temporal::plain_time_since::<crate::vm::Vm> as *const (), 1),
|
||||
("add", oxide_builtins::temporal::plain_time_add::<crate::vm::Vm> as *const (), 1),
|
||||
("subtract", oxide_builtins::temporal::plain_time_subtract::<crate::vm::Vm> as *const (), 1),
|
||||
],
|
||||
);
|
||||
bind_well_known_data_property(
|
||||
|
||||
@@ -706,6 +706,56 @@ fn plain_time_until_since_options_and_direction() {
|
||||
assert_eq!(str_val(&vm, r), "PT1H");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_time_add_subtract_normal() {
|
||||
let mut vm = Vm::new();
|
||||
// 同日内小时加/减与跨分钟进位。
|
||||
let r = eval(&mut vm, "new Temporal.PlainTime(12, 30).add({ hours: 1, minutes: 5 }).toString()").unwrap();
|
||||
assert_eq!(str_val(&vm, r), "13:35:00");
|
||||
let r = eval(&mut vm, "new Temporal.PlainTime(12, 30).subtract({ hours: 1, minutes: 5 }).toString()").unwrap();
|
||||
assert_eq!(str_val(&vm, r), "11:25:00");
|
||||
// 亚秒进位:秒进位到分钟。
|
||||
let r = eval(&mut vm, "new Temporal.PlainTime(12, 30, 59, 500).add({ milliseconds: 500 }).toString()").unwrap();
|
||||
assert_eq!(str_val(&vm, r), "12:31:00");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_time_add_subtract_cross_midnight() {
|
||||
let mut vm = Vm::new();
|
||||
// 时间溢出跨午夜:rem_euclid 回 0-24 域。
|
||||
let r = eval(&mut vm, "new Temporal.PlainTime(23, 30).add({ hours: 1 }).toString()").unwrap();
|
||||
assert_eq!(str_val(&vm, r), "00:30:00");
|
||||
let r = eval(&mut vm, "new Temporal.PlainTime(00, 30).subtract({ hours: 1 }).toString()").unwrap();
|
||||
assert_eq!(str_val(&vm, r), "23:30:00");
|
||||
// 多天增量同样折叠回当日。
|
||||
let r = eval(&mut vm, "new Temporal.PlainTime(12, 30).add({ hours: 26 }).toString()").unwrap();
|
||||
assert_eq!(str_val(&vm, r), "14:30:00");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_time_add_subtract_ignores_date_units_and_blank() {
|
||||
let mut vm = Vm::new();
|
||||
// 日期字段(days 及以上)对 PlainTime 忽略,不报错。
|
||||
let r = eval(&mut vm, "new Temporal.PlainTime(12, 30).add({ days: 3, hours: 1 }).toString()").unwrap();
|
||||
assert_eq!(str_val(&vm, r), "13:30:00");
|
||||
// blank duration(Duration 对象全零):值不变的新对象。
|
||||
let r = eval(&mut vm, "new Temporal.PlainTime(12, 30).add(new Temporal.Duration()).toString()").unwrap();
|
||||
assert_eq!(str_val(&vm, r), "12:30:00");
|
||||
let r = eval(&mut vm, "new Temporal.PlainTime(12, 30).subtract(new Temporal.Duration()).toString()").unwrap();
|
||||
assert_eq!(str_val(&vm, r), "12:30:00");
|
||||
// 空对象无 duration 字段 → TypeError(符合 duration-like 字段要求)。
|
||||
let r = eval(&mut vm, "try { new Temporal.PlainTime(12, 30).add({}) } catch (e) { e.constructor.name }").unwrap();
|
||||
assert_eq!(str_val(&vm, r), "TypeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_time_add_subtract_invalid_argument() {
|
||||
let mut vm = Vm::new();
|
||||
// 非 duration-like 原始值抛 TypeError。
|
||||
let r = eval(&mut vm, "try { new Temporal.PlainTime(12).add(42) } catch (e) { e.constructor.name }").unwrap();
|
||||
assert_eq!(str_val(&vm, r), "TypeError");
|
||||
}
|
||||
|
||||
// -- Temporal.PlainDateTime --
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user