mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: PlainDate toString 支持日历选项与六位年号
This commit is contained in:
Generated
+21
-21
@@ -1160,7 +1160,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_builtins"
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
dependencies = [
|
||||
"chrono",
|
||||
"fast-float",
|
||||
@@ -1185,7 +1185,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_bytecode"
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxide_log",
|
||||
@@ -1193,7 +1193,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_cfg"
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_emit",
|
||||
@@ -1204,7 +1204,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_cli"
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
dependencies = [
|
||||
"ansi_term",
|
||||
"bumpalo",
|
||||
@@ -1228,7 +1228,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_code_cache"
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
dependencies = [
|
||||
"lru",
|
||||
"oxide_bytecode",
|
||||
@@ -1237,7 +1237,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_compiler"
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1258,7 +1258,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_dce"
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1272,7 +1272,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_emit"
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxc_allocator",
|
||||
@@ -1288,7 +1288,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_engine"
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_compiler",
|
||||
@@ -1300,7 +1300,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_fold"
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"oxide_bytecode",
|
||||
@@ -1312,7 +1312,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_ir"
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_log",
|
||||
@@ -1321,7 +1321,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_kernel"
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"dashmap",
|
||||
@@ -1335,7 +1335,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_liveness"
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1347,7 +1347,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_log"
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
dependencies = [
|
||||
"tracing",
|
||||
"tracing-appender",
|
||||
@@ -1356,11 +1356,11 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_net"
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
|
||||
[[package]]
|
||||
name = "oxide_parser"
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
dependencies = [
|
||||
"oxc_allocator",
|
||||
"oxc_ast",
|
||||
@@ -1375,7 +1375,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_regalloc"
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_cfg",
|
||||
@@ -1391,7 +1391,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_runtime_api"
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
dependencies = [
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
@@ -1404,7 +1404,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_test262"
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
dependencies = [
|
||||
"oxide_bytecode",
|
||||
"oxide_compiler",
|
||||
@@ -1424,7 +1424,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_types"
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"dashmap",
|
||||
@@ -1437,7 +1437,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "oxide_vm"
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
dependencies = [
|
||||
"bumpalo",
|
||||
"chrono",
|
||||
|
||||
+1
-1
@@ -25,7 +25,7 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.19.20"
|
||||
version = "0.19.21"
|
||||
edition = "2021"
|
||||
license = "MIT"
|
||||
rust-version = "1.80"
|
||||
|
||||
@@ -1247,12 +1247,26 @@ pub(crate) const CALENDAR_ID_WHITELIST: [&str; 18] = [
|
||||
"roc",
|
||||
];
|
||||
|
||||
/// 严格白名单匹配:命中返回规范小写形式,未命中返回 None。
|
||||
/// CLDR 日历别名(ParseTemporalCalendarString 的别名表):命中后归一到规范小写 ID。
|
||||
const CALENDAR_ID_ALIASES: [(&str, &str); 3] = [
|
||||
("ethiopic-amete-alem", "ethioaa"),
|
||||
("ethiopic-tana-alem", "ethiopic"),
|
||||
("islamicc", "islamic-civil"),
|
||||
];
|
||||
|
||||
/// 严格白名单匹配:先查 18 项白名单,未命中再查 CLDR 别名表;
|
||||
/// 命中返回规范小写形式,未命中返回 None。
|
||||
pub(crate) fn is_builtin_calendar_id(value: &str) -> Option<&'static str> {
|
||||
CALENDAR_ID_WHITELIST.iter().find(|id| value.eq_ignore_ascii_case(id)).copied()
|
||||
if let Some(id) = CALENDAR_ID_WHITELIST.iter().find(|id| value.eq_ignore_ascii_case(id)).copied() {
|
||||
return Some(id);
|
||||
}
|
||||
CALENDAR_ID_ALIASES
|
||||
.iter()
|
||||
.find(|(alias, _)| value.eq_ignore_ascii_case(alias))
|
||||
.map(|(_, id)| *id)
|
||||
}
|
||||
|
||||
/// 严格日历 ID 解析:只走 18 项白名单,拒绝 ISO 串(含注解/compact/extended/空串)。
|
||||
/// 严格日历 ID 解析:只走 18 项白名单与 CLDR 别名表,拒绝 ISO 串(含注解/compact/extended/空串)。
|
||||
/// 供构造器日历参数与注解值判定使用。
|
||||
pub(crate) fn parse_temporal_calendar_id_strict(input: &str) -> Result<String, String> {
|
||||
let trimmed = input.trim();
|
||||
|
||||
@@ -7,11 +7,12 @@ use oxide_types::value::JsValue;
|
||||
|
||||
use super::common::native_try;
|
||||
use super::common::{
|
||||
civil_from_days, days_from_civil, days_in_month, ensure_plain_date, get_calendar_id, get_double_prop,
|
||||
initialize_temporal_receiver, is_ctor_call, parse_plain_date_string, parse_round_options,
|
||||
plain_date_time_object_parts, receiver_obj, reject_partial_object_with_calendar_or_time_zone,
|
||||
resolve_date_time_parts, temporal_calendar_id, temporal_calendar_id_strict, temporal_number_component,
|
||||
temporal_option_string, temporal_option_value, temporal_overflow, zoned_date_time_plain_parts, DateTimeParts,
|
||||
calendar_annotation, civil_from_days, days_from_civil, days_in_month, ensure_plain_date, format_iso_year,
|
||||
get_calendar_id, get_double_prop, initialize_temporal_receiver, is_ctor_call, parse_plain_date_string,
|
||||
parse_round_options, plain_date_time_object_parts, receiver_obj,
|
||||
reject_partial_object_with_calendar_or_time_zone, resolve_date_time_parts, temporal_calendar_id,
|
||||
temporal_calendar_id_strict, temporal_number_component, temporal_option_string, temporal_option_value,
|
||||
temporal_overflow, temporal_to_show_calendar, zoned_date_time_plain_parts, DateTimeParts, ShowCalendar,
|
||||
};
|
||||
use super::difference::{difference_core, parse_difference_settings, IsoDateTime};
|
||||
use super::duration::duration_like_values;
|
||||
@@ -262,15 +263,26 @@ pub fn plain_date_day<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
NativeResult::Ok(JsValue::float(d))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.toString()`:输出 `YYYY-MM-DD`。
|
||||
/// PlainDate ISO 串:auto/never 加 iso8601 日历保持裸 `±YYYY-MM-DD`;
|
||||
/// 非 iso8601 日历与 always/critical 按注解表补 `[u-ca=…]`/`[!u-ca=…]`。
|
||||
fn plain_date_iso_string(obj: &JsObject, show: ShowCalendar) -> String {
|
||||
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;
|
||||
let calendar = get_calendar_id(obj, 3);
|
||||
let annotation = calendar_annotation(&calendar, show);
|
||||
format!("{}-{month:02}-{day:02}{annotation}", format_iso_year(year as i128))
|
||||
}
|
||||
|
||||
/// `Temporal.PlainDate.prototype.toString([options])`:默认 `±YYYY-MM-DD`;
|
||||
/// options 非对象抛 TypeError,calendarName 取值 auto/always/never/critical
|
||||
/// (其余 RangeError);非 iso8601 日历与 always/critical 补日历注解。
|
||||
pub fn plain_date_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let ptr = native_try!(receiver_obj(vm, args));
|
||||
let obj = unsafe { &*ptr };
|
||||
native_try!(ensure_plain_date(vm, obj));
|
||||
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}")))
|
||||
let show = native_try!(temporal_to_show_calendar(vm, args));
|
||||
NativeResult::Ok(vm.new_string_owned(plain_date_iso_string(obj, show)))
|
||||
}
|
||||
|
||||
// ───────────────────── PlainDate 扩展方法 ─────────────────────
|
||||
@@ -710,7 +722,7 @@ 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)。
|
||||
/// `Temporal.PlainDate.prototype.toJSON()`:输出默认形 ISO 日期串(同 toString 的 auto 形),忽略参数。
|
||||
pub fn plain_date_to_json<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let ptr = match receiver_obj(vm, args) {
|
||||
Ok(p) => p,
|
||||
@@ -720,10 +732,7 @@ pub fn plain_date_to_json<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
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}")))
|
||||
NativeResult::Ok(vm.new_string_owned(plain_date_iso_string(obj, ShowCalendar::Auto)))
|
||||
}
|
||||
|
||||
/// 按 duration-like 日期字段对日期做加减(Temporal 大单位运算,月份不足日时取月末)。
|
||||
|
||||
@@ -513,6 +513,64 @@ fn plain_date_to_string() {
|
||||
assert_eq!(str_val(&vm, r), "2024-01-05");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_date_to_string_honors_calendar_name() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"(() => {
|
||||
const d = new Temporal.PlainDate(2000, 5, 2);
|
||||
const auto = d.toString({ calendarName: 'auto' });
|
||||
const always = d.toString({ calendarName: 'always' });
|
||||
const critical = d.toString({ calendarName: 'critical' });
|
||||
const never = d.toString({ calendarName: 'never' });
|
||||
const badValue = (() => { try { d.toString({ calendarName: 'ALWAYS' }); return false; }
|
||||
catch (e) { return e instanceof RangeError; } })();
|
||||
const badOptions = (() => { try { d.toString(null); return false; }
|
||||
catch (e) { return e instanceof TypeError; } })();
|
||||
return [auto, always, critical, never, badValue, badOptions].join('|');
|
||||
})()",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
str_val(&vm, r),
|
||||
"2000-05-02|2000-05-02[u-ca=iso8601]|2000-05-02[!u-ca=iso8601]|2000-05-02|true|true"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_date_serializes_extended_years() {
|
||||
let mut vm = Vm::new();
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"[new Temporal.PlainDate(-100000, 12, 3).toString(),
|
||||
new Temporal.PlainDate(-10000, 4, 5).toString(),
|
||||
new Temporal.PlainDate(-1, 8, 7).toString(),
|
||||
new Temporal.PlainDate(0, 6, 5).toString(),
|
||||
new Temporal.PlainDate(10000, 6, 7).toString(),
|
||||
new Temporal.PlainDate(100000, 8, 9).toJSON()].join('|')",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
str_val(&vm, r),
|
||||
"-100000-12-03|-010000-04-05|-000001-08-07|0000-06-05|+010000-06-07|+100000-08-09"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_date_constructor_accepts_cldr_calendar_aliases() {
|
||||
let mut vm = Vm::new();
|
||||
// CLDR 别名归一到规范小写 ID:ethiopic-amete-alem → ethioaa、islamicc → islamic-civil。
|
||||
let r = eval(
|
||||
&mut vm,
|
||||
"Temporal.PlainDate.from({ year: 2000, month: 5, day: 2, calendar: 'ethiopic-amete-alem' }).calendarId + ',' +
|
||||
Temporal.PlainDate.from({ year: 2000, month: 5, day: 2, calendar: 'islamicc' }).calendarId + ',' +
|
||||
Temporal.PlainDate.from({ year: 2000, month: 5, day: 2, calendar: 'ETHIOpic-TANA-alem' }).calendarId",
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(str_val(&vm, r), "ethioaa,islamic-civil,ethiopic");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn plain_date_requires_new() {
|
||||
let mut vm = Vm::new();
|
||||
@@ -3243,7 +3301,7 @@ fn plain_month_day_to_plain_date_matrix() {
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
str_val(&vm, r),
|
||||
"2002-01-22|TypeError|TypeError|0012-01-22|RangeError|2020-02-29|2023-02-28|2020-02-29|-271821-04-19|RangeError|275760-09-13|RangeError|iso8601|TypeError|2002-01-22"
|
||||
"2002-01-22|TypeError|TypeError|0012-01-22|RangeError|2020-02-29|2023-02-28|2020-02-29|-271821-04-19|RangeError|+275760-09-13|RangeError|iso8601|TypeError|2002-01-22"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3743,7 +3801,7 @@ fn plain_year_month_to_plain_date_matrix() {
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
str_val(&vm, r),
|
||||
"2002-01-22|TypeError|TypeError|TypeError|TypeError|2023-02-28|1998-06-30|2000-05-01|2000-05-01|-271821-04-19|RangeError|275760-09-13|RangeError|RangeError"
|
||||
"2002-01-22|TypeError|TypeError|TypeError|TypeError|2023-02-28|1998-06-30|2000-05-01|2000-05-01|-271821-04-19|RangeError|+275760-09-13|RangeError|RangeError"
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user