mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: Temporal 解锁 + PlainDate 补齐日期派生字段与 add/subtract/equals/compare
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
use chrono::{DateTime, Datelike, NaiveDate, SecondsFormat, Utc};
|
||||
use chrono::{DateTime, Datelike, Days, Months, NaiveDate, SecondsFormat, Utc};
|
||||
|
||||
use oxide_kernel::shape_forge::EMPTY_SHAPE_ID;
|
||||
use oxide_runtime_api::{to_number, to_string, NativeResult, VmHost};
|
||||
@@ -462,3 +462,281 @@ pub fn plain_time_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
|
||||
NativeResult::Ok(vm.new_string(&format!("{h:02}:{m:02}:{s:02}.{digits}")))
|
||||
}
|
||||
}
|
||||
|
||||
// ───────────────────── PlainDate 扩展方法 ─────────────────────
|
||||
|
||||
/// 从 PlainDate receiver 读 year/month/day 并构造 chrono NaiveDate(供日期计算)。
|
||||
fn plain_date_naive<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<NaiveDate, JsValue> {
|
||||
let (y, m, d) = plain_date_ymd(vm, args)?;
|
||||
NaiveDate::from_ymd_opt(y as i32, m as u32, d as u32)
|
||||
.ok_or_else(|| crate::error::create_range_error(vm, "invalid date"))
|
||||
}
|
||||
|
||||
/// 从对象式日期字段(`{year, month, day}`)读三字段;PlainDate 对象直接读内部槽;
|
||||
/// 缺字段返回 None。
|
||||
fn object_ymd<H: VmHost>(vm: &mut H, val: JsValue) -> Option<(i32, u32, u32)> {
|
||||
if !val.is_object() {
|
||||
return None;
|
||||
}
|
||||
let obj = unsafe { &*val.as_js_object_ptr() };
|
||||
if obj.is_plain_date_obj() {
|
||||
return Some((
|
||||
get_double_prop(obj, 0) as i32,
|
||||
get_double_prop(obj, 1) as u32,
|
||||
get_double_prop(obj, 2) as u32,
|
||||
));
|
||||
}
|
||||
let y = read_prop_number(vm, obj, val, "year");
|
||||
let m = read_prop_number(vm, obj, val, "month");
|
||||
let d = read_prop_number(vm, obj, val, "day");
|
||||
if y.is_nan() || m.is_nan() || d.is_nan() {
|
||||
return None;
|
||||
}
|
||||
Some((y.trunc() as i32, m.trunc() as u32, d.trunc() as u32))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.dayOfWeek` getter:ISO 周几(周一 1 … 周日 7)。
|
||||
pub fn plain_date_day_of_week<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let date = match plain_date_naive(vm, args) {
|
||||
Ok(d) => d,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
NativeResult::Ok(JsValue::float((date.weekday().num_days_from_monday() + 1) as f64))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.dayOfYear` getter:年内第几天(1 起)。
|
||||
pub fn plain_date_day_of_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let date = match plain_date_naive(vm, args) {
|
||||
Ok(d) => d,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
NativeResult::Ok(JsValue::float(date.ordinal() as f64))
|
||||
}
|
||||
|
||||
/// 计算给定年月的天数(含闰年)。
|
||||
fn days_in_month_iso(year: i32, month: u32) -> u32 {
|
||||
NaiveDate::from_ymd_opt(year, month + 1, 1)
|
||||
.and_then(|next| next.checked_sub_days(Days::new(1)))
|
||||
.map(|d| d.day())
|
||||
.unwrap_or(31)
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.daysInMonth` getter。
|
||||
pub fn plain_date_days_in_month<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let date = match plain_date_naive(vm, args) {
|
||||
Ok(d) => d,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
NativeResult::Ok(JsValue::float(days_in_month_iso(date.year(), date.month()) as f64))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.daysInWeek` getter:恒 7。
|
||||
pub fn plain_date_days_in_week<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let _ = match receiver_obj(vm, args) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
NativeResult::Ok(JsValue::float(7.0))
|
||||
}
|
||||
|
||||
fn is_leap_year_iso(year: i32) -> bool {
|
||||
NaiveDate::from_ymd_opt(year, 2, 29).is_some()
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.daysInYear` getter:366(闰年)或 365。
|
||||
pub fn plain_date_days_in_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let date = match plain_date_naive(vm, args) {
|
||||
Ok(d) => d,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
NativeResult::Ok(JsValue::float(if is_leap_year_iso(date.year()) { 366.0 } else { 365.0 }))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.monthsInYear` getter:恒 12(ISO 日历)。
|
||||
pub fn plain_date_months_in_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let _ = match receiver_obj(vm, args) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
NativeResult::Ok(JsValue::float(12.0))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.inLeapYear` getter。
|
||||
pub fn plain_date_in_leap_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let date = match plain_date_naive(vm, args) {
|
||||
Ok(d) => d,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
NativeResult::Ok(JsValue::bool(is_leap_year_iso(date.year())))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.weekOfYear` getter:ISO 周数。
|
||||
pub fn plain_date_week_of_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let date = match plain_date_naive(vm, args) {
|
||||
Ok(d) => d,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
NativeResult::Ok(JsValue::float(date.iso_week().week() as f64))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.yearOfWeek` getter:ISO 周所属年。
|
||||
pub fn plain_date_year_of_week<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let date = match plain_date_naive(vm, args) {
|
||||
Ok(d) => d,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
NativeResult::Ok(JsValue::float(date.iso_week().year() as f64))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.monthCode` getter:`M01`..`M12`。
|
||||
pub fn plain_date_month_code<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let (_, m, _) = match plain_date_ymd(vm, args) {
|
||||
Ok(x) => x,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
NativeResult::Ok(vm.new_string(&format!("M{m:02}")))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.era` getter:ISO 日历无纪元,恒 undefined。
|
||||
pub fn plain_date_era<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let _ = match receiver_obj(vm, args) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.eraYear` getter:ISO 日历无纪元,恒 undefined。
|
||||
pub fn plain_date_era_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let _ = match receiver_obj(vm, args) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.calendarId` getter:恒 `"iso8601"`。
|
||||
pub fn plain_date_calendar_id<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let _ = match receiver_obj(vm, args) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
NativeResult::Ok(vm.new_string("iso8601"))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.equals(other)`:比较年月日是否相等。
|
||||
pub fn plain_date_equals<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let (y, m, d) = match plain_date_ymd(vm, args) {
|
||||
Ok(x) => x,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
let other_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let equal = match object_ymd(vm, other_val) {
|
||||
Some((oy, om, od)) => oy as f64 == y && om as f64 == m && od as f64 == d,
|
||||
None => false,
|
||||
};
|
||||
NativeResult::Ok(JsValue::bool(equal))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.compare(a, b)`:静态比较,返回 -1/0/1。
|
||||
pub fn plain_date_compare<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let a_val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let b_val = if args.len() > 2 { vm.reg(args[2]) } else { JsValue::undefined() };
|
||||
let a = match object_ymd(vm, a_val) {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "cannot convert to PlainDate"));
|
||||
}
|
||||
};
|
||||
let b = match object_ymd(vm, b_val) {
|
||||
Some(x) => x,
|
||||
None => {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "cannot convert to PlainDate"));
|
||||
}
|
||||
};
|
||||
let cmp = (a.0, a.1, a.2).cmp(&(b.0, b.1, b.2));
|
||||
NativeResult::Ok(JsValue::float(match cmp {
|
||||
std::cmp::Ordering::Less => -1.0,
|
||||
std::cmp::Ordering::Equal => 0.0,
|
||||
std::cmp::Ordering::Greater => 1.0,
|
||||
}))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.valueOf()`:Temporal 对象禁止转原始值。
|
||||
pub fn plain_date_value_of<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
|
||||
NativeResult::Err(crate::error::create_type_error(vm, "Temporal.PlainDate has no valueOf"))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.toJSON()`:输出 ISO 日期串(同 toString)。
|
||||
pub fn plain_date_to_json<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let ptr = match receiver_obj(vm, args) {
|
||||
Ok(p) => p,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
let obj = unsafe { &*ptr };
|
||||
if let Err(e) = ensure_plain_date(vm, obj) {
|
||||
return NativeResult::Err(e);
|
||||
}
|
||||
let year = get_double_prop(obj, 0) as i32;
|
||||
let month = get_double_prop(obj, 1) as u32;
|
||||
let day = get_double_prop(obj, 2) as u32;
|
||||
NativeResult::Ok(vm.new_string(&format!("{year:04}-{month:02}-{day:02}")))
|
||||
}
|
||||
|
||||
/// 从 duration-like 值读日期单位字段(year/month/week/day);非对象或字段缺失视为 0。
|
||||
fn duration_like_date_fields<H: VmHost>(vm: &mut H, val: JsValue) -> (f64, f64, f64, f64) {
|
||||
if !val.is_object() {
|
||||
return (0.0, 0.0, 0.0, 0.0);
|
||||
}
|
||||
let obj = unsafe { &*val.as_js_object_ptr() };
|
||||
let y = read_prop_number(vm, obj, val, "years");
|
||||
let m = read_prop_number(vm, obj, val, "months");
|
||||
let w = read_prop_number(vm, obj, val, "weeks");
|
||||
let d = read_prop_number(vm, obj, val, "days");
|
||||
(
|
||||
if y.is_nan() { 0.0 } else { y.trunc() },
|
||||
if m.is_nan() { 0.0 } else { m.trunc() },
|
||||
if w.is_nan() { 0.0 } else { w.trunc() },
|
||||
if d.is_nan() { 0.0 } else { d.trunc() },
|
||||
)
|
||||
}
|
||||
|
||||
/// 按 duration-like 日期字段对日期做加减(Temporal 大单位运算,月份不足日时取月末)。
|
||||
fn date_apply_duration<H: VmHost>(
|
||||
vm: &mut H, args: &[u8], sign: i64,
|
||||
) -> NativeResult {
|
||||
let date = match plain_date_naive(vm, args) {
|
||||
Ok(d) => d,
|
||||
Err(e) => return NativeResult::Err(e),
|
||||
};
|
||||
let val = if args.len() > 1 { vm.reg(args[1]) } else { JsValue::undefined() };
|
||||
let (y, m, w, d) = duration_like_date_fields(vm, val);
|
||||
let mut result = date;
|
||||
let total_months = (y * 12.0 + m) * sign as f64;
|
||||
if total_months != 0.0 {
|
||||
if let Some(nd) = result.checked_add_months(Months::new(total_months as u32)) {
|
||||
result = nd;
|
||||
} else {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "date out of range"));
|
||||
}
|
||||
}
|
||||
let total_days = (w * 7.0 + d) * sign as f64;
|
||||
if total_days != 0.0 {
|
||||
if let Some(nd) = result.checked_add_days(Days::new(total_days as u64)) {
|
||||
result = nd;
|
||||
} else {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "date out of range"));
|
||||
}
|
||||
}
|
||||
make_plain_date(vm, result.year(), result.month(), result.day())
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.add(durationLike)`。
|
||||
pub fn plain_date_add<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
date_apply_duration(vm, args, 1)
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.subtract(durationLike)`。
|
||||
pub fn plain_date_subtract<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
date_apply_duration(vm, args, -1)
|
||||
}
|
||||
|
||||
@@ -236,7 +236,6 @@ fn is_blacklisted_harness(name: &str) -> bool {
|
||||
| "testAtomics.js"
|
||||
| "atomicsHelper.js"
|
||||
| "proxyTrapsHelper.js"
|
||||
| "temporalHelpers.js"
|
||||
| "tcoHelper.js"
|
||||
| "detachArrayBuffer.js"
|
||||
| "resizableArrayBufferUtils.js"
|
||||
@@ -402,7 +401,6 @@ fn is_skipped(meta: &TestMeta) -> Option<String> {
|
||||
"Proxy",
|
||||
"BigInt",
|
||||
"Intl",
|
||||
"Temporal",
|
||||
"module",
|
||||
"Atomics",
|
||||
"SharedArrayBuffer",
|
||||
|
||||
@@ -94,7 +94,10 @@ pub fn bind_temporal(core: &Arc<KernelCore>, session: &KernelSession, global: &m
|
||||
world,
|
||||
plain_date_ctor,
|
||||
core,
|
||||
&[("from", oxide_builtins::temporal::plain_date_from::<crate::vm::Vm> as *const (), 1)],
|
||||
&[
|
||||
("from", oxide_builtins::temporal::plain_date_from::<crate::vm::Vm> as *const (), 1),
|
||||
("compare", oxide_builtins::temporal::plain_date_compare::<crate::vm::Vm> as *const (), 2),
|
||||
],
|
||||
);
|
||||
let plain_date_proto_ptr = world.plain_date_proto.as_ptr() as *mut JsObject;
|
||||
let plain_date_proto = unsafe { &mut *plain_date_proto_ptr };
|
||||
@@ -119,11 +122,109 @@ pub fn bind_temporal(core: &Arc<KernelCore>, session: &KernelSession, global: &m
|
||||
"day",
|
||||
oxide_builtins::temporal::plain_date_day::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
bind_accessor_getter(
|
||||
core,
|
||||
session,
|
||||
plain_date_proto,
|
||||
"dayOfWeek",
|
||||
oxide_builtins::temporal::plain_date_day_of_week::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
bind_accessor_getter(
|
||||
core,
|
||||
session,
|
||||
plain_date_proto,
|
||||
"dayOfYear",
|
||||
oxide_builtins::temporal::plain_date_day_of_year::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
bind_accessor_getter(
|
||||
core,
|
||||
session,
|
||||
plain_date_proto,
|
||||
"daysInMonth",
|
||||
oxide_builtins::temporal::plain_date_days_in_month::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
bind_accessor_getter(
|
||||
core,
|
||||
session,
|
||||
plain_date_proto,
|
||||
"daysInWeek",
|
||||
oxide_builtins::temporal::plain_date_days_in_week::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
bind_accessor_getter(
|
||||
core,
|
||||
session,
|
||||
plain_date_proto,
|
||||
"daysInYear",
|
||||
oxide_builtins::temporal::plain_date_days_in_year::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
bind_accessor_getter(
|
||||
core,
|
||||
session,
|
||||
plain_date_proto,
|
||||
"monthsInYear",
|
||||
oxide_builtins::temporal::plain_date_months_in_year::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
bind_accessor_getter(
|
||||
core,
|
||||
session,
|
||||
plain_date_proto,
|
||||
"inLeapYear",
|
||||
oxide_builtins::temporal::plain_date_in_leap_year::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
bind_accessor_getter(
|
||||
core,
|
||||
session,
|
||||
plain_date_proto,
|
||||
"weekOfYear",
|
||||
oxide_builtins::temporal::plain_date_week_of_year::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
bind_accessor_getter(
|
||||
core,
|
||||
session,
|
||||
plain_date_proto,
|
||||
"yearOfWeek",
|
||||
oxide_builtins::temporal::plain_date_year_of_week::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
bind_accessor_getter(
|
||||
core,
|
||||
session,
|
||||
plain_date_proto,
|
||||
"monthCode",
|
||||
oxide_builtins::temporal::plain_date_month_code::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
bind_accessor_getter(
|
||||
core,
|
||||
session,
|
||||
plain_date_proto,
|
||||
"era",
|
||||
oxide_builtins::temporal::plain_date_era::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
bind_accessor_getter(
|
||||
core,
|
||||
session,
|
||||
plain_date_proto,
|
||||
"eraYear",
|
||||
oxide_builtins::temporal::plain_date_era_year::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
bind_accessor_getter(
|
||||
core,
|
||||
session,
|
||||
plain_date_proto,
|
||||
"calendarId",
|
||||
oxide_builtins::temporal::plain_date_calendar_id::<crate::vm::Vm> as *const (),
|
||||
);
|
||||
apply_binding_table(
|
||||
world,
|
||||
plain_date_proto,
|
||||
core,
|
||||
&[("toString", oxide_builtins::temporal::plain_date_to_string::<crate::vm::Vm> as *const (), 0)],
|
||||
&[
|
||||
("toString", oxide_builtins::temporal::plain_date_to_string::<crate::vm::Vm> as *const (), 0),
|
||||
("toJSON", oxide_builtins::temporal::plain_date_to_json::<crate::vm::Vm> as *const (), 0),
|
||||
("valueOf", oxide_builtins::temporal::plain_date_value_of::<crate::vm::Vm> as *const (), 0),
|
||||
("equals", oxide_builtins::temporal::plain_date_equals::<crate::vm::Vm> as *const (), 1),
|
||||
("add", oxide_builtins::temporal::plain_date_add::<crate::vm::Vm> as *const (), 1),
|
||||
("subtract", oxide_builtins::temporal::plain_date_subtract::<crate::vm::Vm> as *const (), 1),
|
||||
],
|
||||
);
|
||||
|
||||
// Temporal.PlainTime:构造器 + 6 个分量 getter 与 toString。
|
||||
|
||||
Reference in New Issue
Block a user