mirror of
https://github.com/pjh456/OxideJS.git
synced 2026-10-23 18:21:13 +08:00
feat: temporal IANA 区接受面与 epoch 依赖偏移读取路径加亚分钟偏移 getter
This commit is contained in:
@@ -20,9 +20,10 @@ use oxide_runtime_api::{to_number, to_string, NativeResult, VmHost};
|
||||
use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use super::time_zone::{tz_canonical_name, zone_offset_seconds};
|
||||
use super::{
|
||||
instant_time_zone_offset, parse_iso_date, parse_plain_date_time_string, parse_plain_time_string, valid_iso_date,
|
||||
valid_plain_date_time_range, valid_plain_time,
|
||||
parse_iso_date, parse_plain_date_time_string, parse_plain_time_string, valid_iso_date, valid_plain_date_time_range,
|
||||
valid_plain_time,
|
||||
};
|
||||
|
||||
pub(crate) const MAX_INSTANT_NS: i128 = 8_640_000_000_000_000_000_000;
|
||||
@@ -658,13 +659,71 @@ pub(crate) fn parse_offset_seconds(value: &str) -> Option<i64> {
|
||||
Some(if bytes[0] == b'-' { -magnitude } else { magnitude })
|
||||
}
|
||||
|
||||
pub(crate) fn canonical_time_zone(value: &str) -> Option<(String, i64)> {
|
||||
/// 数值偏移串统一解析为秒(±HH:MM / ±HH / ±HHMM / ±HH:MM:SS 四形式)。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 四形式以外的串(含带小数秒的 ±HH:MM:SS.xxx)返回 None。
|
||||
/// - 小时 ≤ 23、分钟/秒 ≤ 59,越界返回 None。
|
||||
pub(crate) fn fixed_offset_seconds(value: &str) -> Option<i64> {
|
||||
let bytes = value.as_bytes();
|
||||
let sign = match bytes.first() {
|
||||
Some(b'+') => 1_i64,
|
||||
Some(b'-') => -1_i64,
|
||||
_ => return None,
|
||||
};
|
||||
let digits = |range: std::ops::Range<usize>| -> Option<i64> {
|
||||
if bytes[range.clone()].iter().all(u8::is_ascii_digit) {
|
||||
Some(bytes[range].iter().map(|b| i64::from(b - b'0')).fold(0, |acc, d| acc * 10 + d))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
};
|
||||
// ±HH:MM(6 字符):canonical_time_zone 接受面与 zone_offset_seconds 快速路径共用。
|
||||
if bytes.len() == 6 && bytes[3] == b':' {
|
||||
let hour = digits(1..3)?;
|
||||
let minute = digits(4..6)?;
|
||||
return (hour <= 23 && minute <= 59).then_some(sign * (hour * 3600 + minute * 60));
|
||||
}
|
||||
// ±HH(3 字符):小时制简写,分钟恒 0。
|
||||
if bytes.len() == 3 {
|
||||
let hour = digits(1..3)?;
|
||||
return (hour <= 23).then_some(sign * hour * 3600);
|
||||
}
|
||||
// ±HHMM(5 字符):无冒号归一形式。
|
||||
if bytes.len() == 5 {
|
||||
let hour = digits(1..3)?;
|
||||
let minute = digits(3..5)?;
|
||||
return (hour <= 23 && minute <= 59).then_some(sign * (hour * 3600 + minute * 60));
|
||||
}
|
||||
// ±HH:MM:SS(9 字符):亚分钟偏移,zone_offset_seconds 快速路径专用。
|
||||
if bytes.len() == 9 && bytes[3] == b':' && bytes[6] == b':' {
|
||||
let hour = digits(1..3)?;
|
||||
let minute = digits(4..6)?;
|
||||
let second = digits(7..9)?;
|
||||
return (hour <= 23 && minute <= 59 && second <= 59).then_some(sign * (hour * 3600 + minute * 60 + second));
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// 时区标识符规范化:UTC/Z 归一为 "UTC",数值偏移串原样保留,IANA 区名解别名后
|
||||
/// 返回规范名。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. UTC/Z(大小写折叠)→ "UTC"。
|
||||
/// 2. 数值偏移串(±HH:MM / ±HH / ±HHMM)原样保留为时区 ID。
|
||||
/// 3. IANA 区名(含 legacy 别名)命中区表 → 规范名。
|
||||
/// 4. 带注解的日期时间以最后一个方括号内标识符为准;日期时间串尾偏移按偏移串处理。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 返回只含规范名,不含偏移:偏移是 epoch 的函数,由 zone_offset_seconds 统一提供。
|
||||
/// - 非法形式(越界小时/分钟、-000000 前缀、未知区名)返回 None。
|
||||
pub(crate) fn canonical_time_zone(value: &str) -> Option<String> {
|
||||
let input = value.trim();
|
||||
if input.eq_ignore_ascii_case("UTC") || input.eq_ignore_ascii_case("Z") {
|
||||
return Some(("UTC".to_string(), 0));
|
||||
return Some("UTC".to_string());
|
||||
}
|
||||
if let Some(offset) = parse_offset_seconds(input) {
|
||||
return Some((input.to_string(), offset));
|
||||
if parse_offset_seconds(input).is_some() {
|
||||
return Some(input.to_string());
|
||||
}
|
||||
|
||||
// ±HH 基本偏移("+01" / "-05"):3 字符,hour ≤ 23,分钟恒 0。仅放宽构造器时区接受面。
|
||||
@@ -674,8 +733,7 @@ pub(crate) fn canonical_time_zone(value: &str) -> Option<(String, i64)> {
|
||||
{
|
||||
let hour = (input.as_bytes()[1] - b'0') as i64 * 10 + (input.as_bytes()[2] - b'0') as i64;
|
||||
if hour <= 23 {
|
||||
let sign = if input.as_bytes()[0] == b'-' { -1 } else { 1 };
|
||||
return Some((input.to_string(), sign * hour * 3600));
|
||||
return Some(input.to_string());
|
||||
}
|
||||
return None;
|
||||
}
|
||||
@@ -688,8 +746,7 @@ pub(crate) fn canonical_time_zone(value: &str) -> Option<(String, i64)> {
|
||||
let hour = (input.as_bytes()[1] - b'0') as i64 * 10 + (input.as_bytes()[2] - b'0') as i64;
|
||||
let minute = (input.as_bytes()[3] - b'0') as i64 * 10 + (input.as_bytes()[4] - b'0') as i64;
|
||||
if hour <= 23 && minute <= 59 {
|
||||
let sign = if input.as_bytes()[0] == b'-' { -1 } else { 1 };
|
||||
return Some((input.to_string(), sign * (hour * 3600 + minute * 60)));
|
||||
return Some(input.to_string());
|
||||
}
|
||||
return None;
|
||||
}
|
||||
@@ -697,6 +754,11 @@ pub(crate) fn canonical_time_zone(value: &str) -> Option<(String, i64)> {
|
||||
return None;
|
||||
}
|
||||
|
||||
// IANA 区名(含 legacy 别名):解别名后返回规范名。
|
||||
if let Some(name) = tz_canonical_name(input) {
|
||||
return Some(name.to_string());
|
||||
}
|
||||
|
||||
// 带 annotation 的日期时间以最后一个方括号内标识符为准。
|
||||
if let Some(open) = input.rfind('[') {
|
||||
if !input.ends_with(']') || open + 2 > input.len() {
|
||||
@@ -704,22 +766,25 @@ pub(crate) fn canonical_time_zone(value: &str) -> Option<(String, i64)> {
|
||||
}
|
||||
let annotation = &input[open + 1..input.len() - 1];
|
||||
if annotation.eq_ignore_ascii_case("UTC") {
|
||||
return Some(("UTC".to_string(), 0));
|
||||
return Some("UTC".to_string());
|
||||
}
|
||||
return parse_offset_seconds(annotation).map(|offset| (annotation.to_string(), offset));
|
||||
if parse_offset_seconds(annotation).is_some() {
|
||||
return Some(annotation.to_string());
|
||||
}
|
||||
return tz_canonical_name(annotation).map(|name| name.to_string());
|
||||
}
|
||||
|
||||
let time_start = input.find(['T', 't', ' '])?;
|
||||
let time = &input[time_start + 1..];
|
||||
if time.ends_with(['Z', 'z']) {
|
||||
return Some(("UTC".to_string(), 0));
|
||||
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..];
|
||||
parse_offset_seconds(offset_id).map(|offset| (offset_id.to_string(), offset))
|
||||
parse_offset_seconds(offset_id).map(|_| offset_id.to_string())
|
||||
}
|
||||
|
||||
pub(crate) fn civil_from_days(days: i128) -> (i128, i128, i128) {
|
||||
@@ -1154,6 +1219,20 @@ pub(crate) fn temporal_calendar_id_strict<H: VmHost>(vm: &mut H, value: JsValue)
|
||||
temporal_calendar_id(vm, value)
|
||||
}
|
||||
|
||||
/// 偏移秒数格式化为 `±HH:MM`;秒数非分钟整数倍时补秒段(`±HH:MM:SS`)。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 输入为有符号偏移秒(|offset| ≤ 24 小时),负值取绝对值后拼负号。
|
||||
pub(crate) fn format_offset_seconds_text(offset_seconds: i64) -> String {
|
||||
let sign = if offset_seconds < 0 { '-' } else { '+' };
|
||||
let magnitude = offset_seconds.abs();
|
||||
if magnitude % 60 != 0 {
|
||||
format!("{sign}{:02}:{:02}:{:02}", magnitude / 3600, (magnitude % 3600) / 60, magnitude % 60)
|
||||
} else {
|
||||
format!("{sign}{:02}:{:02}", magnitude / 3600, (magnitude % 3600) / 60)
|
||||
}
|
||||
}
|
||||
|
||||
/// 将 ZonedDateTime 按时区偏移转换为本地 PlainDateTime 分量。
|
||||
pub(crate) fn zoned_date_time_plain_parts<H: VmHost>(
|
||||
vm: &mut H, obj: &JsObject,
|
||||
@@ -1162,7 +1241,7 @@ pub(crate) fn zoned_date_time_plain_parts<H: VmHost>(
|
||||
return Err(crate::error::create_range_error(vm, "invalid ZonedDateTime"));
|
||||
};
|
||||
let time_zone_id = to_string(obj.get_prop_at(1));
|
||||
let Some(offset_seconds) = instant_time_zone_offset(&time_zone_id) else {
|
||||
let Some(offset_seconds) = zone_offset_seconds(&time_zone_id, epoch_ns.div_euclid(1_000_000_000) as i64) else {
|
||||
return Err(crate::error::create_range_error(vm, "invalid time zone"));
|
||||
};
|
||||
const DAY_NS: i128 = 86_400_000_000_000;
|
||||
|
||||
@@ -15,7 +15,8 @@ use super::difference::{
|
||||
add_date_duration, add_days_iso, compare_iso_date, date_duration_sign, date_until_iso, nudge_iso_difference,
|
||||
nudge_window, plain_date_time_unit_index, round_instant_difference, DifferenceSettings, DAY_NS, MAX_ISO_DAY,
|
||||
};
|
||||
use super::instant::{instant_rounding_mode, instant_time_zone_offset, InstantRoundingMode};
|
||||
use super::instant::{instant_rounding_mode, InstantRoundingMode};
|
||||
use super::time_zone::zone_offset_seconds;
|
||||
use super::zoned_date_time::{parse_any_offset_seconds, valid_offset_fraction, zoned_date_time_string_parts};
|
||||
use super::{make_duration, parse_plain_date_time_string};
|
||||
|
||||
@@ -306,11 +307,11 @@ fn duration_relative_to_date<H: VmHost>(
|
||||
if let Ok((year, month, day, _time_ns)) = parse_plain_date_time_string(&text) {
|
||||
return Ok(Some((i128::from(year), i128::from(month), i128::from(day))));
|
||||
}
|
||||
// 回退支:plain 首支解析失败的串(含 Z 的串或非法注解)。命名区当前全部 RangeError
|
||||
// (canonical_time_zone 拒 IANA),可解析的带注解串已被 plain 首支接受。
|
||||
// 按 instant 解析 + 注解时区反推墙钟日期。
|
||||
// 回退支:plain 首支解析失败的串(含 Z 的串、IANA 区注解串)。
|
||||
// 按 instant 解析 + 注解时区偏移(epoch 依赖查找)反推墙历日期。
|
||||
let (epoch_ns, time_zone_id, _calendar) = zoned_date_time_string_parts(vm, &text, "reject")?;
|
||||
let offset_seconds = instant_time_zone_offset(&time_zone_id).unwrap_or(0);
|
||||
let offset_seconds = zone_offset_seconds(&time_zone_id, epoch_ns.div_euclid(1_000_000_000) as i64)
|
||||
.ok_or_else(|| crate::error::create_range_error(vm, "invalid time zone"))?;
|
||||
let wall_ns = epoch_ns + i128::from(offset_seconds) * 1_000_000_000;
|
||||
let (year, month, day) = civil_from_days(wall_ns.div_euclid(DAY_NS));
|
||||
return Ok(Some((year, month, day)));
|
||||
|
||||
@@ -7,12 +7,14 @@ use oxide_types::object::JsObject;
|
||||
use oxide_types::value::JsValue;
|
||||
|
||||
use super::common::native_try;
|
||||
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, 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, receiver_obj, round_instant_difference, temporal_option_number,
|
||||
temporal_option_string, temporal_option_value, FractionalSecondDigitsInput, MAX_INSTANT_NS,
|
||||
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, receiver_obj,
|
||||
round_instant_difference, temporal_option_number, temporal_option_string, temporal_option_value,
|
||||
FractionalSecondDigitsInput, MAX_INSTANT_NS,
|
||||
};
|
||||
|
||||
pub(crate) fn instant_string_without_annotations(input: &str) -> Option<&str> {
|
||||
@@ -727,11 +729,7 @@ pub fn instant_since<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
instant_difference(vm, args, true)
|
||||
}
|
||||
|
||||
pub(crate) fn instant_time_zone_offset(value: &str) -> Option<i64> {
|
||||
canonical_time_zone(value).map(|(_, offset)| offset)
|
||||
}
|
||||
|
||||
fn format_instant_iso(
|
||||
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;
|
||||
@@ -767,9 +765,7 @@ fn format_instant_iso(
|
||||
}
|
||||
|
||||
if let Some(offset) = offset_seconds {
|
||||
let sign = if offset < 0 { '-' } else { '+' };
|
||||
let magnitude = offset.abs();
|
||||
output.push_str(&format!("{sign}{:02}:{:02}", magnitude / 3600, (magnitude % 3600) / 60));
|
||||
output.push_str(&format_offset_seconds_text(offset));
|
||||
} else {
|
||||
output.push('Z');
|
||||
}
|
||||
@@ -857,7 +853,7 @@ pub fn instant_to_string<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "invalid time zone"));
|
||||
}
|
||||
let time_zone = to_string(time_zone_raw);
|
||||
match instant_time_zone_offset(&time_zone) {
|
||||
match zone_offset_seconds(&time_zone, epoch_ns.div_euclid(1_000_000_000) as i64) {
|
||||
Some(offset) => Some(offset),
|
||||
None => return NativeResult::Err(crate::error::create_range_error(vm, "invalid time zone")),
|
||||
}
|
||||
@@ -900,7 +896,7 @@ pub fn instant_to_zoned_date_time_iso<H: VmHost>(vm: &mut H, args: &[u8]) -> Nat
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "invalid time zone"));
|
||||
}
|
||||
let input = to_string(raw);
|
||||
let Some((time_zone_id, _)) = canonical_time_zone(&input) else {
|
||||
let Some(time_zone_id) = canonical_time_zone(&input) else {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid time zone"));
|
||||
};
|
||||
make_zoned_date_time(vm, epoch_ns, &time_zone_id, "iso8601")
|
||||
|
||||
@@ -19,6 +19,8 @@ pub use plain_date_time::*;
|
||||
pub use plain_month_day::*;
|
||||
pub use plain_time::*;
|
||||
pub use plain_year_month::*;
|
||||
#[cfg(test)]
|
||||
use time_zone::*;
|
||||
pub use zoned_date_time::*;
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@@ -164,10 +164,10 @@ fn zoned_date_time_string_wall_day_range_boundary() {
|
||||
|
||||
#[test]
|
||||
fn canonical_time_zone_4_digit_offset() {
|
||||
// ±HHMM 无冒号形式归一:ID 保留原串,offset 秒数正确换算。
|
||||
assert_eq!(canonical_time_zone("+0000"), Some(("+0000".to_string(), 0)));
|
||||
assert_eq!(canonical_time_zone("-0530"), Some(("-0530".to_string(), -19800)));
|
||||
assert_eq!(canonical_time_zone("+2330"), Some(("+2330".to_string(), 84600)));
|
||||
// ±HHMM 无冒号形式归一:ID 保留原串(偏移秒值由 fixed_offset_seconds 单独提供)。
|
||||
assert_eq!(canonical_time_zone("+0000"), Some("+0000".to_string()));
|
||||
assert_eq!(canonical_time_zone("-0530"), Some("-0530".to_string()));
|
||||
assert_eq!(canonical_time_zone("+2330"), Some("+2330".to_string()));
|
||||
// 非法分钟/小时拒绝。
|
||||
assert_eq!(canonical_time_zone("+2400"), None);
|
||||
assert_eq!(canonical_time_zone("+0060"), None);
|
||||
@@ -182,8 +182,8 @@ fn offset_seconds_representation_sub_minute() {
|
||||
assert_eq!(parse_any_offset_seconds("+01:00:00"), Some(3600));
|
||||
assert_eq!(parse_any_offset_seconds("-23:59:59"), Some(-(23 * 3600 + 59 * 60 + 59)));
|
||||
// 固定偏移秒值与分钟值 ×60 一致(±HH:MM 六字符形)。
|
||||
assert_eq!(canonical_time_zone("+01:00"), Some(("+01:00".to_string(), 3600)));
|
||||
assert_eq!(canonical_time_zone("-05:30"), Some(("-05:30".to_string(), -19800)));
|
||||
assert_eq!(canonical_time_zone("+01:00"), Some("+01:00".to_string()));
|
||||
assert_eq!(canonical_time_zone("-05:30"), Some("-05:30".to_string()));
|
||||
// 亚分钟偏移经 local_to_epoch_ns 与 parse_instant_string 互逆对拍。
|
||||
assert_eq!(
|
||||
parse_instant_string("2024-01-01T00:00:00-00:44:30"),
|
||||
@@ -385,3 +385,51 @@ fn plain_time_apply_duration_ignores_date_units() {
|
||||
// 忽略 days:time_delta 不含 DAY_NS 分量。
|
||||
assert_eq!(time_delta % 86_400_000_000_000, 9_000_000_000_000);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn canonical_time_zone_iana_acceptance() {
|
||||
// IANA 区名(含 legacy 别名)解到规范名;非法区名拒绝。
|
||||
assert_eq!(canonical_time_zone("America/New_York"), Some("America/New_York".to_string()));
|
||||
assert_eq!(canonical_time_zone("Asia/Calcutta"), Some("Asia/Kolkata".to_string()));
|
||||
assert_eq!(canonical_time_zone("Etc/Ignored"), Some("Etc/UTC".to_string()));
|
||||
assert_eq!(canonical_time_zone("Not/AZone"), None);
|
||||
// UTC / 数值偏移臂不变(只返回规范名,不再返回偏移)。
|
||||
assert_eq!(canonical_time_zone("UTC"), Some("UTC".to_string()));
|
||||
assert_eq!(canonical_time_zone("Z"), Some("UTC".to_string()));
|
||||
assert_eq!(canonical_time_zone("+01:00"), Some("+01:00".to_string()));
|
||||
// 注解串:IANA 区注解解到规范名。
|
||||
assert_eq!(canonical_time_zone("2024-01-01T00:00:00[UTC]"), Some("UTC".to_string()));
|
||||
assert_eq!(
|
||||
canonical_time_zone("2024-01-01T00:00:00[America/New_York]"),
|
||||
Some("America/New_York".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zone_offset_seconds_fixed_and_iana() {
|
||||
// 固定区退化情形:偏移与 epoch 无关,恒返常量(含亚分钟偏移)。
|
||||
assert_eq!(zone_offset_seconds("+05:30", 0), Some(19800));
|
||||
assert_eq!(zone_offset_seconds("-00:44:30", 1_000_000_000), Some(-2670));
|
||||
assert_eq!(zone_offset_seconds("UTC", 0), Some(0));
|
||||
// IANA 区:1970 前后两点(EST / EDT 边界 2000-04-02T07:00Z,纽约本地 02:00)。
|
||||
assert_eq!(zone_offset_seconds("America/New_York", 0), Some(-18000));
|
||||
assert_eq!(zone_offset_seconds("America/New_York", 954_658_800), Some(-14400));
|
||||
assert_eq!(zone_offset_seconds("America/New_York", 954_658_800 - 1), Some(-18000));
|
||||
// 非法区名返回 None。
|
||||
assert_eq!(zone_offset_seconds("Not/AZone", 0), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_minute_offset_getter_branch() {
|
||||
// Monrovia -00:44:30:四格式化面补秒段;整分钟偏移不补。
|
||||
assert_eq!(format_offset_seconds_text(-2670), "-00:44:30");
|
||||
assert_eq!(format_offset_seconds_text(2670), "+00:44:30");
|
||||
assert_eq!(format_offset_seconds_text(3600), "+01:00");
|
||||
assert_eq!(format_time_zone_annotation("-00:44:30", false), "[-00:44:30]");
|
||||
let iso = format_zoned_date_time_iso(0, -2670, "-00:44:30", "iso8601", true, None, "auto", "auto", "auto");
|
||||
assert_eq!(iso, Some("1969-12-31T23:15:30-00:44:30[-00:44:30]".to_string()));
|
||||
assert_eq!(
|
||||
format_instant_iso(0, Some(-2670), true, None),
|
||||
Some("1969-12-31T23:15:30-00:44:30".to_string())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
|
||||
use std::sync::LazyLock;
|
||||
|
||||
use super::common::fixed_offset_seconds;
|
||||
|
||||
/// 单个时区的解析结果:transition 表加偏移查找所需的预存值。
|
||||
///
|
||||
/// `transitions` 为 `(纪元秒, 该时刻起生效的偏移秒)`,按纪元秒严格升序;
|
||||
@@ -1121,7 +1123,6 @@ static ZONE_TABLE: LazyLock<ZoneTable> = LazyLock::new(|| {
|
||||
///
|
||||
/// # 注意事项
|
||||
/// - 首次调用触发全量解析(毫秒级),之后为纯二分。
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn tz_zone_data(name: &str) -> Option<&'static TzifZone> {
|
||||
let target = match LEGACY_ALIASES.binary_search_by(|a| a.0.cmp(name)) {
|
||||
Ok(i) => LEGACY_ALIASES[i].1,
|
||||
@@ -1133,6 +1134,26 @@ pub(crate) fn tz_zone_data(name: &str) -> Option<&'static TzifZone> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 按 IANA 区名(含 legacy 别名)查规范区名。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 先解 legacy 别名(`LEGACY_ALIASES` 二分),未命中则原名。
|
||||
/// 2. 在区表二分查找,命中返回其规范名(`&'static str`)。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 别名只解一层:别名目标是现行区名,不再二次解别名。
|
||||
pub(crate) fn tz_canonical_name(name: &str) -> Option<&'static str> {
|
||||
let target = match LEGACY_ALIASES.binary_search_by(|a| a.0.cmp(name)) {
|
||||
Ok(i) => LEGACY_ALIASES[i].1,
|
||||
Err(_) => name,
|
||||
};
|
||||
ZONE_TABLE
|
||||
.zones
|
||||
.binary_search_by(|z| z.0.cmp(target))
|
||||
.ok()
|
||||
.map(|i| ZONE_TABLE.zones[i].0)
|
||||
}
|
||||
|
||||
/// 查某区在 `epoch_s`(纪元秒)生效的 UTC 偏移(秒)。
|
||||
///
|
||||
/// # 步骤
|
||||
@@ -1147,7 +1168,6 @@ pub(crate) fn tz_zone_data(name: &str) -> Option<&'static TzifZone> {
|
||||
/// # 注意事项
|
||||
/// - 只支持正向查找(epoch → 偏移);local → epoch 的逆换算(ambiguous /
|
||||
/// nonexistent)由调用方迭代,不在本函数。
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn tz_offset_seconds(zone: &str, epoch_s: i64) -> Option<i64> {
|
||||
let z = tz_zone_data(zone)?;
|
||||
if z.transitions.is_empty() {
|
||||
@@ -1163,6 +1183,23 @@ pub(crate) fn tz_offset_seconds(zone: &str, epoch_s: i64) -> Option<i64> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 统一时区偏移查找(epoch 依赖):固定偏移区(数值偏移串)走快速路径恒返常量,
|
||||
/// IANA 区走 transition 表二分。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. 数值偏移串(±HH:MM / ±HH / ±HHMM / ±HH:MM:SS)直接解析为秒返回。
|
||||
/// 2. 未命中走 `tz_offset_seconds`(IANA 区,含 legacy 别名)。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 固定偏移区是 zone-aware 路径的退化情形:偏移与 epoch 无关,恒返回常量。
|
||||
/// - 非法区名(非数值偏移串且不在区表)返回 `None`。
|
||||
pub(crate) fn zone_offset_seconds(zone: &str, epoch_s: i64) -> Option<i64> {
|
||||
if let Some(offset) = fixed_offset_seconds(zone) {
|
||||
return Some(offset);
|
||||
}
|
||||
tz_offset_seconds(zone, epoch_s)
|
||||
}
|
||||
|
||||
/// 判断 `name` 是否为可接受的 IANA 区名(含 legacy 别名)。
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn tz_is_iana_zone(name: &str) -> bool {
|
||||
|
||||
@@ -8,19 +8,20 @@ use oxide_types::value::JsValue;
|
||||
|
||||
use super::common::native_try;
|
||||
use super::common::{
|
||||
canonical_time_zone, civil_from_days, days_from_civil, days_in_month, ensure_zoned_date_time, format_iso_year,
|
||||
get_calendar_id, get_double_prop, get_instant_epoch_ns, initialize_temporal_receiver, is_ctor_call,
|
||||
local_to_epoch_ns, make_instant, make_zoned_date_time, parse_digits, parse_fractional_second_digits,
|
||||
parse_temporal_string_impl, plain_date_time_object_parts, receiver_obj,
|
||||
canonical_time_zone, civil_from_days, days_from_civil, days_in_month, ensure_zoned_date_time, fixed_offset_seconds,
|
||||
format_iso_year, format_offset_seconds_text, get_calendar_id, get_double_prop, get_instant_epoch_ns,
|
||||
initialize_temporal_receiver, is_ctor_call, local_to_epoch_ns, make_instant, make_zoned_date_time, parse_digits,
|
||||
parse_fractional_second_digits, parse_temporal_string_impl, plain_date_time_object_parts, receiver_obj,
|
||||
reject_partial_object_with_calendar_or_time_zone, temporal_calendar_id, temporal_calendar_id_strict,
|
||||
temporal_option_number, temporal_option_string, temporal_option_value, temporal_overflow, temporal_string_strict,
|
||||
zoned_date_time_plain_parts, FractionalSecondDigitsInput, MAX_INSTANT_NS,
|
||||
};
|
||||
use super::difference::{difference_core, parse_difference_settings, MAX_ISO_DAY};
|
||||
use super::instant::{
|
||||
instant_rounding_mode, instant_string_without_annotations, instant_time_zone_offset, parse_instant_string,
|
||||
primitive_to_bigint, round_instant_ns,
|
||||
instant_rounding_mode, instant_string_without_annotations, parse_instant_string, primitive_to_bigint,
|
||||
round_instant_ns,
|
||||
};
|
||||
use super::time_zone::zone_offset_seconds;
|
||||
use super::{
|
||||
days_in_month_iso, duration_component_integer, duration_like_values, is_leap_year_iso, make_duration,
|
||||
make_plain_date, make_plain_date_time, make_plain_time, plain_time_components, plain_time_like_ns, valid_iso_date,
|
||||
@@ -45,7 +46,7 @@ pub fn zoned_date_time_constructor<H: VmHost>(vm: &mut H, args: &[u8]) -> Native
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "invalid time zone"));
|
||||
}
|
||||
let time_zone_input = to_string(vm.reg(args[2]));
|
||||
let Some((time_zone_id, _)) = canonical_time_zone(&time_zone_input) else {
|
||||
let Some(time_zone_id) = canonical_time_zone(&time_zone_input) else {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid time zone"));
|
||||
};
|
||||
let calendar = if args.len() >= 4 && !vm.reg(args[3]).is_undefined() {
|
||||
@@ -295,10 +296,11 @@ fn zoned_date_time_like_epoch_ns<H: VmHost>(
|
||||
/// 字符串分支:instant 解析 + 时区注解提取,按 offset 选项决定最终 epoch。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. extract_time_zone_annotation 取注解 → canonical_time_zone 得时区 ID 与偏移。
|
||||
/// 1. extract_time_zone_annotation 取注解 → canonical_time_zone 得时区 ID。
|
||||
/// 2. 主体剥注解后按 Z / 数值偏移 / 无偏移分路:Z 定 exact time,有偏移经 parse_instant_string
|
||||
/// 反推墙钟,无偏移直接解析墙钟。
|
||||
/// 3. 按 offset 选项 use/ignore/prefer/reject 决策 epoch,并做 Instant 范围校验。
|
||||
/// 3. 时区偏移按候选 epoch(墙钟按 UTC 解释)单次查找。
|
||||
/// 4. 按 offset 选项 use/ignore/prefer/reject 决策 epoch,并做 Instant 范围校验。
|
||||
///
|
||||
/// # 边界与前提
|
||||
/// - 字符串缺时区注解或注解无法规范化抛 RangeError;主体非法抛 RangeError。
|
||||
@@ -309,9 +311,8 @@ pub(crate) fn zoned_date_time_string_parts<H: VmHost>(
|
||||
let Some((tz_annotation, _critical)) = extract_time_zone_annotation(input) else {
|
||||
return Err(crate::error::create_range_error(vm, "invalid time zone"));
|
||||
};
|
||||
let Some((time_zone_id, time_zone_offset)) = canonical_time_zone(&tz_annotation) else {
|
||||
return Err(crate::error::create_range_error(vm, "invalid time zone"));
|
||||
};
|
||||
let time_zone_id =
|
||||
canonical_time_zone(&tz_annotation).ok_or_else(|| crate::error::create_range_error(vm, "invalid time zone"))?;
|
||||
let Some(body) = instant_string_without_annotations(input) else {
|
||||
return Err(crate::error::create_range_error(vm, "invalid ISO 8601 date-time"));
|
||||
};
|
||||
@@ -342,6 +343,11 @@ pub(crate) fn zoned_date_time_string_parts<H: VmHost>(
|
||||
let wall_epoch =
|
||||
|offset_seconds: i64| local_to_epoch_ns(wall_parts.0, wall_parts.1, wall_parts.2, wall_parts.3, offset_seconds);
|
||||
|
||||
// 时区偏移是 epoch 的函数:候选 epoch 取墙钟按 UTC 解释的单次查找近似。
|
||||
let candidate_ns = wall_epoch(0).ok_or_else(|| crate::error::create_range_error(vm, "invalid date-time"))?;
|
||||
let time_zone_offset = zone_offset_seconds(&time_zone_id, candidate_ns.div_euclid(1_000_000_000) as i64)
|
||||
.ok_or_else(|| crate::error::create_range_error(vm, "invalid time zone"))?;
|
||||
|
||||
// 按 offsetBehaviour 决策:Z → exact(墙钟 epoch);无偏移 → wall(墙钟 + 时区偏移);
|
||||
// 有偏移 → 按 offset 选项在字符串偏移与时区偏移之间选择。
|
||||
let epoch_ns = if has_utc_designator {
|
||||
@@ -395,9 +401,8 @@ fn zoned_date_time_bag_parts<H: VmHost>(
|
||||
return Err(crate::error::create_type_error(vm, "timeZone is required"));
|
||||
}
|
||||
let time_zone_input = temporal_option_string(vm, time_zone_raw)?;
|
||||
let Some((time_zone_id, time_zone_offset)) = canonical_time_zone(&time_zone_input) else {
|
||||
return Err(crate::error::create_range_error(vm, "invalid time zone"));
|
||||
};
|
||||
let time_zone_id = canonical_time_zone(&time_zone_input)
|
||||
.ok_or_else(|| crate::error::create_range_error(vm, "invalid time zone"))?;
|
||||
|
||||
// offset 可选:语法校验(ToOffsetString 语义)先于数值字段转换。
|
||||
let offset_raw = temporal_option_value(vm, obj, value, "offset")?;
|
||||
@@ -406,7 +411,7 @@ fn zoned_date_time_bag_parts<H: VmHost>(
|
||||
} else {
|
||||
let offset_input = temporal_option_string(vm, offset_raw)?;
|
||||
Some(
|
||||
instant_time_zone_offset(&offset_input)
|
||||
fixed_offset_seconds(&offset_input)
|
||||
.ok_or_else(|| crate::error::create_range_error(vm, "invalid offset"))?,
|
||||
)
|
||||
};
|
||||
@@ -414,6 +419,12 @@ fn zoned_date_time_bag_parts<H: VmHost>(
|
||||
// 年月日字段与 calendar:内部先读 calendar 再按字母序读字段并做类型转换校验。
|
||||
let (year, month, day, total_ns, calendar) = plain_date_time_object_parts(vm, value, obj, false, false)?;
|
||||
|
||||
// 时区偏移是 epoch 的函数:候选 epoch 取墙钟按 UTC 解释的单次查找近似。
|
||||
let candidate_ns = local_to_epoch_ns(year, month, day, total_ns, 0)
|
||||
.ok_or_else(|| crate::error::create_range_error(vm, "invalid date-time"))?;
|
||||
let time_zone_offset = zone_offset_seconds(&time_zone_id, candidate_ns.div_euclid(1_000_000_000) as i64)
|
||||
.ok_or_else(|| crate::error::create_range_error(vm, "invalid time zone"))?;
|
||||
|
||||
// 按 offset 选项决定 epoch(InterpretISODateTimeOffset 固定偏移简化:候选恒唯一)。
|
||||
let epoch_ns = match (bag_offset_seconds, offset_mode) {
|
||||
(None, _) | (Some(_), "ignore") => local_to_epoch_ns(year, month, day, total_ns, time_zone_offset),
|
||||
@@ -482,13 +493,11 @@ pub fn zoned_date_time_compare<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResu
|
||||
}))
|
||||
}
|
||||
|
||||
/// 时区注解文本:命名区原样返回,偏移区规范化为 `±HH:MM` 带冒号;critical 时 `!` 置于括号内。
|
||||
/// 时区注解文本:命名区原样返回,偏移区规范化为 `±HH:MM` 带冒号(秒非零时 `±HH:MM:SS`);
|
||||
/// critical 时 `!` 置于括号内。
|
||||
pub(crate) 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'-')) {
|
||||
let offset_seconds = instant_time_zone_offset(time_zone_id).unwrap_or(0);
|
||||
let sign = if offset_seconds < 0 { '-' } else { '+' };
|
||||
let magnitude = offset_seconds.abs();
|
||||
format!("{sign}{:02}:{:02}", magnitude / 3600, (magnitude % 3600) / 60)
|
||||
format_offset_seconds_text(fixed_offset_seconds(time_zone_id).unwrap_or(0))
|
||||
} else {
|
||||
time_zone_id.to_string()
|
||||
};
|
||||
@@ -504,7 +513,7 @@ pub(crate) fn format_time_zone_annotation(time_zone_id: &str, critical: bool) ->
|
||||
/// # 边界与前提
|
||||
/// - epoch_ns 须已按 quantum 舍入(调用方保证),此处仅做本地墙钟分解。
|
||||
/// - offset_name/time_zone_name 取 auto/never/critical,calendar_name 取 auto/never/always/critical。
|
||||
/// - 偏移段恒 `±HH:MM` 带冒号;时区注解命名区原样、偏移区规范化。
|
||||
/// - 偏移段 `±HH:MM` 带冒号(秒非零时 `±HH:MM:SS`);时区注解命名区原样、偏移区规范化。
|
||||
#[expect(clippy::too_many_arguments)]
|
||||
pub(crate) fn format_zoned_date_time_iso(
|
||||
epoch_ns: i128, offset_seconds: i64, time_zone_id: &str, calendar_id: &str, include_seconds: bool,
|
||||
@@ -544,12 +553,10 @@ pub(crate) fn format_zoned_date_time_iso(
|
||||
|
||||
// offset 段:auto/always 显示,never 省略,critical 段前加 !。
|
||||
if offset_name != "never" {
|
||||
let sign = if offset_seconds < 0 { '-' } else { '+' };
|
||||
let magnitude = offset_seconds.abs();
|
||||
if offset_name == "critical" {
|
||||
output.push('!');
|
||||
}
|
||||
output.push_str(&format!("{sign}{:02}:{:02}", magnitude / 3600, (magnitude % 3600) / 60));
|
||||
output.push_str(&format_offset_seconds_text(offset_seconds));
|
||||
}
|
||||
|
||||
// timeZoneName 注解:auto 显示,never 省略,critical 时 `!` 置于括号内。
|
||||
@@ -736,7 +743,7 @@ pub fn zoned_date_time_with_time_zone<H: VmHost>(vm: &mut H, args: &[u8]) -> Nat
|
||||
return NativeResult::Err(crate::error::create_type_error(vm, "invalid time zone"));
|
||||
}
|
||||
let input = to_string(raw);
|
||||
let Some((time_zone_id, _)) = canonical_time_zone(&input) else {
|
||||
let Some(time_zone_id) = canonical_time_zone(&input) else {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid time zone"));
|
||||
};
|
||||
make_zoned_date_time(vm, epoch_ns, &time_zone_id, &calendar_id)
|
||||
@@ -1207,7 +1214,10 @@ fn zoned_date_time_other_wall_epoch_ns<H: VmHost>(
|
||||
if !valid_iso_date(year, month, day) || !total_ns.is_finite() || !(0.0..86_400_000_000_000.0).contains(&total_ns) {
|
||||
return Err(crate::error::create_range_error(vm, "invalid date-time component"));
|
||||
}
|
||||
let offset_seconds = instant_time_zone_offset(default_tz_id)
|
||||
// 时区偏移是 epoch 的函数:候选 epoch 取墙钟按 UTC 解释的单次查找近似。
|
||||
let candidate_ns = local_to_epoch_ns(year, month, day, total_ns, 0)
|
||||
.ok_or_else(|| crate::error::create_range_error(vm, "invalid date-time"))?;
|
||||
let offset_seconds = zone_offset_seconds(default_tz_id, candidate_ns.div_euclid(1_000_000_000) as i64)
|
||||
.ok_or_else(|| crate::error::create_range_error(vm, "invalid time zone"))?;
|
||||
local_to_epoch_ns(year, month, day, total_ns, offset_seconds)
|
||||
.ok_or_else(|| crate::error::create_range_error(vm, "invalid date-time"))
|
||||
@@ -1224,15 +1234,14 @@ fn zoned_date_time_other_bag_epoch_ns<H: VmHost>(
|
||||
vm: &mut H, value: JsValue, obj: &JsObject, default_tz_id: &str,
|
||||
) -> Result<i128, JsValue> {
|
||||
let time_zone_raw = temporal_option_value(vm, obj, value, "timeZone")?;
|
||||
let time_zone_offset = if time_zone_raw.is_undefined() {
|
||||
instant_time_zone_offset(default_tz_id)
|
||||
.ok_or_else(|| crate::error::create_range_error(vm, "invalid time zone"))?
|
||||
let time_zone_id = if time_zone_raw.is_undefined() {
|
||||
default_tz_id.to_string()
|
||||
} else {
|
||||
if !time_zone_raw.is_string() {
|
||||
return Err(crate::error::create_type_error(vm, "invalid time zone"));
|
||||
}
|
||||
let input = to_string(time_zone_raw);
|
||||
instant_time_zone_offset(&input).ok_or_else(|| crate::error::create_range_error(vm, "invalid time zone"))?
|
||||
canonical_time_zone(&to_string(time_zone_raw))
|
||||
.ok_or_else(|| crate::error::create_range_error(vm, "invalid time zone"))?
|
||||
};
|
||||
|
||||
let offset_raw = temporal_option_value(vm, obj, value, "offset")?;
|
||||
@@ -1244,12 +1253,17 @@ fn zoned_date_time_other_bag_epoch_ns<H: VmHost>(
|
||||
}
|
||||
let offset_input = to_string(offset_raw);
|
||||
Some(
|
||||
instant_time_zone_offset(&offset_input)
|
||||
fixed_offset_seconds(&offset_input)
|
||||
.ok_or_else(|| crate::error::create_range_error(vm, "invalid offset"))?,
|
||||
)
|
||||
};
|
||||
|
||||
let (year, month, day, total_ns, _calendar) = plain_date_time_object_parts(vm, value, obj, false, false)?;
|
||||
// 时区偏移是 epoch 的函数:候选 epoch 取墙钟按 UTC 解释的单次查找近似。
|
||||
let candidate_ns = local_to_epoch_ns(year, month, day, total_ns, 0)
|
||||
.ok_or_else(|| crate::error::create_range_error(vm, "invalid date-time"))?;
|
||||
let time_zone_offset = zone_offset_seconds(&time_zone_id, candidate_ns.div_euclid(1_000_000_000) as i64)
|
||||
.ok_or_else(|| crate::error::create_range_error(vm, "invalid time zone"))?;
|
||||
if let Some(offset) = bag_offset_seconds {
|
||||
if offset != time_zone_offset {
|
||||
return Err(crate::error::create_range_error(vm, "offset and time zone disagree"));
|
||||
@@ -1266,7 +1280,7 @@ fn zoned_date_time_other_bag_epoch_ns<H: VmHost>(
|
||||
/// ZDT 差值核心:两 ZDT 的 epoch 差 → receiver 时区下的本地墙钟分量差 → difference_core。
|
||||
///
|
||||
/// # 步骤
|
||||
/// 1. receiver 三槽读取:epoch、offset 分钟、时区 ID,本地分量经 zoned_date_time_plain_parts。
|
||||
/// 1. receiver 三槽读取:epoch、偏移秒、时区 ID,本地分量经 zoned_date_time_plain_parts。
|
||||
/// 2. other → epoch(zoned_date_time_other_epoch_ns,receiver 时区作 bag 默认)。
|
||||
/// 3. epoch 相等快速路径:先于任何分量计算返回全零时长。
|
||||
/// 4. other 本地分量:epoch + offset → div_euclid/rem_euclid 拆墙钟(receiver 时区)。
|
||||
@@ -1342,7 +1356,7 @@ pub(crate) fn zoned_date_time_round_unit(value: &str) -> Option<(i128, i128)> {
|
||||
/// # 步骤
|
||||
/// 1. roundTo 解析:undefined → TypeError;字符串 → {smallestUnit: 串};对象 → 依次 Get
|
||||
/// roundingIncrement → roundingMode → smallestUnit,全部读完再统一校验。
|
||||
/// 2. receiver:ensure_zoned_date_time → epoch_r;instant_time_zone_offset(tz) 得 offset_seconds;
|
||||
/// 2. receiver:ensure_zoned_date_time → epoch_r;zone_offset_seconds(tz, epoch_r) 得 offset_seconds;
|
||||
/// zoned_date_time_plain_parts 得 (y, m, d, time_ns)。
|
||||
/// 3. 单位表查 smallestUnit(None → RangeError "invalid smallest unit")。
|
||||
/// 4. roundingIncrement 校验:1..=1e9 且真因子(increment < units_per_day 且
|
||||
@@ -1363,7 +1377,7 @@ pub fn zoned_date_time_round<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid ZonedDateTime"));
|
||||
};
|
||||
let time_zone_id = to_string(obj.get_prop_at(1));
|
||||
let Some(offset_seconds) = instant_time_zone_offset(&time_zone_id) else {
|
||||
let Some(offset_seconds) = zone_offset_seconds(&time_zone_id, epoch_r.div_euclid(1_000_000_000) as i64) else {
|
||||
return NativeResult::Err(crate::error::create_range_error(vm, "invalid time zone"));
|
||||
};
|
||||
let calendar_id = get_calendar_id(obj, 2);
|
||||
@@ -1569,21 +1583,24 @@ zoned_date_time_brand_only_getter!(zoned_date_time_months_in_year, JsValue::floa
|
||||
zoned_date_time_brand_only_getter!(zoned_date_time_era, JsValue::undefined());
|
||||
zoned_date_time_brand_only_getter!(zoned_date_time_era_year, JsValue::undefined());
|
||||
|
||||
/// 读 ZDT 时区偏移(秒);槽 1 解析失败返回 RangeError。调用方须先 ensure_zoned_date_time。
|
||||
/// 读 ZDT 时区偏移(秒):epoch 依赖查找(槽 0 epoch 秒 + 槽 1 时区名 → zone_offset_seconds)。
|
||||
/// 调用方须先 ensure_zoned_date_time。
|
||||
fn zoned_date_time_offset_seconds<H: VmHost>(vm: &mut H, obj: &JsObject) -> Result<i64, JsValue> {
|
||||
let Some(epoch_ns) = get_instant_epoch_ns(obj) else {
|
||||
return Err(crate::error::create_range_error(vm, "invalid ZonedDateTime"));
|
||||
};
|
||||
let time_zone_id = to_string(obj.get_prop_at(1));
|
||||
instant_time_zone_offset(&time_zone_id).ok_or_else(|| crate::error::create_range_error(vm, "invalid time zone"))
|
||||
zone_offset_seconds(&time_zone_id, epoch_ns.div_euclid(1_000_000_000) as i64)
|
||||
.ok_or_else(|| crate::error::create_range_error(vm, "invalid time zone"))
|
||||
}
|
||||
|
||||
/// `Temporal.ZonedDateTime.prototype.offset`:由偏移秒数规范化为 ±HH:MM 字符串。
|
||||
/// `Temporal.ZonedDateTime.prototype.offset`:由偏移秒数规范化为 `±HH:MM` 字符串(秒非零时 `±HH:MM:SS`)。
|
||||
pub fn zoned_date_time_offset<H: VmHost>(vm: &mut H, args: &[u8]) -> NativeResult {
|
||||
let ptr = native_try!(receiver_obj(vm, args));
|
||||
let obj = unsafe { &*ptr };
|
||||
native_try!(ensure_zoned_date_time(vm, obj));
|
||||
let offset_seconds = native_try!(zoned_date_time_offset_seconds(vm, obj));
|
||||
let sign = if offset_seconds < 0 { '-' } else { '+' };
|
||||
let magnitude = offset_seconds.abs();
|
||||
NativeResult::Ok(vm.new_string(&format!("{sign}{:02}:{:02}", magnitude / 3600, (magnitude % 3600) / 60)))
|
||||
NativeResult::Ok(vm.new_string(&format_offset_seconds_text(offset_seconds)))
|
||||
}
|
||||
|
||||
/// `Temporal.ZonedDateTime.prototype.offsetNanoseconds`:偏移秒数换算纳秒(f64 精确域内)。
|
||||
|
||||
Reference in New Issue
Block a user