feat: ZonedDateTime.compare 跨实例按 epoch 比较(复用 from 解析器,忽略时区/日历)
This commit is contained in:
@@ -1734,6 +1734,28 @@ pub fn zoned_date_time_from<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
|
|||||||
make_zoned_date_time(vm, epoch_ns, &time_zone_id, &calendar_id)
|
make_zoned_date_time(vm, epoch_ns, &time_zone_id, &calendar_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// `Temporal.ZonedDateTime.compare(one, two)`:按纪元纳秒比较两 ZDT,忽略时区 ID 与日历。
|
||||||
|
///
|
||||||
|
/// # 步骤
|
||||||
|
/// 1. 两参数各经 zoned_date_time_like_epoch_ns 解析(默认 offset=reject + disambiguation=compatible)。
|
||||||
|
/// 2. 仅比较 epoch 纳秒,返回 -1/0/1。
|
||||||
|
///
|
||||||
|
/// # 边界与前提
|
||||||
|
/// - 参数可为 ZDT 对象 / ISO 字符串 / property bag;解析失败抛错。
|
||||||
|
/// - 同 epoch 不同时区或日历恒相等(比较不读时区/日历槽)。
|
||||||
|
pub fn zoned_date_time_compare<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let one = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||||
|
let two = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||||
|
let one = native_try!(zoned_date_time_like_epoch_ns(vm, one, "reject", "compatible"));
|
||||||
|
let two = native_try!(zoned_date_time_like_epoch_ns(vm, two, "reject", "compatible"));
|
||||||
|
let ordering = one.0.cmp(&two.0);
|
||||||
|
NativeResult::Ok(JsValue::int(match ordering {
|
||||||
|
std::cmp::Ordering::Less => -1,
|
||||||
|
std::cmp::Ordering::Equal => 0,
|
||||||
|
std::cmp::Ordering::Greater => 1,
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
/// 时区注解文本:命名区原样返回,偏移区规范化为 `±HH:MM` 带冒号;critical 时 `!` 置于括号内。
|
/// 时区注解文本:命名区原样返回,偏移区规范化为 `±HH:MM` 带冒号;critical 时 `!` 置于括号内。
|
||||||
fn format_time_zone_annotation(time_zone_id: &str, critical: bool) -> String {
|
fn format_time_zone_annotation(time_zone_id: &str, critical: bool) -> String {
|
||||||
let inner = if matches!(time_zone_id.as_bytes().first(), Some(b'+') | Some(b'-')) {
|
let inner = if matches!(time_zone_id.as_bytes().first(), Some(b'+') | Some(b'-')) {
|
||||||
|
|||||||
@@ -152,7 +152,14 @@ pub fn bind_temporal(core: &Arc<KernelCore>, session: &KernelSession, global: &m
|
|||||||
world,
|
world,
|
||||||
zoned_date_time_ctor,
|
zoned_date_time_ctor,
|
||||||
core,
|
core,
|
||||||
&[("from", oxide_builtins::temporal::zoned_date_time_from::<crate::vm::Vm> as *const (), 1)],
|
&[
|
||||||
|
("from", oxide_builtins::temporal::zoned_date_time_from::<crate::vm::Vm> as *const (), 1),
|
||||||
|
(
|
||||||
|
"compare",
|
||||||
|
oxide_builtins::temporal::zoned_date_time_compare::<crate::vm::Vm> as *const (),
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
let zoned_date_time_proto_ptr = world.zoned_date_time_proto.as_ptr() as *mut JsObject;
|
let zoned_date_time_proto_ptr = world.zoned_date_time_proto.as_ptr() as *mut JsObject;
|
||||||
|
|||||||
@@ -1487,6 +1487,74 @@ fn zoned_date_time_from_options_validation() {
|
|||||||
assert_eq!(str_val(&vm, r), "true|true|true");
|
assert_eq!(str_val(&vm, r), "true|true|true");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn zoned_date_time_compare_orders_by_epoch() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
// 按 epoch 大小比较而非墙钟:epoch 大者胜,返回 -1/0/1。
|
||||||
|
let r = eval(
|
||||||
|
&mut vm,
|
||||||
|
"const a = new Temporal.ZonedDateTime(217175010123456789n, 'UTC');
|
||||||
|
const b = new Temporal.ZonedDateTime(217175010223456789n, 'UTC');
|
||||||
|
Temporal.ZonedDateTime.compare(a, a) + '|' +
|
||||||
|
Temporal.ZonedDateTime.compare(a, b) + '|' +
|
||||||
|
Temporal.ZonedDateTime.compare(b, a)",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(str_val(&vm, r), "0|-1|1");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn zoned_date_time_compare_ignores_time_zone_and_calendar() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
// 同 epoch 不同时区/日历 → 0(只按 epoch 比较)。
|
||||||
|
let r = eval(
|
||||||
|
&mut vm,
|
||||||
|
"const a = new Temporal.ZonedDateTime(0n, '+01:00', 'hebrew');
|
||||||
|
const b = new Temporal.ZonedDateTime(0n, 'UTC');
|
||||||
|
const c = new Temporal.ZonedDateTime(1n, '-05:00');
|
||||||
|
Temporal.ZonedDateTime.compare(a, b) + '|' +
|
||||||
|
Temporal.ZonedDateTime.compare(b, c)",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(str_val(&vm, r), "0|-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn zoned_date_time_compare_accepts_string_and_bag() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
// 字符串 / property bag 参数走 from 解析器归一为 epoch 后比较。
|
||||||
|
let r = eval(
|
||||||
|
&mut vm,
|
||||||
|
"const zdt = new Temporal.ZonedDateTime(0n, 'UTC');
|
||||||
|
Temporal.ZonedDateTime.compare('1970-01-01T00:00Z[UTC]', zdt) + '|' +
|
||||||
|
Temporal.ZonedDateTime.compare(
|
||||||
|
{ year: 1969, month: 12, day: 31, hour: 19, minute: 0, timeZone: '-05:00' },
|
||||||
|
zdt) + '|' +
|
||||||
|
Temporal.ZonedDateTime.compare(zdt, '1970-01-01T00:00:01Z[UTC]')",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(str_val(&vm, r), "0|0|-1");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn zoned_date_time_compare_error_paths() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
// 非法字符串 / 缺 timeZone 的 bag / 非转换原始值均抛错(RangeError/TypeError)。
|
||||||
|
let r = eval(
|
||||||
|
&mut vm,
|
||||||
|
"( () => { try { Temporal.ZonedDateTime.compare('garbage', '1970-01-01T00:00Z[UTC]'); return 'no-throw'; }
|
||||||
|
catch (e) { return e instanceof RangeError; } })() + '|' +
|
||||||
|
(() => { try { Temporal.ZonedDateTime.compare(
|
||||||
|
{ year: 2000, month: 1, day: 1 },
|
||||||
|
'1970-01-01T00:00Z[UTC]'); return 'no-throw'; }
|
||||||
|
catch (e) { return e instanceof TypeError; } })() + '|' +
|
||||||
|
(() => { try { Temporal.ZonedDateTime.compare(123, 456); return 'no-throw'; }
|
||||||
|
catch (e) { return e instanceof TypeError; } })()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(str_val(&vm, r), "true|true|true");
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn zoned_date_time_until_defaults_to_hours() {
|
fn zoned_date_time_until_defaults_to_hours() {
|
||||||
let mut vm = Vm::new();
|
let mut vm = Vm::new();
|
||||||
|
|||||||
Reference in New Issue
Block a user