feat: ZonedDateTime.withTimeZone 与 equals 基础实现
This commit is contained in:
@@ -1576,6 +1576,67 @@ pub fn zoned_date_time_value_of<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeRe
|
||||
NativeResult::Err(crate::error::create_type_error(vm, "Temporal.ZonedDateTime has no valueOf"))
|
||||
}
|
||||
|
||||
/// `Temporal.ZonedDateTime.prototype.withTimeZone(timeZone)`:返回同一 instant 换时区槽的新 ZDT。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. branding 校验 receiver 为 ZDT。
|
||||
/// 2. 读槽 0 epoch 与槽 2 calendar(保不变),参数经 canonical_time_zone 解析新时区。
|
||||
/// 3. make_zoned_date_time 重建对象,仅替换时区槽。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 参数须为字符串;非字符串抛 TypeError。无法解析的时区串抛 RangeError。
|
||||
/// - epoch 与 calendar 槽原样保留,仅时区 ID 变化。
|
||||
pub fn zoned_date_time_with_time_zone<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let ptr = native_try!(receiver_obj(vm, args));
|
||||
let obj = unsafe { &*ptr };
|
||||
native_try!(ensure_zoned_date_time(vm, obj));
|
||||
let Some(epoch_ns) = get_instant_epoch_ns(obj) else {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid ZonedDateTime"));
|
||||
};
|
||||
let calendar_id = get_calendar_id(obj, 2);
|
||||
if args.len() < 2 {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "timeZone is required"));
|
||||
}
|
||||
let raw = vm.reg(args[1]);
|
||||
if !raw.is_string() {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "invalid time zone"));
|
||||
}
|
||||
let input = to_string(raw);
|
||||
let Some((time_zone_id, _)) = canonical_time_zone(&input) else {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid time zone"));
|
||||
};
|
||||
make_zoned_date_time(vm, epoch_ns, &time_zone_id, &calendar_id)
|
||||
}
|
||||
|
||||
/// `Temporal.ZonedDateTime.prototype.equals(other)`:与另一 ZDT 按 epoch/时区/日历三槽比较。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - receiver 须为 ZDT,否则 TypeError。
|
||||
/// - 参数仅支持 ZDT 对象:三槽(epoch/时区/日历)全等才返回 true。
|
||||
/// - 非 ZDT 对象或非对象参数:S3 基础范围外,返回 false(完整比较语义待后续)。
|
||||
pub fn zoned_date_time_equals<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let ptr = native_try!(receiver_obj(vm, args));
|
||||
let obj = unsafe { &*ptr };
|
||||
native_try!(ensure_zoned_date_time(vm, obj));
|
||||
|
||||
let other = if args.len() < 2 { JsValue::undefined() } else { vm.reg(args[1]) };
|
||||
if !other.is_object() {
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
let other_ptr = other.as_js_object_ptr();
|
||||
if other_ptr.is_null() {
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
let other_obj = unsafe { &*other_ptr };
|
||||
if !other_obj.is_zoned_date_time_obj() {
|
||||
return NativeResult::Ok(JsValue::bool(false));
|
||||
}
|
||||
let epoch_equal = get_instant_epoch_ns(obj) == get_instant_epoch_ns(other_obj);
|
||||
let zone_equal = to_string(obj.get_prop_at(1)) == to_string(other_obj.get_prop_at(1));
|
||||
let calendar_equal = get_calendar_id(obj, 2) == get_calendar_id(other_obj, 2);
|
||||
NativeResult::Ok(JsValue::bool(epoch_equal && zone_equal && calendar_equal))
|
||||
}
|
||||
|
||||
/// ZDT 字段 getter 宏:branding 后读本地分量,按选择函数取字段。
|
||||
macro_rules! zoned_date_time_parts_getter {
|
||||
($name:ident, $select:expr) => {
|
||||
|
||||
@@ -396,6 +396,16 @@ pub fn bind_temporal(core: &Arc<KernelCore>, session: &KernelSession, global: &m
|
||||
oxide_builtins::temporal::zoned_date_time_value_of::<crate::vm::Vm> as *const (),
|
||||
0,
|
||||
),
|
||||
(
|
||||
"withTimeZone",
|
||||
oxide_builtins::temporal::zoned_date_time_with_time_zone::<crate::vm::Vm> as *const (),
|
||||
1,
|
||||
),
|
||||
(
|
||||
"equals",
|
||||
oxide_builtins::temporal::zoned_date_time_equals::<crate::vm::Vm> as *const (),
|
||||
1,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
@@ -1269,3 +1269,68 @@ fn zoned_date_time_to_string_options_validation() {
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "true|true|true|true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zoned_date_time_with_time_zone_swaps_zone_keeps_instant() {
|
||||
let mut vm = Vm::new();
|
||||
// 换时区后 instant 保留、返回新对象、timeZoneId 更新;大小写不敏感 + ±HH 均可用。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"const z = new Temporal.ZonedDateTime(0n, 'UTC');
|
||||
const w = z.withTimeZone('+01:00');
|
||||
w.timeZoneId + '|' + w.epochNanoseconds + '|' +
|
||||
(w !== z) + '|' + z.timeZoneId + '|' +
|
||||
new Temporal.ZonedDateTime(0n, 'uTc').withTimeZone('+01').timeZoneId",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "+01:00|0|true|UTC|+01");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zoned_date_time_with_time_zone_bad_receiver_or_zone() {
|
||||
let mut vm = Vm::new();
|
||||
// 非 ZDT receiver 抛 TypeError;非法时区串抛 RangeError;非字符串抛 TypeError。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"( () => { try { Temporal.ZonedDateTime.prototype.withTimeZone.call({}, 'UTC'); return 'no-throw'; }
|
||||
catch (e) { return e instanceof TypeError; } })() + '|' +
|
||||
( () => { try { new Temporal.ZonedDateTime(0n, 'UTC').withTimeZone('Not/AZone'); return 'no-throw'; }
|
||||
catch (e) { return e instanceof RangeError; } })() + '|' +
|
||||
( () => { try { new Temporal.ZonedDateTime(0n, 'UTC').withTimeZone(42); return 'no-throw'; }
|
||||
catch (e) { return e instanceof TypeError; } })()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "true|true|true");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zoned_date_time_equals_compares_epoch_zone_calendar() {
|
||||
let mut vm = Vm::new();
|
||||
// 同 instant/时区/日历 true;异 instant false;异时区 false;异日历 false。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"const a = new Temporal.ZonedDateTime(0n, 'UTC');
|
||||
const b = new Temporal.ZonedDateTime(0n, 'UTC');
|
||||
const c = new Temporal.ZonedDateTime(1n, 'UTC');
|
||||
const d = new Temporal.ZonedDateTime(0n, '-05:00');
|
||||
const e = new Temporal.ZonedDateTime(0n, 'UTC', 'japanese');
|
||||
a.equals(b) + '|' + a.equals(c) + '|' + a.equals(d) + '|' + a.equals(e)",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "true|false|false|false");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zoned_date_time_equals_receiver_branding_and_non_zoned_arg() {
|
||||
let mut vm = Vm::new();
|
||||
// 非 ZDT receiver 抛 TypeError;非 ZDT 对象参数返回 false。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"( () => { try { Temporal.ZonedDateTime.prototype.equals.call({}, new Temporal.ZonedDateTime(0n, 'UTC')); return 'no-throw'; }
|
||||
catch (e) { return e instanceof TypeError; } })() + '|' +
|
||||
new Temporal.ZonedDateTime(0n, 'UTC').equals({}) + '|' +
|
||||
new Temporal.ZonedDateTime(0n, 'UTC').equals('2021-01-01T00:00:00+00:00[UTC]')",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "true|false|false");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user