mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
fix: 日期多参组合补全分量有限性守卫与构造器 TimeClip 收尾
This commit is contained in:
@@ -164,14 +164,19 @@ fn local_components(ms: f64) -> Option<(i64, i64, i64, i64, i64, i64, i64)> {
|
|||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 本地 setter 收尾:组合出的时刻值过 TimeClip 包络,越界(含非有限)为
|
/// TimeClip:|ts| > 8.64e15 或非有限的时刻值归 NaN(Invalid Date)。
|
||||||
/// Invalid Date(存 NaN 并返回 NaN);有效值写回并原样返回。
|
fn time_clip(ts: f64) -> f64 {
|
||||||
fn finish_local_setter(obj: &mut JsObject, ts: f64) -> NativeResult {
|
if ts.is_finite() && (-MS_LIMIT as f64..=MS_LIMIT as f64).contains(&ts) {
|
||||||
let ts = if ts.is_finite() && (-MS_LIMIT as f64..=MS_LIMIT as f64).contains(&ts) {
|
|
||||||
ts
|
ts
|
||||||
} else {
|
} else {
|
||||||
f64::NAN
|
f64::NAN
|
||||||
};
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 本地 setter 收尾:组合出的时刻值过 TimeClip 包络,越界(含非有限)为
|
||||||
|
/// Invalid Date(存 NaN 并返回 NaN);有效值写回并原样返回。
|
||||||
|
fn finish_local_setter(obj: &mut JsObject, ts: f64) -> NativeResult {
|
||||||
|
let ts = time_clip(ts);
|
||||||
set_timestamp(obj, ts);
|
set_timestamp(obj, ts);
|
||||||
NativeResult::Ok(JsValue::float(ts))
|
NativeResult::Ok(JsValue::float(ts))
|
||||||
}
|
}
|
||||||
@@ -205,17 +210,33 @@ macro_rules! apply_date_result {
|
|||||||
/// 再按本地时区映射到真实 UTC 时刻。
|
/// 再按本地时区映射到真实 UTC 时刻。
|
||||||
///
|
///
|
||||||
/// # 边界与前提
|
/// # 边界与前提
|
||||||
/// - 调用方保证各分量已 ToNumber 且非 NaN(任一 NaN 由调用方短路)。
|
/// - 任一分量非有限(±Infinity 或 NaN)直接返回 NaN。
|
||||||
/// - 分量按 ToIntegerOrInfinity 截断;超规范包络(|时刻| > 275760 年)的组合时刻由调用方 TimeClip 归 NaN。
|
/// - 分量按 ToIntegerOrInfinity 截断;年/月进位远超包络(日数公式乘积溢出带)
|
||||||
|
/// 直接返回 NaN。
|
||||||
|
/// - naive 时刻超 TimeClip 包络(|naive_ms| > 8.64e15)返回 NaN,同时兜住
|
||||||
|
/// i64 转换的饱和带;最终 UTC 时刻的 TimeClip 由调用方收尾(finish_local_setter
|
||||||
|
/// 或构造器 time_clip)。
|
||||||
fn make_local_timestamp(y: f64, m: f64, d: f64, h: f64, min: f64, sec: f64, ms: f64) -> f64 {
|
fn make_local_timestamp(y: f64, m: f64, d: f64, h: f64, min: f64, sec: f64, ms: f64) -> f64 {
|
||||||
// 远超包络的分量直接拒绝(避免后续整型运算溢出)。
|
// 任一非有限分量按规范无效,同时封死后续整型饱和带。
|
||||||
if y.abs() > 2e15 {
|
if !y.is_finite()
|
||||||
|
|| !m.is_finite()
|
||||||
|
|| !d.is_finite()
|
||||||
|
|| !h.is_finite()
|
||||||
|
|| !min.is_finite()
|
||||||
|
|| !sec.is_finite()
|
||||||
|
|| !ms.is_finite()
|
||||||
|
{
|
||||||
return f64::NAN;
|
return f64::NAN;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 月溢出归一到 [0, 12),商进位到年份(负月同样成立)。
|
// 月溢出归一到 [0, 12),商进位到年份(负月同样成立)。
|
||||||
let m_norm = m.trunc().rem_euclid(12.0);
|
let m_norm = m.trunc().rem_euclid(12.0);
|
||||||
let y_carry = (m.trunc() / 12.0).floor();
|
let y_carry = (m.trunc() / 12.0).floor();
|
||||||
|
|
||||||
|
// 年与月进位远超包络时直接拒绝,避免日数公式乘积溢出。
|
||||||
|
if y.abs() > 2e15 || y_carry.abs() > 2e15 {
|
||||||
|
return f64::NAN;
|
||||||
|
}
|
||||||
let y = (y.trunc() + y_carry) as i64;
|
let y = (y.trunc() + y_carry) as i64;
|
||||||
|
|
||||||
// 基准取当月 1 号零点,全部分量统一折算为 i64 毫秒,
|
// 基准取当月 1 号零点,全部分量统一折算为 i64 毫秒,
|
||||||
@@ -223,7 +244,8 @@ fn make_local_timestamp(y: f64, m: f64, d: f64, h: f64, min: f64, sec: f64, ms:
|
|||||||
let day_ms = civil_day_count(y, m_norm as i64 + 1, 1) as f64 * 86_400_000.0;
|
let day_ms = civil_day_count(y, m_norm as i64 + 1, 1) as f64 * 86_400_000.0;
|
||||||
let time_ms = h.trunc() * 3_600_000.0 + min.trunc() * 60_000.0 + sec.trunc() * 1_000.0 + ms.trunc();
|
let time_ms = h.trunc() * 3_600_000.0 + min.trunc() * 60_000.0 + sec.trunc() * 1_000.0 + ms.trunc();
|
||||||
let naive_ms = day_ms + (d.trunc() - 1.0) * 86_400_000.0 + time_ms;
|
let naive_ms = day_ms + (d.trunc() - 1.0) * 86_400_000.0 + time_ms;
|
||||||
if !naive_ms.is_finite() {
|
// 组合超 TimeClip 包络即无效,同时兜住 i64 转换的饱和带。
|
||||||
|
if !naive_ms.is_finite() || naive_ms.abs() > MS_LIMIT as f64 {
|
||||||
return f64::NAN;
|
return f64::NAN;
|
||||||
}
|
}
|
||||||
let naive_ms = naive_ms as i64;
|
let naive_ms = naive_ms as i64;
|
||||||
@@ -363,7 +385,8 @@ pub fn date_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
|||||||
{
|
{
|
||||||
f64::NAN
|
f64::NAN
|
||||||
} else {
|
} else {
|
||||||
make_local_timestamp(y_val, m_val, d_val, h_val, min_val, sec_val, ms_val)
|
// 组合结果与 setter 同收尾:TimeClip 包络外(含非有限)归 NaN。
|
||||||
|
time_clip(make_local_timestamp(y_val, m_val, d_val, h_val, min_val, sec_val, ms_val))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let val = vm.reg(args[1]);
|
let val = vm.reg(args[1]);
|
||||||
|
|||||||
@@ -98,3 +98,21 @@ fn typed_array_fill_finite_negative_huge_start() {
|
|||||||
let s = eval_str("new Uint8Array(4).fill(0, -1e300).join(',')").unwrap();
|
let s = eval_str("new Uint8Array(4).fill(0, -1e300).join(',')").unwrap();
|
||||||
assert_eq!(s, "0,0,0,0");
|
assert_eq!(s, "0,0,0,0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 月份分量 ±Infinity/有限巨值:构造器与 setter 均直接 NaN(debug 构建不得整型溢出)。
|
||||||
|
#[test]
|
||||||
|
fn date_constructor_huge_month_is_nan() {
|
||||||
|
assert_nan("new Date(2020, Infinity, 1).getTime()");
|
||||||
|
assert_nan("new Date(2020, -Infinity, 1).getTime()");
|
||||||
|
assert_nan("new Date(2020, 1e300, 1).getTime()");
|
||||||
|
assert_nan("new Date(2020, 0, 1).setMonth(1e300)");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 年分量超 TimeClip 包络:构造器组合结果归 NaN;包络内邻近组合不损伤
|
||||||
|
// (valueOf 直接读槽位,不经 chrono 年份上限,可钉超 262142 年存储值)。
|
||||||
|
#[test]
|
||||||
|
fn date_constructor_out_of_envelope_year_is_nan() {
|
||||||
|
assert_nan("new Date(275760, 8, 13, 1).getTime()");
|
||||||
|
let (_vm, result) = eval("new Date(275759, 11, 31).valueOf()").unwrap();
|
||||||
|
assert!(result.is_double() && result.as_double().is_finite(), "包络内组合应得有限时间戳");
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user