mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: Temporal Now 七方法与族 toStringTag
This commit is contained in:
@@ -914,6 +914,67 @@ pub(crate) fn canonical_time_zone(value: &str) -> Option<String> {
|
||||
parse_offset_seconds(offset_id).map(|_| offset_id.to_string())
|
||||
}
|
||||
|
||||
/// 整数分钟偏移串解析为秒(±HH:MM / ±HH / ±HHMM 三形式)。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 亚分钟形式(±HH:MM:SS 及带小数秒的串)一律拒绝,返回 None。
|
||||
/// - 小时 ≤ 23、分钟 ≤ 59,越界返回 None。
|
||||
pub(crate) fn whole_minute_offset(value: &str) -> Option<i64> {
|
||||
if value.len() == 9 {
|
||||
return None;
|
||||
}
|
||||
fixed_offset_seconds(value)
|
||||
}
|
||||
|
||||
/// 时区字符串解析(ParseTemporalTimeZoneString):返回规范时区 ID。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. "-000000" 前缀(负零年出 ISO 界)先于一切分支拒绝。
|
||||
/// 2. UTC/Z(大小写折叠)→ "UTC"。
|
||||
/// 3. 独立整数分钟偏移串原样保留为时区 ID。
|
||||
/// 4. IANA 区名(含 legacy 别名)→ 规范名。
|
||||
/// 5. 完整日期时间串:带方括号注解 → 注解内容递归走同一分支序;
|
||||
/// 无注解、带尾偏移 → 偏移子串原样保留;无注解、无偏移 → None。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 亚分钟偏移(±HH:MM:SS、小数秒)在步骤 3 与步骤 5 的偏移分支均被拒绝。
|
||||
/// - 裸日期时间串(无偏移、无注解)不是合法时区,返回 None。
|
||||
/// - 与 canonical_time_zone 的区别:尾偏移接受 ±HHMM / ±HH 整数分钟形式,
|
||||
/// 不修改 canonical_time_zone 的既有接受面。
|
||||
pub(crate) fn parse_time_zone_string(value: &str) -> Option<String> {
|
||||
let input = value.trim();
|
||||
if input.starts_with("-000000") {
|
||||
return None;
|
||||
}
|
||||
if input.eq_ignore_ascii_case("UTC") || input.eq_ignore_ascii_case("Z") {
|
||||
return Some("UTC".to_string());
|
||||
}
|
||||
if whole_minute_offset(input).is_some() {
|
||||
return Some(input.to_string());
|
||||
}
|
||||
if let Some(name) = tz_canonical_name(input) {
|
||||
return Some(name.to_string());
|
||||
}
|
||||
if let Some(open) = input.rfind('[') {
|
||||
if !input.ends_with(']') || open + 2 > input.len() {
|
||||
return None;
|
||||
}
|
||||
let annotation = &input[open + 1..input.len() - 1];
|
||||
return parse_time_zone_string(annotation);
|
||||
}
|
||||
let time_start = input.find(['T', 't', ' '])?;
|
||||
let time = &input[time_start + 1..];
|
||||
if time.ends_with(['Z', 'z']) {
|
||||
return Some("UTC".to_string());
|
||||
}
|
||||
let offset_start = time
|
||||
.char_indices()
|
||||
.rev()
|
||||
.find_map(|(index, ch)| matches!(ch, '+' | '-').then_some(index))?;
|
||||
let offset_id = &time[offset_start..];
|
||||
whole_minute_offset(offset_id).map(|_| offset_id.to_string())
|
||||
}
|
||||
|
||||
pub(crate) fn civil_from_days(days: i128) -> (i128, i128, i128) {
|
||||
let shifted = days + 719_468;
|
||||
let era = shifted.div_euclid(146_097);
|
||||
|
||||
@@ -11,12 +11,15 @@ use super::time_zone::zone_offset_seconds;
|
||||
use super::{
|
||||
balance_instant_difference, canonical_time_zone, civil_from_days, days_from_civil, days_in_month,
|
||||
duration_like_values, duration_time_nanoseconds, ensure_instant, format_iso_year, format_offset_seconds_text,
|
||||
get_instant_epoch_ns, initialize_temporal_receiver, is_ctor_call, make_duration, make_instant,
|
||||
make_zoned_date_time, native_engine_error, parse_digits, parse_fractional_second_digits, parse_round_options,
|
||||
get_instant_epoch_ns, initialize_temporal_receiver, is_ctor_call, make_duration, make_instant, make_plain_date,
|
||||
make_plain_date_time, make_plain_month_day, make_plain_time, make_plain_year_month, make_zoned_date_time,
|
||||
native_engine_error, parse_digits, parse_fractional_second_digits, parse_round_options, parse_time_zone_string,
|
||||
receiver_obj, round_instant_difference, temporal_option_number, temporal_option_string, temporal_option_value,
|
||||
FractionalSecondDigitsInput, MAX_INSTANT_NS,
|
||||
};
|
||||
|
||||
const DAY_NS: i128 = 86_400_000_000_000;
|
||||
|
||||
pub(crate) fn instant_string_without_annotations(input: &str) -> Option<&str> {
|
||||
let Some(first_annotation) = input.find('[') else {
|
||||
return Some(input);
|
||||
@@ -288,9 +291,7 @@ fn instant_like_epoch_ns<H: VmHost>(vm: &mut H, value: JsValue) -> Result<i128,
|
||||
|
||||
/// `Temporal.Now.instant()`:返回当前时刻的 Temporal.Instant。
|
||||
pub fn now_instant<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
|
||||
let now = Utc::now();
|
||||
let epoch_ns = now.timestamp() as i128 * 1_000_000_000 + now.timestamp_subsec_nanos() as i128;
|
||||
make_instant(vm, epoch_ns)
|
||||
make_instant(vm, now_epoch_ns())
|
||||
}
|
||||
|
||||
/// `Temporal.Now.timeZoneId()`:引擎固定使用 UTC。
|
||||
@@ -298,6 +299,78 @@ pub fn now_time_zone_id<H: VmHost>(vm: &mut H, _args: &[u8]) -> NativeResult {
|
||||
NativeResult::Ok(vm.new_string("UTC"))
|
||||
}
|
||||
|
||||
fn now_epoch_ns() -> i128 {
|
||||
let now = Utc::now();
|
||||
now.timestamp() as i128 * 1_000_000_000 + now.timestamp_subsec_nanos() as i128
|
||||
}
|
||||
|
||||
/// Now.*ISO 方法的可选 timeZone 参数解析:undefined → "UTC";非字符串 →
|
||||
/// TypeError;字符串经 parse_time_zone_string 校验(空串与非法形式 → RangeError)。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 本批口径下 zone 参仅做语法校验,返回分量恒取 UTC,zone 值不影响分量。
|
||||
fn now_time_zone_param<H: VmHost>(vm: &mut H, args: &[u8]) -> Result<String, JsValue> {
|
||||
let raw = if args.len() < 2 { JsValue::undefined() } else { vm.reg(args[1]) };
|
||||
if raw.is_undefined() {
|
||||
return Ok("UTC".to_string());
|
||||
}
|
||||
if !raw.is_string() {
|
||||
return Err(crate::error::create_type_error(vm, "invalid time zone"));
|
||||
}
|
||||
let input = to_string(raw);
|
||||
parse_time_zone_string(&input).ok_or_else(|| crate::error::create_range_error(vm, "invalid time zone"))
|
||||
}
|
||||
|
||||
/// `Temporal.Now.plainDateISO([timeZone])`:返回当前 UTC 日期的 PlainDate。
|
||||
pub fn now_plain_date_iso<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let epoch_ns = now_epoch_ns();
|
||||
native_try!(now_time_zone_param(vm, args));
|
||||
let (year, month, day) = civil_from_days(epoch_ns.div_euclid(DAY_NS));
|
||||
make_plain_date(vm, year as i32, month as u32, day as u32, "iso8601")
|
||||
}
|
||||
|
||||
/// `Temporal.Now.plainTimeISO([timeZone])`:返回当前 UTC 时刻的 PlainTime。
|
||||
/// 直接按纳秒分量构造,不经 toPlainTime 路径。
|
||||
pub fn now_plain_time_iso<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let epoch_ns = now_epoch_ns();
|
||||
native_try!(now_time_zone_param(vm, args));
|
||||
make_plain_time(vm, epoch_ns.rem_euclid(DAY_NS) as f64)
|
||||
}
|
||||
|
||||
/// `Temporal.Now.plainDateTimeISO([timeZone])`:返回当前 UTC 日期时间的 PlainDateTime。
|
||||
pub fn now_plain_date_time_iso<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let epoch_ns = now_epoch_ns();
|
||||
native_try!(now_time_zone_param(vm, args));
|
||||
let (year, month, day) = civil_from_days(epoch_ns.div_euclid(DAY_NS));
|
||||
make_plain_date_time(vm, year as i32, month as u32, day as u32, epoch_ns.rem_euclid(DAY_NS) as f64, "iso8601")
|
||||
}
|
||||
|
||||
/// `Temporal.Now.plainMonthDayISO([timeZone])`:返回当前 UTC 月日的 PlainMonthDay
|
||||
/// (参考年取当前年)。
|
||||
pub fn now_plain_month_day_iso<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let epoch_ns = now_epoch_ns();
|
||||
native_try!(now_time_zone_param(vm, args));
|
||||
let (year, month, day) = civil_from_days(epoch_ns.div_euclid(DAY_NS));
|
||||
make_plain_month_day(vm, month as u32, day as u32, year as i32, "iso8601")
|
||||
}
|
||||
|
||||
/// `Temporal.Now.plainYearMonthISO([timeZone])`:返回当前 UTC 年月的 PlainYearMonth
|
||||
/// (参考日取当前日)。
|
||||
pub fn now_plain_year_month_iso<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let epoch_ns = now_epoch_ns();
|
||||
native_try!(now_time_zone_param(vm, args));
|
||||
let (year, month, day) = civil_from_days(epoch_ns.div_euclid(DAY_NS));
|
||||
make_plain_year_month(vm, year as i32, month as u32, day as u32, "iso8601")
|
||||
}
|
||||
|
||||
/// `Temporal.Now.zonedDateTimeISO([timeZone])`:返回同一纪元纳秒、指定时区的
|
||||
/// ZonedDateTime(calendar 恒 iso8601)。
|
||||
pub fn now_zoned_date_time_iso<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let epoch_ns = now_epoch_ns();
|
||||
let zone = native_try!(now_time_zone_param(vm, args));
|
||||
make_zoned_date_time(vm, epoch_ns, &zone, "iso8601")
|
||||
}
|
||||
|
||||
/// `Temporal.Instant` 构造器:把参数按 ToBigInt 转换为纪元纳秒。
|
||||
pub fn instant_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let ctor_proto = vm.builtin_proto(ProtoKind::InstantProto);
|
||||
@@ -691,7 +764,6 @@ pub fn instant_since<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
pub(crate) fn format_instant_iso(
|
||||
epoch_ns: i128, offset_seconds: Option<i64>, include_seconds: bool, fractional_digits: Option<usize>,
|
||||
) -> Option<String> {
|
||||
const DAY_NS: i128 = 86_400_000_000_000;
|
||||
let offset_ns = i128::from(offset_seconds.unwrap_or(0)).checked_mul(1_000_000_000)?;
|
||||
let local_ns = epoch_ns.checked_add(offset_ns)?;
|
||||
let days = local_ns.div_euclid(DAY_NS);
|
||||
|
||||
Reference in New Issue
Block a user