mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: 字符串时区注解并行提取 helper(提取与剥离同一次扫描)
This commit is contained in:
@@ -1362,6 +1362,77 @@ pub fn zoned_date_time_calendar_id<H: VmHost>(vm: &mut H, args: &[u8]) -> Native
|
||||
NativeResult::Ok(obj.get_prop_at(2))
|
||||
}
|
||||
|
||||
/// 与 instant_string_without_annotations 同一次扫描并行提取末尾时区注解(不剥离)。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 仿 instant_string_without_annotations 的注解扫描循环,跳过带 `=` 的 key 注解。
|
||||
/// 2. 首个无 `=` 的注解按时间区形式校验(UTC 大小写 / ±HH / ±HH:MM / ±HHMM),命中即返回。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 无时区注解或注解形式非法返回 None;critical(`!` 前缀)注解同样返回(带标记)。
|
||||
/// - 不修改 instant_string_without_annotations 的返回;剥离与提取各自独立扫描。
|
||||
/// - IANA 命名区(如 America/New_York)通过本函数校验,由调用方 canonical_time_zone 裁决。
|
||||
#[allow(dead_code)]
|
||||
fn extract_time_zone_annotation(input: &str) -> Option<(String, bool)> {
|
||||
let first_annotation = input.find('[')?;
|
||||
let mut rest = &input[first_annotation..];
|
||||
while !rest.is_empty() {
|
||||
let body_start = rest.strip_prefix('[')?;
|
||||
let close = body_start.find(']')?;
|
||||
let body = &body_start[..close];
|
||||
rest = &body_start[close + 1..];
|
||||
if body.is_empty() || (!rest.is_empty() && !rest.starts_with('[')) {
|
||||
return None;
|
||||
}
|
||||
let (critical, annotation) = match body.strip_prefix('!') {
|
||||
Some(value) => (true, value),
|
||||
None => (false, body),
|
||||
};
|
||||
|
||||
// 带 `=` 的是 key 注解(u-ca 等),跳过继续扫描。
|
||||
if annotation.contains('=') {
|
||||
continue;
|
||||
}
|
||||
if !is_time_zone_annotation_value(annotation) {
|
||||
return None;
|
||||
}
|
||||
return Some((annotation.to_string(), critical));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// 校验时区注解值:UTC/Z 大小写折叠、±HH / ±HH:MM / ±HHMM 数值偏移。
|
||||
/// 非数值形式(IANA 命名区)原样放行,交由 canonical_time_zone 裁决。
|
||||
fn is_time_zone_annotation_value(annotation: &str) -> bool {
|
||||
if annotation.eq_ignore_ascii_case("UTC") || annotation.eq_ignore_ascii_case("Z") {
|
||||
return true;
|
||||
}
|
||||
if !matches!(annotation.as_bytes().first(), Some(b'+' | b'-')) {
|
||||
return true;
|
||||
}
|
||||
let bytes = annotation.as_bytes();
|
||||
let hour_only = bytes.len() == 3 && bytes[1..3].iter().all(u8::is_ascii_digit);
|
||||
let hour_minute = bytes.len() == 6
|
||||
&& bytes[3] == b':'
|
||||
&& bytes[1..3].iter().all(u8::is_ascii_digit)
|
||||
&& bytes[4..6].iter().all(u8::is_ascii_digit);
|
||||
let hour_minute_compact = bytes.len() == 5 && bytes[1..5].iter().all(u8::is_ascii_digit);
|
||||
if hour_only {
|
||||
return (bytes[1] - b'0') * 10 + bytes[2] - b'0' <= 23;
|
||||
}
|
||||
if hour_minute {
|
||||
let hour = (bytes[1] - b'0') * 10 + bytes[2] - b'0';
|
||||
let minute = (bytes[4] - b'0') * 10 + bytes[5] - b'0';
|
||||
return hour <= 23 && minute <= 59;
|
||||
}
|
||||
if hour_minute_compact {
|
||||
let hour = (bytes[1] - b'0') * 10 + bytes[2] - b'0';
|
||||
let minute = (bytes[3] - b'0') * 10 + bytes[4] - b'0';
|
||||
return hour <= 23 && minute <= 59;
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// 时区注解文本:命名区原样返回,偏移区规范化为 `±HH:MM` 带冒号;critical 时 `!` 置于括号内。
|
||||
fn format_time_zone_annotation(time_zone_id: &str, critical: bool) -> String {
|
||||
let inner = if matches!(time_zone_id.as_bytes().first(), Some(b'+') | Some(b'-')) {
|
||||
@@ -5493,4 +5564,32 @@ mod tests {
|
||||
assert_eq!(canonical_time_zone("+0060"), None);
|
||||
assert_eq!(canonical_time_zone("+123"), None); // 长度不符
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extract_time_zone_annotation_forms() {
|
||||
// UTC / critical / 数值偏移各形式提取;u-ca 注解跳过,时区注解在前后均能取到。
|
||||
assert_eq!(extract_time_zone_annotation("1976-11-18T15:23:30[UTC]"), Some(("UTC".to_string(), false)));
|
||||
assert_eq!(extract_time_zone_annotation("1976-11-18T15:23:30[!UTC]"), Some(("UTC".to_string(), true)));
|
||||
assert_eq!(
|
||||
extract_time_zone_annotation("1976-11-18T15:23:30+01:00[+01:00]"),
|
||||
Some(("+01:00".to_string(), false))
|
||||
);
|
||||
assert_eq!(extract_time_zone_annotation("1976-11-18T15:23:30[+01]"), Some(("+01".to_string(), false)));
|
||||
assert_eq!(
|
||||
extract_time_zone_annotation("1976-11-18T15:23:30[+0100]"),
|
||||
Some(("+0100".to_string(), false))
|
||||
);
|
||||
assert_eq!(
|
||||
extract_time_zone_annotation("1976-11-18T15:23:30[u-ca=iso8601][UTC]"),
|
||||
Some(("UTC".to_string(), false))
|
||||
);
|
||||
assert_eq!(
|
||||
extract_time_zone_annotation("1976-11-18T15:23:30[UTC][u-ca=iso8601]"),
|
||||
Some(("UTC".to_string(), false))
|
||||
);
|
||||
// 无注解 / 非法偏移注解返回 None。
|
||||
assert_eq!(extract_time_zone_annotation("1976-11-18T15:23:30Z"), None);
|
||||
assert_eq!(extract_time_zone_annotation("1976-11-18T15:23:30[+24:00]"), None);
|
||||
assert_eq!(extract_time_zone_annotation("1976-11-18T15:23:30[]"), None);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user