feat: PlainDate toString 支持日历选项与六位年号

This commit is contained in:
2026-10-15 17:08:08 +00:00
parent 5d4ed8908c
commit 6bc85aa9b1
5 changed files with 123 additions and 42 deletions
+17 -3
View File
@@ -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 大单位运算,月份不足日时取月末)。