mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 统一 Duration.round 纯时间路径范围判据为存储分量加权和
This commit is contained in:
@@ -306,7 +306,9 @@ fn duration_relative_to_date<H: VmHost>(
|
|||||||
if let Ok((year, month, day, _time_ns)) = parse_plain_date_time_string(&text) {
|
if let Ok((year, month, day, _time_ns)) = parse_plain_date_time_string(&text) {
|
||||||
return Ok(Some((i128::from(year), i128::from(month), i128::from(day))));
|
return Ok(Some((i128::from(year), i128::from(month), i128::from(day))));
|
||||||
}
|
}
|
||||||
// ZonedDateTime-like(含 Z 或时区注解):按 instant 解析 + 注解时区反推墙钟日期。
|
// 回退支:plain 首支解析失败的串(含 Z 的串或非法注解)。命名区当前全部 RangeError
|
||||||
|
// (canonical_time_zone 拒 IANA),可解析的带注解串已被 plain 首支接受。
|
||||||
|
// 按 instant 解析 + 注解时区反推墙钟日期。
|
||||||
let (epoch_ns, time_zone_id, _calendar) = zoned_date_time_string_parts(vm, &text, "reject")?;
|
let (epoch_ns, time_zone_id, _calendar) = zoned_date_time_string_parts(vm, &text, "reject")?;
|
||||||
let offset_minutes = instant_time_zone_offset(&time_zone_id).unwrap_or(0);
|
let offset_minutes = instant_time_zone_offset(&time_zone_id).unwrap_or(0);
|
||||||
let wall_ns = epoch_ns + i128::from(offset_minutes) * 60_000_000_000;
|
let wall_ns = epoch_ns + i128::from(offset_minutes) * 60_000_000_000;
|
||||||
@@ -651,7 +653,7 @@ pub fn duration_add<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
/// 4. 平衡后存储时间分量(含 days,𝔽 舍入)的纳秒加权和校验 2^53 秒上限。
|
/// 4. 平衡后存储时间分量(含 days,𝔽 舍入)的纳秒加权和校验 2^53 秒上限。
|
||||||
///
|
///
|
||||||
/// # 边界与前提
|
/// # 边界与前提
|
||||||
/// - 任一侧含日历单位抛 RangeError(本批无 relativeTo 支持)。
|
/// - add/subtract 无 relativeTo 选项,任一侧含 years/months/weeks 即抛 RangeError。
|
||||||
/// - 全零与 nanosecond 分量和为 0 时直接返回零时长。
|
/// - 全零与 nanosecond 分量和为 0 时直接返回零时长。
|
||||||
fn add_duration_values<H: VmHost>(vm: &mut H, receiver: &[f64; 10], other: &[f64; 10]) -> Result<[f64; 10], JsValue> {
|
fn add_duration_values<H: VmHost>(vm: &mut H, receiver: &[f64; 10], other: &[f64; 10]) -> Result<[f64; 10], JsValue> {
|
||||||
if receiver[..3].iter().any(|value| *value != 0.0) || other[..3].iter().any(|value| *value != 0.0) {
|
if receiver[..3].iter().any(|value| *value != 0.0) || other[..3].iter().any(|value| *value != 0.0) {
|
||||||
@@ -1088,24 +1090,15 @@ pub fn duration_round<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
unit -= 1;
|
unit -= 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 范围校验:对每个时间分量按纳秒刻度检查是否达到 2^53 秒上限。
|
// 范围校验:判据作用在存储分量(平衡后 𝔽 舍入结果)的纳秒加权和上,与
|
||||||
const MAX_TIME_NANOSECONDS: f64 = (1_i128 << 53) as f64 * 1_000_000_000.0;
|
// add/subtract 同式;加权和覆盖逐分量检查的盲区(总和落在 [2^53 s, 2^53 s +
|
||||||
const UNIT_SCALES: [f64; 7] = [
|
// 86400 s) 而各分量均低于上限的缝隙带)。
|
||||||
86_400_000_000_000.0,
|
const MAX_TIME_NANOSECONDS: i128 = (1_i128 << 53) * 1_000_000_000;
|
||||||
3_600_000_000_000.0,
|
let Some(stored_ns) = duration_time_nanoseconds(&result) else {
|
||||||
60_000_000_000.0,
|
return NativeResult::Err(crate::error::create_range_error(vm, "invalid duration"));
|
||||||
1_000_000_000.0,
|
};
|
||||||
1_000_000.0,
|
if stored_ns.abs() >= MAX_TIME_NANOSECONDS {
|
||||||
1_000.0,
|
return NativeResult::Err(crate::error::create_range_error(vm, "duration time fields are out of range"));
|
||||||
1.0,
|
|
||||||
];
|
|
||||||
for (index, scale) in (3..10).zip(UNIT_SCALES) {
|
|
||||||
if result[index] != 0.0 && result[index].abs() * scale >= MAX_TIME_NANOSECONDS {
|
|
||||||
return NativeResult::Err(crate::error::create_range_error(
|
|
||||||
vm,
|
|
||||||
"duration time fields are out of range",
|
|
||||||
));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return make_duration(vm, result);
|
return make_duration(vm, result);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2629,6 +2629,33 @@ fn duration_total_relative_zdt() {
|
|||||||
assert_eq!(r.as_double(), 1.0);
|
assert_eq!(r.as_double(), 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn duration_round_weighted_sum_range_boundary() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
// round 纯时间路径范围判据按存储分量加权和(spec IsValidDuration),非逐分量:
|
||||||
|
// 输入 2^53 s − 30 s 合法,expand 舍到 minute 后 2^53 s + 28 s 越界 → RangeError
|
||||||
|
// (平衡后各分量均低于上限);负镜像同抛;2^53 s − 1 ns 输入舍到 day 得
|
||||||
|
// 104249991374 天(2^53 s − 27392 s)→ 接受。
|
||||||
|
let r = eval(
|
||||||
|
&mut vm,
|
||||||
|
"(() => {
|
||||||
|
const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } };
|
||||||
|
return [
|
||||||
|
kind(() => new Temporal.Duration(0, 0, 0, 104249991374, 7, 36, 2)
|
||||||
|
.round({ smallestUnit: 'minute', roundingMode: 'expand' })),
|
||||||
|
kind(() => new Temporal.Duration(0, 0, 0, -104249991374, -7, -36, -2)
|
||||||
|
.round({ smallestUnit: 'minute', roundingMode: 'expand' })),
|
||||||
|
kind(() => new Temporal.Duration(0, 0, 0, 104249991374, 7, 36, 31, 999, 999, 999)
|
||||||
|
.round({ smallestUnit: 'day' })),
|
||||||
|
new Temporal.Duration(0, 0, 0, 104249991374, 7, 36, 31, 999, 999, 999)
|
||||||
|
.round({ smallestUnit: 'day' }).toString(),
|
||||||
|
].join('|');
|
||||||
|
})()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(str_val(&vm, r), "RangeError|RangeError|no|P104249991374D");
|
||||||
|
}
|
||||||
|
|
||||||
// -- Temporal.PlainMonthDay / PlainYearMonth(E 批)--
|
// -- Temporal.PlainMonthDay / PlainYearMonth(E 批)--
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
Reference in New Issue
Block a user