mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: 新增 PlainMonthDay 算术与 Instant 转换方法
This commit is contained in:
Generated
+21
-21
@@ -1160,7 +1160,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_builtins"
|
name = "oxide_builtins"
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"fast-float",
|
"fast-float",
|
||||||
@@ -1185,7 +1185,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_bytecode"
|
name = "oxide_bytecode"
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"oxide_log",
|
"oxide_log",
|
||||||
@@ -1193,7 +1193,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_cfg"
|
name = "oxide_cfg"
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_emit",
|
"oxide_emit",
|
||||||
@@ -1204,7 +1204,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_cli"
|
name = "oxide_cli"
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"ansi_term",
|
"ansi_term",
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
@@ -1228,7 +1228,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_code_cache"
|
name = "oxide_code_cache"
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"lru",
|
"lru",
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
@@ -1237,7 +1237,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_compiler"
|
name = "oxide_compiler"
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_cfg",
|
"oxide_cfg",
|
||||||
@@ -1258,7 +1258,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_dce"
|
name = "oxide_dce"
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_cfg",
|
"oxide_cfg",
|
||||||
@@ -1272,7 +1272,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_emit"
|
name = "oxide_emit"
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"oxc_allocator",
|
"oxc_allocator",
|
||||||
@@ -1288,7 +1288,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_engine"
|
name = "oxide_engine"
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_compiler",
|
"oxide_compiler",
|
||||||
@@ -1300,7 +1300,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_fold"
|
name = "oxide_fold"
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
@@ -1312,7 +1312,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_ir"
|
name = "oxide_ir"
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_log",
|
"oxide_log",
|
||||||
@@ -1321,7 +1321,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_kernel"
|
name = "oxide_kernel"
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
@@ -1335,7 +1335,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_liveness"
|
name = "oxide_liveness"
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_cfg",
|
"oxide_cfg",
|
||||||
@@ -1347,7 +1347,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_log"
|
name = "oxide_log"
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"tracing",
|
"tracing",
|
||||||
"tracing-appender",
|
"tracing-appender",
|
||||||
@@ -1356,11 +1356,11 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_net"
|
name = "oxide_net"
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_parser"
|
name = "oxide_parser"
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxc_allocator",
|
"oxc_allocator",
|
||||||
"oxc_ast",
|
"oxc_ast",
|
||||||
@@ -1375,7 +1375,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_regalloc"
|
name = "oxide_regalloc"
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_cfg",
|
"oxide_cfg",
|
||||||
@@ -1391,7 +1391,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_runtime_api"
|
name = "oxide_runtime_api"
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
@@ -1404,7 +1404,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_test262"
|
name = "oxide_test262"
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"oxide_bytecode",
|
"oxide_bytecode",
|
||||||
"oxide_compiler",
|
"oxide_compiler",
|
||||||
@@ -1424,7 +1424,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_types"
|
name = "oxide_types"
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
@@ -1437,7 +1437,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "oxide_vm"
|
name = "oxide_vm"
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bumpalo",
|
"bumpalo",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
|||||||
+1
-1
@@ -25,7 +25,7 @@ members = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.19.13"
|
version = "0.19.14"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT"
|
license = "MIT"
|
||||||
rust-version = "1.80"
|
rust-version = "1.80"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
use num_traits::ToPrimitive;
|
use num_traits::ToPrimitive;
|
||||||
|
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||||
use oxide_runtime_api::{to_number, to_string, NativeResult, ProtoKind, VmHost};
|
use oxide_runtime_api::{to_number, to_string, NativeResult, ProtoKind, VmHost};
|
||||||
use oxide_types::object::JsObject;
|
use oxide_types::object::JsObject;
|
||||||
use oxide_types::value::JsValue;
|
use oxide_types::value::JsValue;
|
||||||
@@ -937,6 +938,71 @@ pub fn instant_to_zoned_date_time_iso<H: VmHost>(vm: &mut H, args: &[u8]) -> Nat
|
|||||||
make_zoned_date_time(vm, epoch_ns, &time_zone_id, "iso8601")
|
make_zoned_date_time(vm, epoch_ns, &time_zone_id, "iso8601")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// ToNumeric 语义的纪元纳秒取值:number 须为有限整数(否则 RangeError),
|
||||||
|
/// BigInt 直读 i128;其余原始值 → TypeError。
|
||||||
|
fn to_numeric_epoch_ns<H: VmHost>(vm: &mut H, value: JsValue) -> Result<i128, JsValue> {
|
||||||
|
if value.is_number() {
|
||||||
|
let number = to_number(value);
|
||||||
|
if !number.is_finite() || number.fract() != 0.0 {
|
||||||
|
return Err(crate::error::create_range_error(vm, "invalid epochNanoseconds"));
|
||||||
|
}
|
||||||
|
return Ok(number as i128);
|
||||||
|
}
|
||||||
|
if value.is_bigint() {
|
||||||
|
return Ok(vm.bigint_value(value).to_i128().unwrap_or(i128::MAX));
|
||||||
|
}
|
||||||
|
Err(crate::error::create_type_error(vm, "invalid epochNanoseconds"))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Temporal.Instant.prototype.with(properties)`:properties.epochNanoseconds
|
||||||
|
/// (number 或 BigInt)须过 Instant 范围,返回新 Instant;options 不读取。
|
||||||
|
///
|
||||||
|
/// # 步骤
|
||||||
|
/// 1. receiver branding(TypeError)。
|
||||||
|
/// 2. properties 非对象 → TypeError。
|
||||||
|
/// 3. Get epochNanoseconds:undefined → TypeError;ToNumeric 取值。
|
||||||
|
/// 4. 绝对值超 MAX_INSTANT_NS → RangeError;make_instant 重建。
|
||||||
|
pub fn instant_with<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let ptr = native_try!(receiver_obj(vm, args));
|
||||||
|
let obj = unsafe { &*ptr };
|
||||||
|
native_try!(ensure_instant(vm, obj));
|
||||||
|
let properties = if args.len() < 2 { JsValue::undefined() } else { vm.reg(args[1]) };
|
||||||
|
if !properties.is_object() {
|
||||||
|
return NativeResult::Err(crate::error::create_type_error(vm, "properties must be an object"));
|
||||||
|
}
|
||||||
|
let pptr = properties.as_js_object_ptr();
|
||||||
|
if pptr.is_null() {
|
||||||
|
return NativeResult::Err(crate::error::create_type_error(vm, "properties must be an object"));
|
||||||
|
}
|
||||||
|
let properties_obj = unsafe { &*pptr };
|
||||||
|
let raw = native_try!(temporal_option_value(vm, properties_obj, properties, "epochNanoseconds"));
|
||||||
|
if raw.is_undefined() {
|
||||||
|
return NativeResult::Err(crate::error::create_type_error(vm, "epochNanoseconds is required"));
|
||||||
|
}
|
||||||
|
let epoch_ns = native_try!(to_numeric_epoch_ns(vm, raw));
|
||||||
|
if epoch_ns.unsigned_abs() > MAX_INSTANT_NS as u128 {
|
||||||
|
return NativeResult::Err(crate::error::create_range_error(vm, "Instant outside supported range"));
|
||||||
|
}
|
||||||
|
make_instant(vm, epoch_ns)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Temporal.Instant.prototype.toDate()`:纪元纳秒按毫秒取整(floor)后
|
||||||
|
/// 产出 Date 对象(同一时刻,亚毫秒精度丢失,与 epochMilliseconds getter 同口径)。
|
||||||
|
pub fn instant_to_date<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let ptr = native_try!(receiver_obj(vm, args));
|
||||||
|
let obj = unsafe { &*ptr };
|
||||||
|
native_try!(ensure_instant(vm, obj));
|
||||||
|
let Some(epoch_ns) = get_instant_epoch_ns(obj) else {
|
||||||
|
return NativeResult::Err(crate::error::create_range_error(vm, "invalid Instant"));
|
||||||
|
};
|
||||||
|
let epoch_ms = epoch_ns.div_euclid(1_000_000) as f64;
|
||||||
|
let mut date =
|
||||||
|
JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::from_js_object(vm.builtin_proto(ProtoKind::DateProto)));
|
||||||
|
date.type_tag = JsObject::OBJ_TYPE_DATE;
|
||||||
|
date.set_prop_at(0, JsValue::float(epoch_ms));
|
||||||
|
NativeResult::Ok(JsValue::from_js_object(vm.alloc_object(date)))
|
||||||
|
}
|
||||||
|
|
||||||
/// `Temporal.Instant.prototype.valueOf()`:Temporal 对象禁止转原始值。
|
/// `Temporal.Instant.prototype.valueOf()`:Temporal 对象禁止转原始值。
|
||||||
pub fn instant_value_of<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
|
pub fn instant_value_of<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
|
||||||
NativeResult::Err(crate::error::create_type_error(vm, "Temporal.Instant has no valueOf"))
|
NativeResult::Err(crate::error::create_type_error(vm, "Temporal.Instant has no valueOf"))
|
||||||
|
|||||||
@@ -6,12 +6,12 @@ use oxide_types::value::JsValue;
|
|||||||
|
|
||||||
use super::common::native_try;
|
use super::common::native_try;
|
||||||
use super::{
|
use super::{
|
||||||
calendar_annotation, days_from_civil, days_in_month_iso, ensure_plain_month_day, format_iso_year, get_calendar_id,
|
calendar_annotation, days_from_civil, days_in_month_iso, duration_like_values, ensure_plain_month_day,
|
||||||
get_double_prop, initialize_temporal_receiver, is_ctor_call, make_plain_date, make_plain_month_day,
|
format_iso_year, get_calendar_id, get_double_prop, initialize_temporal_receiver, is_ctor_call,
|
||||||
month_day_bag_calendar_id, read_iso2, receiver_obj, reject_partial_object_with_calendar_or_time_zone,
|
make_plain_date, make_plain_month_day, month_day_bag_calendar_id, parse_round_options, read_iso2, receiver_obj,
|
||||||
strip_iso_offset, temporal_calendar_id_strict, temporal_number_component, temporal_option_string,
|
reject_partial_object_with_calendar_or_time_zone, strip_iso_offset, temporal_calendar_id_strict,
|
||||||
temporal_option_value, temporal_overflow, temporal_to_show_calendar, valid_iso_date, valid_plain_time,
|
temporal_number_component, temporal_option_string, temporal_option_value, temporal_overflow,
|
||||||
ShowCalendar,
|
temporal_to_show_calendar, valid_iso_date, valid_plain_time, ShowCalendar,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// 读 PlainMonthDay 的 月/日/参考年 三元组(槽 0-2),含 receiver 校验。
|
/// 读 PlainMonthDay 的 月/日/参考年 三元组(槽 0-2),含 receiver 校验。
|
||||||
@@ -746,3 +746,109 @@ pub fn plain_month_day_to_plain_date<H: VmHost>(vm: &mut H, args: &[u8]) -> Nati
|
|||||||
let calendar = get_calendar_id(obj, 3);
|
let calendar = get_calendar_id(obj, 3);
|
||||||
make_plain_date(vm, year, month, day, &calendar)
|
make_plain_date(vm, year, month, day, &calendar)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== PlainMonthDay add/subtract/round ====================
|
||||||
|
|
||||||
|
/// `Temporal.PlainMonthDay.prototype.add/subtract(durationLike [, options])` 核心。
|
||||||
|
///
|
||||||
|
/// # 步骤
|
||||||
|
/// 1. receiver branding(TypeError)。
|
||||||
|
/// 2. ToTemporalDuration(串 / bag / 实例;TypeError、RangeError 各自语义)。
|
||||||
|
/// 3. 读 overflow(缺省 constrain)。
|
||||||
|
/// 4. years / weeks / days / 时间分量任一非零 → RangeError(PMD 无年与日分量)。
|
||||||
|
/// 5. (年, 月) 按参考年(槽 2)折算绝对月数后平衡,日按 overflow 规整
|
||||||
|
/// (constrain 钳到目标年月长,reject 非法即抛)。
|
||||||
|
/// 6. 结果 (年, 月, 日) 过 ISODateWithinLimits(day 级)→ 越界 RangeError。
|
||||||
|
///
|
||||||
|
/// # 边界与副作用
|
||||||
|
/// - 结果参考年为算术后的年,日历标识取自 receiver 槽 3。
|
||||||
|
/// - subtract 与 add 共用本函数,sign 参数取反月分量。
|
||||||
|
fn plain_month_day_apply_duration<H: VmHost>(vm: &mut H, args: &[u8], sign: i64) -> NativeResult {
|
||||||
|
let ptr = native_try!(receiver_obj(vm, args));
|
||||||
|
let obj = unsafe { &*ptr };
|
||||||
|
native_try!(ensure_plain_month_day(vm, obj));
|
||||||
|
let month = get_double_prop(obj, 0) as i64;
|
||||||
|
let day = get_double_prop(obj, 1) as i64;
|
||||||
|
let ref_year = get_double_prop(obj, 2) as i64;
|
||||||
|
let calendar = get_calendar_id(obj, 3);
|
||||||
|
let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||||
|
let values = native_try!(duration_like_values(vm, val));
|
||||||
|
let constrain = native_try!(temporal_overflow(vm, args));
|
||||||
|
// PMD 无年分量:years 与 weeks/days/时间分量均须为零。
|
||||||
|
if values[0] != 0.0 || values[2..].iter().any(|value| *value != 0.0) {
|
||||||
|
return NativeResult::Err(crate::error::create_range_error(
|
||||||
|
vm,
|
||||||
|
"month-day cannot add years, weeks, days, or time units",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
// BalanceISOYearMonth:月折算绝对月数后平衡(days 恒 0,日不参与)。
|
||||||
|
let months = sign * values[1] as i64;
|
||||||
|
let absolute = ref_year * 12 + month - 1 + months;
|
||||||
|
let year = absolute.div_euclid(12);
|
||||||
|
let month = absolute.rem_euclid(12) + 1;
|
||||||
|
// RegulateISODate:constrain 钳日到目标年月长,reject 非法即抛。
|
||||||
|
let day = if constrain {
|
||||||
|
day.min(days_in_month_iso(year as i32, month as u32) as i64)
|
||||||
|
} else {
|
||||||
|
if !valid_iso_date(year as i32, month as u32, day as u32) {
|
||||||
|
return NativeResult::Err(crate::error::create_range_error(vm, "invalid ISO date"));
|
||||||
|
}
|
||||||
|
day
|
||||||
|
};
|
||||||
|
// ISODateWithinLimits(day 级):-100_000_001..=100_000_000。
|
||||||
|
let day_count = days_from_civil(i128::from(year), i128::from(month), i128::from(day));
|
||||||
|
if !(-100_000_001..=100_000_000).contains(&day_count) {
|
||||||
|
return NativeResult::Err(crate::error::create_range_error(vm, "month-day is out of range"));
|
||||||
|
}
|
||||||
|
make_plain_month_day(vm, month as u32, day as u32, year as i32, &calendar)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Temporal.PlainMonthDay.prototype.add(durationLike)`。
|
||||||
|
pub fn plain_month_day_add<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
plain_month_day_apply_duration(vm, args, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Temporal.PlainMonthDay.prototype.subtract(durationLike)`。
|
||||||
|
pub fn plain_month_day_subtract<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
plain_month_day_apply_duration(vm, args, -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// `Temporal.PlainMonthDay.prototype.round(roundTo)`:smallestUnit 仅 "day",
|
||||||
|
/// 增量须为 1(日已是整数,舍入为恒等),日钳到参考年月长后过表示范围。
|
||||||
|
///
|
||||||
|
/// # 步骤
|
||||||
|
/// 1. receiver 品牌校验;读槽 0-3 入本地(参考年与日历槽继承 receiver)。
|
||||||
|
/// 2. roundTo 经 parse_round_options(undefined → TypeError;字符串简写;对象固定读序)。
|
||||||
|
/// 3. smallestUnit 缺失 → RangeError;非 day → RangeError;增量 > 1 → RangeError。
|
||||||
|
/// 4. 日钳到参考年月长(constrain 语义);valid_iso_date 与 day 级表示范围检查。
|
||||||
|
/// 5. make_plain_month_day 重建(参考年与日历槽继承 receiver)。
|
||||||
|
pub fn plain_month_day_round<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||||
|
let ptr = native_try!(receiver_obj(vm, args));
|
||||||
|
let obj = unsafe { &*ptr };
|
||||||
|
native_try!(ensure_plain_month_day(vm, obj));
|
||||||
|
let month = get_double_prop(obj, 0) as u32;
|
||||||
|
let day = get_double_prop(obj, 1) as u32;
|
||||||
|
let ref_year = get_double_prop(obj, 2) as i32;
|
||||||
|
let calendar = get_calendar_id(obj, 3);
|
||||||
|
let round_to = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||||
|
let (increment, _mode, unit_value) = native_try!(parse_round_options(vm, round_to));
|
||||||
|
let Some(unit_value) = unit_value else {
|
||||||
|
return NativeResult::Err(crate::error::create_range_error(vm, "smallestUnit is required"));
|
||||||
|
};
|
||||||
|
if unit_value != "day" && unit_value != "days" {
|
||||||
|
return NativeResult::Err(crate::error::create_range_error(vm, "invalid smallest unit"));
|
||||||
|
}
|
||||||
|
if increment > 1 {
|
||||||
|
return NativeResult::Err(crate::error::create_range_error(vm, "invalid rounding increment"));
|
||||||
|
}
|
||||||
|
// 日已是整数且增量恒 1,舍入为恒等;钳到参考年月长。
|
||||||
|
let day = day.min(days_in_month_iso(ref_year, month));
|
||||||
|
if !valid_iso_date(ref_year, month, day) {
|
||||||
|
return NativeResult::Err(crate::error::create_range_error(vm, "month-day is out of range"));
|
||||||
|
}
|
||||||
|
let day_count = days_from_civil(i128::from(ref_year), i128::from(month), i128::from(day));
|
||||||
|
if !(-100_000_001..=100_000_000).contains(&day_count) {
|
||||||
|
return NativeResult::Err(crate::error::create_range_error(vm, "month-day is out of range"));
|
||||||
|
}
|
||||||
|
make_plain_month_day(vm, month, day, ref_year, &calendar)
|
||||||
|
}
|
||||||
|
|||||||
@@ -149,6 +149,8 @@ pub fn bind_temporal(env: BindEnv<'_>, global: &mut JsObject) {
|
|||||||
),
|
),
|
||||||
("until", oxide_builtins::temporal::instant_until::<crate::vm::Vm> as *const (), 1),
|
("until", oxide_builtins::temporal::instant_until::<crate::vm::Vm> as *const (), 1),
|
||||||
("valueOf", oxide_builtins::temporal::instant_value_of::<crate::vm::Vm> as *const (), 0),
|
("valueOf", oxide_builtins::temporal::instant_value_of::<crate::vm::Vm> as *const (), 0),
|
||||||
|
("with", oxide_builtins::temporal::instant_with::<crate::vm::Vm> as *const (), 1),
|
||||||
|
("toDate", oxide_builtins::temporal::instant_to_date::<crate::vm::Vm> as *const (), 0),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
bind_well_known_data_property(
|
bind_well_known_data_property(
|
||||||
@@ -1063,6 +1065,13 @@ pub fn bind_temporal(env: BindEnv<'_>, global: &mut JsObject) {
|
|||||||
oxide_builtins::temporal::plain_month_day_to_plain_date::<crate::vm::Vm> as *const (),
|
oxide_builtins::temporal::plain_month_day_to_plain_date::<crate::vm::Vm> as *const (),
|
||||||
1,
|
1,
|
||||||
),
|
),
|
||||||
|
("add", oxide_builtins::temporal::plain_month_day_add::<crate::vm::Vm> as *const (), 1),
|
||||||
|
(
|
||||||
|
"subtract",
|
||||||
|
oxide_builtins::temporal::plain_month_day_subtract::<crate::vm::Vm> as *const (),
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
("round", oxide_builtins::temporal::plain_month_day_round::<crate::vm::Vm> as *const (), 1),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
bind_well_known_data_property(
|
bind_well_known_data_property(
|
||||||
|
|||||||
@@ -412,6 +412,61 @@ fn instant_from_invalid_throws_range_error() {
|
|||||||
assert_eq!(str_val(&vm, r), "RangeError");
|
assert_eq!(str_val(&vm, r), "RangeError");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn instant_with_epoch_nanoseconds() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
// with:epochNanoseconds 按 ToNumeric 取值(number 须有限整数、BigInt 直读);
|
||||||
|
// 越界 → RangeError;缺失/非对象 → TypeError;非整数/NaN → RangeError;
|
||||||
|
// 边界值 ±MAX_INSTANT_NS 合法。
|
||||||
|
let r = eval(
|
||||||
|
&mut vm,
|
||||||
|
"(() => {
|
||||||
|
const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } };
|
||||||
|
const i = Temporal.Instant.fromEpochNanoseconds(0n);
|
||||||
|
return [
|
||||||
|
i.with({epochNanoseconds: 123456789n}).epochNanoseconds,
|
||||||
|
i.with({epochNanoseconds: 5000000}).epochNanoseconds,
|
||||||
|
kind(() => i.with({epochNanoseconds: 8640000000000000000001n})),
|
||||||
|
kind(() => i.with({epochNanoseconds: -8640000000000000000001n})),
|
||||||
|
i.with({epochNanoseconds: 8640000000000000000000n}).epochNanoseconds,
|
||||||
|
i.with({epochNanoseconds: -8640000000000000000000n}).epochNanoseconds,
|
||||||
|
kind(() => i.with({})),
|
||||||
|
kind(() => i.with(5)),
|
||||||
|
kind(() => i.with({epochNanoseconds: 1.5})),
|
||||||
|
kind(() => i.with({epochNanoseconds: NaN})),
|
||||||
|
kind(() => i.with({epochNanoseconds: '1'})),
|
||||||
|
].join('|');
|
||||||
|
})()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
str_val(&vm, r),
|
||||||
|
"123456789|5000000|RangeError|RangeError|8640000000000000000000|-8640000000000000000000|TypeError|TypeError|RangeError|RangeError|TypeError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn instant_to_date() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
// toDate:纳秒按毫秒向下取整(与 epochMilliseconds getter 同口径),
|
||||||
|
// 负值向 -Inf 取整;结果是真实 Date(instanceof + getTime)。
|
||||||
|
let r = eval(
|
||||||
|
&mut vm,
|
||||||
|
"(() => {
|
||||||
|
const d = Temporal.Instant.fromEpochNanoseconds(1234567890123456789n).toDate();
|
||||||
|
return [
|
||||||
|
d instanceof Date,
|
||||||
|
d.getTime(),
|
||||||
|
Temporal.Instant.fromEpochNanoseconds(-1n).toDate().getTime(),
|
||||||
|
Temporal.Instant.fromEpochNanoseconds(-1000001n).toDate().getTime(),
|
||||||
|
Temporal.Instant.fromEpochNanoseconds(1000000000n).toDate().getTime(),
|
||||||
|
].join('|');
|
||||||
|
})()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(str_val(&vm, r), "true|1234567890123|-1|-2|1000");
|
||||||
|
}
|
||||||
|
|
||||||
// -- Temporal.PlainDate --
|
// -- Temporal.PlainDate --
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -3166,6 +3221,101 @@ fn plain_month_day_to_plain_date_matrix() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn plain_month_day_add_balance_and_clamp() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
// add:月按(参考年, 月)平衡,负年经 div_euclid 处理;日钳到目标年月长
|
||||||
|
// (constrain 缺省,reject 非法即抛);years/weeks/days/时间分量 → RangeError;
|
||||||
|
// 无参 → TypeError;day 级表示范围 ±100_000_00x。
|
||||||
|
let r = eval(
|
||||||
|
&mut vm,
|
||||||
|
"(() => {
|
||||||
|
const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } };
|
||||||
|
const md = Temporal.PlainMonthDay.from('01-31');
|
||||||
|
return [
|
||||||
|
md.add('P1M').toString(),
|
||||||
|
kind(() => md.add('P1M', {overflow: 'reject'})),
|
||||||
|
Temporal.PlainMonthDay.from('02-29').add('P1M').toString(),
|
||||||
|
Temporal.PlainMonthDay.from('12-15').add('P1M').toString(),
|
||||||
|
Temporal.PlainMonthDay.from('01-15').add('P13M').toString(),
|
||||||
|
Temporal.PlainMonthDay.from('01-15').add({months: -1}).toString(),
|
||||||
|
new Temporal.PlainMonthDay(1, 15, 'iso8601', -1).add({months: -1}).toString(),
|
||||||
|
Temporal.PlainMonthDay.from('01-15').add({months: 1}).toString(),
|
||||||
|
kind(() => Temporal.PlainMonthDay.from('01-15').add('P1Y')),
|
||||||
|
kind(() => Temporal.PlainMonthDay.from('01-15').add('P1W')),
|
||||||
|
kind(() => Temporal.PlainMonthDay.from('01-15').add('P1D')),
|
||||||
|
kind(() => Temporal.PlainMonthDay.from('01-15').add('PT1H')),
|
||||||
|
kind(() => Temporal.PlainMonthDay.from('01-15').add()),
|
||||||
|
kind(() => new Temporal.PlainMonthDay(10, 31, 'iso8601', 275760).add('P1M')),
|
||||||
|
].join('|');
|
||||||
|
})()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
str_val(&vm, r),
|
||||||
|
"02-29|RangeError|03-29|01-15|02-15|12-15|12-15|02-15|RangeError|RangeError|RangeError|RangeError|TypeError|RangeError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn plain_month_day_subtract_balance_and_clamp() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
// subtract:月分量反向(sign 取反);日钳到目标年月长;
|
||||||
|
// years/weeks/days/时间分量 → RangeError;day 级表示范围下界。
|
||||||
|
let r = eval(
|
||||||
|
&mut vm,
|
||||||
|
"(() => {
|
||||||
|
const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } };
|
||||||
|
return [
|
||||||
|
Temporal.PlainMonthDay.from('03-31').subtract('P1M').toString(),
|
||||||
|
kind(() => Temporal.PlainMonthDay.from('03-31').subtract('P1M', {overflow: 'reject'})),
|
||||||
|
Temporal.PlainMonthDay.from('01-15').subtract('P13M').toString(),
|
||||||
|
new Temporal.PlainMonthDay(1, 15, 'iso8601', -1).subtract('P1M').toString(),
|
||||||
|
kind(() => Temporal.PlainMonthDay.from('01-15').subtract('P1Y')),
|
||||||
|
kind(() => Temporal.PlainMonthDay.from('01-15').subtract('P1D')),
|
||||||
|
kind(() => Temporal.PlainMonthDay.from('01-15').subtract('PT1H')),
|
||||||
|
kind(() => new Temporal.PlainMonthDay(3, 10, 'iso8601', -271821).subtract('P1M')),
|
||||||
|
].join('|');
|
||||||
|
})()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
str_val(&vm, r),
|
||||||
|
"02-29|RangeError|12-15|12-15|RangeError|RangeError|RangeError|RangeError"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn plain_month_day_round_identity_and_validation() {
|
||||||
|
let mut vm = Vm::new();
|
||||||
|
// round:日已是整数且增量恒 1,舍入为恒等;日钳到参考年月长
|
||||||
|
// (from('02-31') 串路径不校验日,可造出超长按日);smallestUnit 须 day
|
||||||
|
// (缺失/其他 → RangeError);增量 > 1 → RangeError;非串/对象 → TypeError。
|
||||||
|
let r = eval(
|
||||||
|
&mut vm,
|
||||||
|
"(() => {
|
||||||
|
const kind = (fn) => { try { fn(); return 'no'; } catch (e) { return e.constructor.name; } };
|
||||||
|
const leap = Temporal.PlainMonthDay.from('02-29');
|
||||||
|
return [
|
||||||
|
leap.round('day').toString(),
|
||||||
|
leap.round({smallestUnit: 'days'}).toString(),
|
||||||
|
Temporal.PlainMonthDay.from('02-31').round('day').toString(),
|
||||||
|
kind(() => leap.round('month')),
|
||||||
|
kind(() => leap.round({})),
|
||||||
|
kind(() => leap.round({smallestUnit: 'day', roundingIncrement: 2})),
|
||||||
|
kind(() => leap.round(5)),
|
||||||
|
kind(() => leap.round()),
|
||||||
|
leap.round('day').day,
|
||||||
|
].join('|');
|
||||||
|
})()",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
str_val(&vm, r),
|
||||||
|
"02-29|02-29|02-29|RangeError|RangeError|RangeError|TypeError|TypeError|29"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn plain_year_month_from_string_matrix() {
|
fn plain_year_month_from_string_matrix() {
|
||||||
let mut vm = Vm::new();
|
let mut vm = Vm::new();
|
||||||
|
|||||||
Reference in New Issue
Block a user