feat: implement Temporal PlainDateTime foundation
This commit is contained in:
@@ -6,9 +6,11 @@ use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
// Temporal 命名空间的最小实现子集:Temporal.Now / Temporal.Instant /
|
||||
// Temporal.PlainDate / Temporal.PlainTime / Temporal.ZonedDateTime。内部数据按对象类型存入 prop 槽:
|
||||
// Temporal.PlainDate / Temporal.PlainTime / Temporal.PlainDateTime / Temporal.ZonedDateTime。
|
||||
// 内部数据按对象类型存入 prop 槽:
|
||||
// Instant 存纪元纳秒(BigInt,prop 0)、PlainDate 存年/月/日(prop 0-2)、
|
||||
// PlainTime 存午夜后纳秒(f64,prop 0),ZonedDateTime 存纪元纳秒、时区 ID、日历 ID(prop 0-2)。
|
||||
// PlainTime 存午夜后纳秒(f64,prop 0)、PlainDateTime 存年/月/日与午夜后纳秒(prop 0-3),
|
||||
// ZonedDateTime 存纪元纳秒、时区 ID、日历 ID(prop 0-2)。
|
||||
|
||||
const MAX_INSTANT_NS: i128 = 8_640_000_000_000_000_000_000;
|
||||
|
||||
@@ -62,6 +64,13 @@ fn ensure_plain_time<H: VmHost>(vm: &mut H, obj: &JsObject) -> Result<(), JsValu
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_plain_date_time<H: VmHost>(vm: &mut H, obj: &JsObject) -> Result<(), JsValue> {
|
||||
if !obj.is_plain_date_time_obj() {
|
||||
return Err(crate::error::create_type_error(vm, "called on incompatible receiver"));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ensure_duration<H: VmHost>(vm: &mut H, obj: &JsObject) -> Result<(), JsValue> {
|
||||
if !obj.is_duration_obj() {
|
||||
return Err(crate::error::create_type_error(vm, "called on incompatible receiver"));
|
||||
@@ -1326,6 +1335,14 @@ fn make_plain_date<H: VmHost>(vm: &mut H, year: i32, month: u32, day: u32) -> Na
|
||||
NativeResult::Ok(JsValue::from_js_object(vm.alloc_object(obj)))
|
||||
}
|
||||
|
||||
fn make_plain_time<H: VmHost>(vm: &mut H, total_ns: f64) -> NativeResult {
|
||||
let proto = JsValue::from_js_object(vm.session().builtin_world().plain_time_proto.as_ptr() as *mut JsObject);
|
||||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto);
|
||||
obj.type_tag = JsObject::OBJ_TYPE_PLAIN_TIME;
|
||||
obj.set_prop_at(0, JsValue::float(total_ns));
|
||||
NativeResult::Ok(JsValue::from_js_object(vm.alloc_object(obj)))
|
||||
}
|
||||
|
||||
fn make_duration<H: VmHost>(vm: &mut H, values: [f64; 10]) -> NativeResult {
|
||||
let proto = JsValue::from_js_object(vm.session().builtin_world().duration_proto.as_ptr() as *mut JsObject);
|
||||
let mut obj = JsObject::new_empty(EMPTY_SHAPE_ID, proto);
|
||||
@@ -2072,6 +2089,257 @@ pub fn plain_time_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
|
||||
}
|
||||
}
|
||||
|
||||
// ───────────────────── PlainDateTime 基础方法 ─────────────────────
|
||||
|
||||
/// `Temporal.PlainDateTime` 构造器:保存 ISO 日期与午夜后纳秒。
|
||||
pub fn plain_date_time_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let ctor_proto = vm.session().builtin_world().plain_date_time_proto.as_ptr();
|
||||
if !is_ctor_call(vm, args, ctor_proto) {
|
||||
return NativeResult::Err(crate::error::create_type_error(
|
||||
vm,
|
||||
"Class constructor Temporal.PlainDateTime cannot be invoked without 'new'",
|
||||
));
|
||||
}
|
||||
|
||||
let mut component = |index: usize, default: f64| -> Result<f64, JsValue> {
|
||||
if args.len() > index {
|
||||
let raw = vm.reg(args[index]);
|
||||
temporal_option_number(vm, raw).map(f64::trunc)
|
||||
} else {
|
||||
Ok(default)
|
||||
}
|
||||
};
|
||||
let year_value = native_try!(component(1, f64::NAN));
|
||||
let month_value = native_try!(component(2, f64::NAN));
|
||||
let day_value = native_try!(component(3, f64::NAN));
|
||||
let hour_value = native_try!(component(4, 0.0));
|
||||
let minute_value = native_try!(component(5, 0.0));
|
||||
let second_value = native_try!(component(6, 0.0));
|
||||
let millisecond_value = native_try!(component(7, 0.0));
|
||||
let microsecond_value = native_try!(component(8, 0.0));
|
||||
let nanosecond_value = native_try!(component(9, 0.0));
|
||||
let components = [
|
||||
year_value,
|
||||
month_value,
|
||||
day_value,
|
||||
hour_value,
|
||||
minute_value,
|
||||
second_value,
|
||||
millisecond_value,
|
||||
microsecond_value,
|
||||
nanosecond_value,
|
||||
];
|
||||
if components.iter().any(|value| !value.is_finite()) {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid date-time component"));
|
||||
}
|
||||
|
||||
let year = year_value as i32;
|
||||
let month = month_value as u32;
|
||||
let day = day_value as u32;
|
||||
let hour = hour_value as u32;
|
||||
let minute = minute_value as u32;
|
||||
let second = second_value as u32;
|
||||
let millisecond = millisecond_value as u32;
|
||||
let microsecond = microsecond_value as u32;
|
||||
let nanosecond = nanosecond_value as u32;
|
||||
let non_negative = components[1..].iter().all(|value| *value >= 0.0);
|
||||
if !non_negative
|
||||
|| !valid_iso_date(year, month, day)
|
||||
|| !valid_plain_time(hour, minute, second, millisecond, microsecond, nanosecond)
|
||||
{
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid date-time component"));
|
||||
}
|
||||
|
||||
if args.len() > 10 && !vm.reg(args[10]).is_undefined() {
|
||||
let calendar = vm.reg(args[10]);
|
||||
let calendar = native_try!(temporal_option_string(vm, calendar));
|
||||
if !calendar.eq_ignore_ascii_case("iso8601") {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "unsupported calendar"));
|
||||
}
|
||||
}
|
||||
|
||||
let total_ns = hour as f64 * 3_600_000_000_000.0
|
||||
+ minute as f64 * 60_000_000_000.0
|
||||
+ second as f64 * 1_000_000_000.0
|
||||
+ millisecond as f64 * 1_000_000.0
|
||||
+ microsecond as f64 * 1_000.0
|
||||
+ nanosecond as f64;
|
||||
initialize_temporal_receiver(
|
||||
vm,
|
||||
args,
|
||||
JsObject::OBJ_TYPE_PLAIN_DATE_TIME,
|
||||
[
|
||||
JsValue::float(year as f64),
|
||||
JsValue::float(month as f64),
|
||||
JsValue::float(day as f64),
|
||||
JsValue::float(total_ns),
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
fn plain_date_time_parts<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<(i32, u32, u32, f64), JsValue> {
|
||||
let ptr = receiver_obj(vm, args)?;
|
||||
let obj = unsafe { &*ptr };
|
||||
ensure_plain_date_time(vm, obj)?;
|
||||
Ok((
|
||||
get_double_prop(obj, 0) as i32,
|
||||
get_double_prop(obj, 1) as u32,
|
||||
get_double_prop(obj, 2) as u32,
|
||||
get_double_prop(obj, 3),
|
||||
))
|
||||
}
|
||||
|
||||
macro_rules! plain_date_time_date_getter {
|
||||
($name:ident, $index:tt) => {
|
||||
pub fn $name<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let parts = native_try!(plain_date_time_parts(vm, args));
|
||||
NativeResult::Ok(JsValue::float(parts.$index as f64))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
plain_date_time_date_getter!(plain_date_time_year, 0);
|
||||
plain_date_time_date_getter!(plain_date_time_month, 1);
|
||||
plain_date_time_date_getter!(plain_date_time_day, 2);
|
||||
|
||||
fn plain_date_time_naive<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<NaiveDate, JsValue> {
|
||||
let (year, month, day, _) = plain_date_time_parts(vm, args)?;
|
||||
NaiveDate::from_ymd_opt(year, month, day).ok_or_else(|| crate::error::create_range_error(vm, "invalid date"))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDateTime.prototype.dayOfWeek` getter。
|
||||
pub fn plain_date_time_day_of_week<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let date = native_try!(plain_date_time_naive(vm, args));
|
||||
NativeResult::Ok(JsValue::float((date.weekday().num_days_from_monday() + 1) as f64))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDateTime.prototype.dayOfYear` getter。
|
||||
pub fn plain_date_time_day_of_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let date = native_try!(plain_date_time_naive(vm, args));
|
||||
NativeResult::Ok(JsValue::float(date.ordinal() as f64))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDateTime.prototype.daysInMonth` getter。
|
||||
pub fn plain_date_time_days_in_month<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let date = native_try!(plain_date_time_naive(vm, args));
|
||||
NativeResult::Ok(JsValue::float(days_in_month_iso(date.year(), date.month()) as f64))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDateTime.prototype.daysInWeek` getter。
|
||||
pub fn plain_date_time_days_in_week<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let _ = native_try!(plain_date_time_parts(vm, args));
|
||||
NativeResult::Ok(JsValue::float(7.0))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDateTime.prototype.daysInYear` getter。
|
||||
pub fn plain_date_time_days_in_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let date = native_try!(plain_date_time_naive(vm, args));
|
||||
NativeResult::Ok(JsValue::float(if is_leap_year_iso(date.year()) { 366.0 } else { 365.0 }))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDateTime.prototype.monthsInYear` getter。
|
||||
pub fn plain_date_time_months_in_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let _ = native_try!(plain_date_time_parts(vm, args));
|
||||
NativeResult::Ok(JsValue::float(12.0))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDateTime.prototype.inLeapYear` getter。
|
||||
pub fn plain_date_time_in_leap_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let date = native_try!(plain_date_time_naive(vm, args));
|
||||
NativeResult::Ok(JsValue::bool(is_leap_year_iso(date.year())))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDateTime.prototype.weekOfYear` getter。
|
||||
pub fn plain_date_time_week_of_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let date = native_try!(plain_date_time_naive(vm, args));
|
||||
NativeResult::Ok(JsValue::float(date.iso_week().week() as f64))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDateTime.prototype.yearOfWeek` getter。
|
||||
pub fn plain_date_time_year_of_week<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let date = native_try!(plain_date_time_naive(vm, args));
|
||||
NativeResult::Ok(JsValue::float(date.iso_week().year() as f64))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDateTime.prototype.monthCode` getter。
|
||||
pub fn plain_date_time_month_code<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let (_, month, _, _) = native_try!(plain_date_time_parts(vm, args));
|
||||
NativeResult::Ok(vm.new_string(&format!("M{month:02}")))
|
||||
}
|
||||
|
||||
/// ISO 日历没有可观察的 era 字段。
|
||||
pub fn plain_date_time_era<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let _ = native_try!(plain_date_time_parts(vm, args));
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
/// ISO 日历没有可观察的 eraYear 字段。
|
||||
pub fn plain_date_time_era_year<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let _ = native_try!(plain_date_time_parts(vm, args));
|
||||
NativeResult::Ok(JsValue::undefined())
|
||||
}
|
||||
|
||||
fn plain_date_time_time_get<H: VmHost>(
|
||||
vm: &mut H, args: &[u8], select: fn(u32, u32, u32, u32, u32, u32) -> u32,
|
||||
) -> NativeResult {
|
||||
let (_, _, _, total_ns) = native_try!(plain_date_time_parts(vm, args));
|
||||
let (hour, minute, second, millisecond, microsecond, nanosecond) = plain_time_components(total_ns);
|
||||
NativeResult::Ok(JsValue::float(select(hour, minute, second, millisecond, microsecond, nanosecond) as f64))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDateTime.prototype.hour` getter。
|
||||
pub fn plain_date_time_hour<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
plain_date_time_time_get(vm, args, |hour, _, _, _, _, _| hour)
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDateTime.prototype.minute` getter。
|
||||
pub fn plain_date_time_minute<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
plain_date_time_time_get(vm, args, |_, minute, _, _, _, _| minute)
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDateTime.prototype.second` getter。
|
||||
pub fn plain_date_time_second<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
plain_date_time_time_get(vm, args, |_, _, second, _, _, _| second)
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDateTime.prototype.millisecond` getter。
|
||||
pub fn plain_date_time_millisecond<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
plain_date_time_time_get(vm, args, |_, _, _, millisecond, _, _| millisecond)
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDateTime.prototype.microsecond` getter。
|
||||
pub fn plain_date_time_microsecond<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
plain_date_time_time_get(vm, args, |_, _, _, _, microsecond, _| microsecond)
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDateTime.prototype.nanosecond` getter。
|
||||
pub fn plain_date_time_nanosecond<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
plain_date_time_time_get(vm, args, |_, _, _, _, _, nanosecond| nanosecond)
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDateTime.prototype.calendarId` getter。
|
||||
pub fn plain_date_time_calendar_id<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let _ = native_try!(plain_date_time_parts(vm, args));
|
||||
NativeResult::Ok(vm.new_string("iso8601"))
|
||||
}
|
||||
|
||||
/// 返回仅保留日期分量的新 `Temporal.PlainDate`。
|
||||
pub fn plain_date_time_to_plain_date<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let (year, month, day, _) = native_try!(plain_date_time_parts(vm, args));
|
||||
make_plain_date(vm, year, month, day)
|
||||
}
|
||||
|
||||
/// 返回仅保留时间分量的新 `Temporal.PlainTime`。
|
||||
pub fn plain_date_time_to_plain_time<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let (_, _, _, total_ns) = native_try!(plain_date_time_parts(vm, args));
|
||||
make_plain_time(vm, total_ns)
|
||||
}
|
||||
|
||||
/// Temporal 对象禁止隐式转换为原始值。
|
||||
pub fn plain_date_time_value_of<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
|
||||
NativeResult::Err(crate::error::create_type_error(vm, "Temporal.PlainDateTime has no valueOf"))
|
||||
}
|
||||
|
||||
// ───────────────────── PlainDate 扩展方法 ─────────────────────
|
||||
|
||||
/// 从 PlainDate receiver 读 year/month/day 并构造 chrono NaiveDate(供日期计算)。
|
||||
|
||||
@@ -256,6 +256,8 @@ pub struct BuiltinWorld {
|
||||
pub duration_proto: P<JsObject>,
|
||||
pub zoned_date_time_constructor: P<JsObject>,
|
||||
pub zoned_date_time_proto: P<JsObject>,
|
||||
pub plain_date_time_constructor: P<JsObject>,
|
||||
pub plain_date_time_proto: P<JsObject>,
|
||||
pub bigint_constructor: P<JsObject>,
|
||||
pub bigint_proto: P<JsObject>,
|
||||
pub stub_objects: Vec<P<JsObject>>,
|
||||
@@ -511,10 +513,11 @@ fn wire_builtin_world_links(world: &BuiltinWorld) {
|
||||
wire_ctor_proto(&world.plain_time_constructor, &world.plain_time_proto);
|
||||
wire_ctor_proto(&world.duration_constructor, &world.duration_proto);
|
||||
wire_ctor_proto(&world.zoned_date_time_constructor, &world.zoned_date_time_proto);
|
||||
wire_ctor_proto(&world.plain_date_time_constructor, &world.plain_date_time_proto);
|
||||
wire_ctor_proto(&world.bigint_constructor, &world.bigint_proto);
|
||||
|
||||
let obj_proto_val = JsValue::from_js_object(world.object_proto.as_ptr() as *mut JsObject);
|
||||
let non_object_protos: [&P<JsObject>; 20] = [
|
||||
let non_object_protos: [&P<JsObject>; 21] = [
|
||||
&world.array_proto,
|
||||
&world.function_proto,
|
||||
&world.string_proto,
|
||||
@@ -534,6 +537,7 @@ fn wire_builtin_world_links(world: &BuiltinWorld) {
|
||||
&world.plain_time_proto,
|
||||
&world.duration_proto,
|
||||
&world.zoned_date_time_proto,
|
||||
&world.plain_date_time_proto,
|
||||
&world.bigint_proto,
|
||||
];
|
||||
for proto in &non_object_protos {
|
||||
@@ -674,6 +678,8 @@ impl BuiltinWorld {
|
||||
BuiltinId::DurationProto => &self.duration_proto,
|
||||
BuiltinId::ZonedDateTimeConstructor => &self.zoned_date_time_constructor,
|
||||
BuiltinId::ZonedDateTimeProto => &self.zoned_date_time_proto,
|
||||
BuiltinId::PlainDateTimeConstructor => &self.plain_date_time_constructor,
|
||||
BuiltinId::PlainDateTimeProto => &self.plain_date_time_proto,
|
||||
BuiltinId::BigIntConstructor => &self.bigint_constructor,
|
||||
BuiltinId::BigIntProto => &self.bigint_proto,
|
||||
}
|
||||
@@ -728,6 +734,8 @@ impl BuiltinWorld {
|
||||
let (duration_proto, duration_constructor) = make_named_pair(string_forge, shape_forge, labels, "Duration");
|
||||
let (zoned_date_time_proto, zoned_date_time_constructor) =
|
||||
make_named_pair(string_forge, shape_forge, labels, "ZonedDateTime");
|
||||
let (plain_date_time_proto, plain_date_time_constructor) =
|
||||
make_named_pair(string_forge, shape_forge, labels, "PlainDateTime");
|
||||
let (bigint_proto, bigint_constructor) = make_named_pair(string_forge, shape_forge, labels, "BigInt");
|
||||
let stub_objects = Vec::new();
|
||||
|
||||
@@ -814,6 +822,8 @@ impl BuiltinWorld {
|
||||
duration_proto,
|
||||
zoned_date_time_constructor,
|
||||
zoned_date_time_proto,
|
||||
plain_date_time_constructor,
|
||||
plain_date_time_proto,
|
||||
bigint_constructor,
|
||||
bigint_proto,
|
||||
stub_objects,
|
||||
@@ -1006,6 +1016,8 @@ impl BuiltinWorld {
|
||||
duration_constructor,
|
||||
zoned_date_time_proto,
|
||||
zoned_date_time_constructor,
|
||||
plain_date_time_proto,
|
||||
plain_date_time_constructor,
|
||||
) = if dirty.temporal {
|
||||
let (instant_proto, instant_constructor) = make_named_pair(string_forge, shape_forge, labels, "Instant");
|
||||
let (plain_date_proto, plain_date_constructor) =
|
||||
@@ -1015,6 +1027,8 @@ impl BuiltinWorld {
|
||||
let (duration_proto, duration_constructor) = make_named_pair(string_forge, shape_forge, labels, "Duration");
|
||||
let (zoned_date_time_proto, zoned_date_time_constructor) =
|
||||
make_named_pair(string_forge, shape_forge, labels, "ZonedDateTime");
|
||||
let (plain_date_time_proto, plain_date_time_constructor) =
|
||||
make_named_pair(string_forge, shape_forge, labels, "PlainDateTime");
|
||||
(
|
||||
P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
|
||||
P::new(JsObject::new_empty(EMPTY_SHAPE_ID, JsValue::null())),
|
||||
@@ -1028,6 +1042,8 @@ impl BuiltinWorld {
|
||||
duration_constructor,
|
||||
zoned_date_time_proto,
|
||||
zoned_date_time_constructor,
|
||||
plain_date_time_proto,
|
||||
plain_date_time_constructor,
|
||||
)
|
||||
} else {
|
||||
(
|
||||
@@ -1043,6 +1059,8 @@ impl BuiltinWorld {
|
||||
current.duration_constructor.clone(),
|
||||
current.zoned_date_time_proto.clone(),
|
||||
current.zoned_date_time_constructor.clone(),
|
||||
current.plain_date_time_proto.clone(),
|
||||
current.plain_date_time_constructor.clone(),
|
||||
)
|
||||
};
|
||||
let (bigint_proto, bigint_constructor) = if dirty.stubs {
|
||||
@@ -1135,6 +1153,8 @@ impl BuiltinWorld {
|
||||
duration_proto,
|
||||
zoned_date_time_constructor,
|
||||
zoned_date_time_proto,
|
||||
plain_date_time_constructor,
|
||||
plain_date_time_proto,
|
||||
bigint_constructor,
|
||||
bigint_proto,
|
||||
stub_objects,
|
||||
|
||||
@@ -225,7 +225,7 @@ pub struct KernelSession {
|
||||
/// 维护注意:每个新增的 `BuiltinWorld` 对象字段都必须加到这里以及
|
||||
/// `KernelSession::dirty_since_snapshot()`,以便选择性重置重建正确的
|
||||
/// builtin 家族。
|
||||
pub const NUM_BUILTINS: usize = 83;
|
||||
pub const NUM_BUILTINS: usize = 85;
|
||||
|
||||
/// 内置对象枚举 id,与 `BuiltinWorld` 中的存储槽一一对应。
|
||||
///
|
||||
@@ -317,6 +317,8 @@ pub enum BuiltinId {
|
||||
SymToStringTag = 80,
|
||||
ZonedDateTimeConstructor = 81,
|
||||
ZonedDateTimeProto = 82,
|
||||
PlainDateTimeConstructor = 83,
|
||||
PlainDateTimeProto = 84,
|
||||
}
|
||||
|
||||
impl BuiltinId {
|
||||
@@ -405,6 +407,8 @@ impl BuiltinId {
|
||||
BuiltinId::SymToStringTag,
|
||||
BuiltinId::ZonedDateTimeConstructor,
|
||||
BuiltinId::ZonedDateTimeProto,
|
||||
BuiltinId::PlainDateTimeConstructor,
|
||||
BuiltinId::PlainDateTimeProto,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -642,7 +646,9 @@ impl KernelSession {
|
||||
|| gen(BuiltinId::DurationConstructor) != snap(BuiltinId::DurationConstructor)
|
||||
|| gen(BuiltinId::DurationProto) != snap(BuiltinId::DurationProto)
|
||||
|| gen(BuiltinId::ZonedDateTimeConstructor) != snap(BuiltinId::ZonedDateTimeConstructor)
|
||||
|| gen(BuiltinId::ZonedDateTimeProto) != snap(BuiltinId::ZonedDateTimeProto),
|
||||
|| gen(BuiltinId::ZonedDateTimeProto) != snap(BuiltinId::ZonedDateTimeProto)
|
||||
|| gen(BuiltinId::PlainDateTimeConstructor) != snap(BuiltinId::PlainDateTimeConstructor)
|
||||
|| gen(BuiltinId::PlainDateTimeProto) != snap(BuiltinId::PlainDateTimeProto),
|
||||
stubs: world.stub_objects.len() != snapshot.stub_objects_len
|
||||
|| stub_generations_dirty
|
||||
|| gen(BuiltinId::BigIntConstructor) != snap(BuiltinId::BigIntConstructor)
|
||||
|
||||
@@ -447,6 +447,8 @@ impl JsObject {
|
||||
pub const OBJ_TYPE_DURATION: u8 = 17;
|
||||
/// Temporal.ZonedDateTime 对象:纪元纳秒、时区 ID、日历 ID 存于 prop 0-2。
|
||||
pub const OBJ_TYPE_ZONED_DATE_TIME: u8 = 18;
|
||||
/// Temporal.PlainDateTime 对象:ISO 年/月/日与午夜后纳秒存于 prop 0-3。
|
||||
pub const OBJ_TYPE_PLAIN_DATE_TIME: u8 = 19;
|
||||
/// `is_session_epoch` 字段中的 session 标记位。
|
||||
pub const SESSION_EPOCH_BIT: u8 = 0x01;
|
||||
/// `is_session_epoch` 字段中的 GC 标记位。
|
||||
@@ -537,6 +539,11 @@ impl JsObject {
|
||||
pub fn is_zoned_date_time_obj(&self) -> bool {
|
||||
self.type_tag == Self::OBJ_TYPE_ZONED_DATE_TIME
|
||||
}
|
||||
/// 是否 Temporal.PlainDateTime 对象。
|
||||
#[inline]
|
||||
pub fn is_plain_date_time_obj(&self) -> bool {
|
||||
self.type_tag == Self::OBJ_TYPE_PLAIN_DATE_TIME
|
||||
}
|
||||
|
||||
/// 构造无属性、可扩展的空对象(`new Object()` 的基础对象)。
|
||||
pub fn new_empty(shape_id: ShapeId, proto: JsValue) -> Self {
|
||||
|
||||
@@ -377,6 +377,129 @@ pub fn bind_temporal(core: &Arc<KernelCore>, session: &KernelSession, global: &m
|
||||
)],
|
||||
);
|
||||
|
||||
// Temporal.PlainDateTime:ISO 日期时间分量、拆分转换与禁止隐式原始值转换。
|
||||
let plain_date_time_ctor_ptr = world.plain_date_time_constructor.as_ptr() as *mut JsObject;
|
||||
let plain_date_time_ctor = unsafe { &mut *plain_date_time_ctor_ptr };
|
||||
configure_native_constructor(
|
||||
plain_date_time_ctor,
|
||||
oxide_builtins::temporal::plain_date_time_constructor::<crate::vm::Vm> as *const (),
|
||||
3,
|
||||
);
|
||||
let length_si = core.perm_interner().intern("length").0;
|
||||
let length_shape = core.shape_forge().make_shape(plain_date_time_ctor.shape_id(), length_si);
|
||||
plain_date_time_ctor.set_shape_id(length_shape);
|
||||
plain_date_time_ctor.ensure_hash_props().push(JsValue::int(3));
|
||||
let length_pos = plain_date_time_ctor
|
||||
.hash_props_vec()
|
||||
.map_or(0, |props| props.len() as u32)
|
||||
.saturating_sub(1);
|
||||
plain_date_time_ctor.set_data_meta(length_pos, PropAttributes::new(false, false, true));
|
||||
let plain_date_time_proto_ptr = world.plain_date_time_proto.as_ptr() as *mut JsObject;
|
||||
let plain_date_time_proto = unsafe { &mut *plain_date_time_proto_ptr };
|
||||
for (name, getter) in [
|
||||
("year", oxide_builtins::temporal::plain_date_time_year::<crate::vm::Vm> as *const ()),
|
||||
("month", oxide_builtins::temporal::plain_date_time_month::<crate::vm::Vm> as *const ()),
|
||||
("day", oxide_builtins::temporal::plain_date_time_day::<crate::vm::Vm> as *const ()),
|
||||
(
|
||||
"dayOfWeek",
|
||||
oxide_builtins::temporal::plain_date_time_day_of_week::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"dayOfYear",
|
||||
oxide_builtins::temporal::plain_date_time_day_of_year::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"daysInMonth",
|
||||
oxide_builtins::temporal::plain_date_time_days_in_month::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"daysInWeek",
|
||||
oxide_builtins::temporal::plain_date_time_days_in_week::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"daysInYear",
|
||||
oxide_builtins::temporal::plain_date_time_days_in_year::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"monthsInYear",
|
||||
oxide_builtins::temporal::plain_date_time_months_in_year::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"inLeapYear",
|
||||
oxide_builtins::temporal::plain_date_time_in_leap_year::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"weekOfYear",
|
||||
oxide_builtins::temporal::plain_date_time_week_of_year::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"yearOfWeek",
|
||||
oxide_builtins::temporal::plain_date_time_year_of_week::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"monthCode",
|
||||
oxide_builtins::temporal::plain_date_time_month_code::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
("era", oxide_builtins::temporal::plain_date_time_era::<crate::vm::Vm> as *const ()),
|
||||
(
|
||||
"eraYear",
|
||||
oxide_builtins::temporal::plain_date_time_era_year::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
("hour", oxide_builtins::temporal::plain_date_time_hour::<crate::vm::Vm> as *const ()),
|
||||
("minute", oxide_builtins::temporal::plain_date_time_minute::<crate::vm::Vm> as *const ()),
|
||||
("second", oxide_builtins::temporal::plain_date_time_second::<crate::vm::Vm> as *const ()),
|
||||
(
|
||||
"millisecond",
|
||||
oxide_builtins::temporal::plain_date_time_millisecond::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"microsecond",
|
||||
oxide_builtins::temporal::plain_date_time_microsecond::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"nanosecond",
|
||||
oxide_builtins::temporal::plain_date_time_nanosecond::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
(
|
||||
"calendarId",
|
||||
oxide_builtins::temporal::plain_date_time_calendar_id::<crate::vm::Vm> as *const (),
|
||||
),
|
||||
] {
|
||||
bind_accessor_getter(core, session, plain_date_time_proto, name, getter);
|
||||
}
|
||||
apply_binding_table(
|
||||
world,
|
||||
plain_date_time_proto,
|
||||
core,
|
||||
&[
|
||||
(
|
||||
"toPlainDate",
|
||||
oxide_builtins::temporal::plain_date_time_to_plain_date::<crate::vm::Vm> as *const (),
|
||||
0,
|
||||
),
|
||||
(
|
||||
"toPlainTime",
|
||||
oxide_builtins::temporal::plain_date_time_to_plain_time::<crate::vm::Vm> as *const (),
|
||||
0,
|
||||
),
|
||||
(
|
||||
"valueOf",
|
||||
oxide_builtins::temporal::plain_date_time_value_of::<crate::vm::Vm> as *const (),
|
||||
0,
|
||||
),
|
||||
],
|
||||
);
|
||||
bind_well_known_data_property(
|
||||
core,
|
||||
plain_date_time_proto,
|
||||
9,
|
||||
JsValue::perm_string(
|
||||
core.perm_interner()
|
||||
.string_ptr(core.perm_interner().intern("Temporal.PlainDateTime").0),
|
||||
),
|
||||
PropAttributes::new(false, false, true),
|
||||
);
|
||||
|
||||
// Temporal.Duration:构造器、from、分量 getter 与 ISO 字符串转换。
|
||||
let duration_ctor_ptr = world.duration_constructor.as_ptr() as *mut JsObject;
|
||||
let duration_ctor = unsafe { &mut *duration_ctor_ptr };
|
||||
@@ -435,6 +558,7 @@ pub fn bind_temporal(core: &Arc<KernelCore>, session: &KernelSession, global: &m
|
||||
bind_global_value(core, temporal, "Instant", JsValue::from_js_object(instant_ctor_ptr));
|
||||
bind_global_value(core, temporal, "PlainDate", JsValue::from_js_object(plain_date_ctor_ptr));
|
||||
bind_global_value(core, temporal, "PlainTime", JsValue::from_js_object(plain_time_ctor_ptr));
|
||||
bind_global_value(core, temporal, "PlainDateTime", JsValue::from_js_object(plain_date_time_ctor_ptr));
|
||||
bind_global_value(core, temporal, "Duration", JsValue::from_js_object(duration_ctor_ptr));
|
||||
bind_global_value(core, temporal, "ZonedDateTime", JsValue::from_js_object(zoned_date_time_ctor_ptr));
|
||||
bind_global_value(core, global, "Temporal", JsValue::from_js_object(temporal_ptr));
|
||||
|
||||
@@ -616,3 +616,73 @@ fn plain_time_range_validation() {
|
||||
let r = eval(&mut vm, "try { new Temporal.PlainTime(0, 60, 0) } catch (e) { e.constructor.name }").unwrap();
|
||||
assert_eq!(str_val(&vm, r), "RangeError");
|
||||
}
|
||||
|
||||
// -- Temporal.PlainDateTime --
|
||||
|
||||
#[test]
|
||||
fn plain_date_time_constructor_and_components() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"let dt = new Temporal.PlainDateTime(2024, 2, 29, 23, 58, 57, 987, 654, 321);
|
||||
dt.year + ',' + dt.month + ',' + dt.day + ',' + dt.hour + ',' + dt.minute + ',' +
|
||||
dt.second + ',' + dt.millisecond + ',' + dt.microsecond + ',' + dt.nanosecond + ',' + dt.calendarId",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "2024,2,29,23,58,57,987,654,321,iso8601");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_date_time_defaults_time_to_midnight() {
|
||||
let mut vm = Vm::new();
|
||||
assert_eq!(
|
||||
num(
|
||||
&mut vm,
|
||||
"let dt = new Temporal.PlainDateTime(2024, 7, 6); dt.hour + dt.minute + dt.second + dt.nanosecond",
|
||||
),
|
||||
0.0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_date_time_splits_into_plain_date_and_time() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"let dt = new Temporal.PlainDateTime(2000, 5, 2, 12, 34, 56, 789, 123, 456);
|
||||
dt.toPlainDate().toString() + 'T' + dt.toPlainTime().toString()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "2000-05-02T12:34:56.789123456");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_date_time_validates_brand_and_ranges() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"let getter = Object.getOwnPropertyDescriptor(Temporal.PlainDateTime.prototype, 'year').get;
|
||||
let a; let b; let c;
|
||||
try { getter.call({}) } catch (e) { a = e.constructor.name }
|
||||
try { new Temporal.PlainDateTime(2023, 2, 29) } catch (e) { b = e.constructor.name }
|
||||
try { new Temporal.PlainDateTime(2024, 1, 1, 24) } catch (e) { c = e.constructor.name }
|
||||
a + ',' + b + ',' + c",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "TypeError,RangeError,RangeError");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_date_time_metadata_and_value_of() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"let dt = new Temporal.PlainDateTime(2024, 1, 2);
|
||||
let error;
|
||||
try { dt.valueOf() } catch (e) { error = e.constructor.name }
|
||||
Temporal.PlainDateTime.length + ',' + (dt instanceof Temporal.PlainDateTime) + ',' +
|
||||
Object.prototype.toString.call(dt) + ',' + error",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "3,true,[object Temporal.PlainDateTime],TypeError");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user